Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-12-11 13:38:14 -05:00
commit 1a259ae5bb
137 changed files with 5134 additions and 2474 deletions

View File

@ -1,4 +1,4 @@
{
"revision": "2e4d09abb604dab914f1f29001012d872b57ef9e",
"revision": "336e9464c144268a8902bbb2d9026be3ff2b327f",
"repo_path": "/integration/gaia-central"
}

View File

@ -57,7 +57,7 @@ MOZ_PLACES=
MOZ_B2G=1
if test "$OS_TARGET" = "Android"; then
MOZ_NUWA_PROCESS=1
MOZ_NUWA_PROCESS=0
fi
MOZ_FOLD_LIBS=1

View File

@ -1155,7 +1155,7 @@ pref("devtools.scratchpad.recentFilesMax", 10);
// Enable the Style Editor.
pref("devtools.styleeditor.enabled", true);
pref("devtools.styleeditor.transitions", true);
pref("devtools.styleeditor.source-maps-enabled", false);
// Enable the Shader Editor.
pref("devtools.shadereditor.enabled", false);

View File

@ -952,13 +952,13 @@ toolbarpaletteitem[place="palette"][hidden] {
@keyframes uitour-zoom {
from {
transform: scale(0.9);
transform: scale(0.8);
}
50% {
transform: scale(1.1);
transform: scale(1.0);
}
to {
transform: scale(0.9);
transform: scale(0.8);
}
}
@ -974,29 +974,27 @@ toolbarpaletteitem[place="palette"][hidden] {
}
}
html|div#UITourHighlight {
display: none;
position: absolute;
z-index: 10000000000;
#UITourHighlightContainer,
#UITourHighlight {
pointer-events: none;
}
html|div#UITourHighlight[active] {
#UITourHighlight[active] {
animation-delay: 2s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
display: block;
}
html|div#UITourHighlight[active="wobble"] {
#UITourHighlight[active="wobble"] {
animation-name: uitour-wobble;
animation-duration: 1s;
}
html|div#UITourHighlight[active="zoom"] {
#UITourHighlight[active="zoom"] {
animation-name: uitour-zoom;
animation-duration: 1s;
}
html|div#UITourHighlight[active="color"] {
#UITourHighlight[active="color"] {
animation-name: uitour-color;
animation-duration: 2s;
}

View File

@ -2929,13 +2929,37 @@ const BrowserSearch = {
return;
}
#endif
var searchBar = this.searchBar;
if (searchBar && window.fullScreen)
let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField)
openUILinkIn(Services.search.defaultEngine.searchForm, "current");
};
let searchBar = this.searchBar;
let placement = CustomizableUI.getPlacementOfWidget("search-container");
if (placement && placement.area == CustomizableUI.AREA_PANEL) {
PanelUI.show().then(() => {
// The panel is not constructed until the first time it is shown.
searchBar = this.searchBar;
searchBar.select();
openSearchPageIfFieldIsNotActive(searchBar);
});
return;
} else if (placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
searchBar.parentNode.classList.contains("overflowedItem")) {
let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
navBar.overflowable.show().then(() => {
// The searchBar gets moved when the overflow panel opens.
searchBar = this.searchBar;
searchBar.select();
openSearchPageIfFieldIsNotActive(searchBar);
});
return;
}
if (searchBar && window.fullScreen) {
FullScreen.mouseoverToggle(true);
if (searchBar)
searchBar.select();
if (!searchBar || document.activeElement != searchBar.textbox.inputField)
openUILinkIn(Services.search.defaultEngine.searchForm, "current");
}
openSearchPageIfFieldIsNotActive(searchBar);
},
/**

View File

@ -197,17 +197,20 @@
<!-- UI tour experience -->
<panel id="UITourTooltip"
type="arrow"
hidden="true"
noautofocus="true"
noautohide="true"
align="start"
orient="vertical"
role="alert">
<label id="UITourTooltipTitle" flex="1"/>
<description id="UITourTooltipDescription" flex="1"/>
</panel>
<html:div id="UITourHighlightContainer" style="position:relative">
<html:div id="UITourHighlight"></html:div>
</html:div>
<panel id="UITourHighlightContainer"
noautofocus="true"
noautohide="true"
consumeoutsideclicks="false">
<box id="UITourHighlight"></box>
</panel>
<panel id="socialActivatedNotification"
type="arrow"

View File

@ -5,7 +5,6 @@
<panel id="PanelUI-popup"
role="group"
type="arrow"
level="top"
hidden="true"
noautofocus="true">
<panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">

View File

@ -6,6 +6,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
"resource:///modules/ScrollbarSampler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
/**
* Maintains the state and dispatches events for the main menu panel.
*/
@ -116,9 +118,11 @@ const PanelUI = {
* @param aEvent the event (if any) that triggers showing the menu.
*/
show: function(aEvent) {
if (this.panel.state == "open" || this.panel.state == "showing" ||
let deferred = Promise.defer();
if (this.panel.state == "open" ||
document.documentElement.hasAttribute("customizing")) {
return;
deferred.resolve();
return deferred.promise;
}
this.ensureReady().then(() => {
@ -144,7 +148,14 @@ const PanelUI = {
aEvent.sourceEvent.target.localName == "key";
this.panel.setAttribute("noautofocus", !keyboardOpened);
this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
this.panel.addEventListener("popupshown", function onPopupShown() {
this.removeEventListener("popupshown", onPopupShown);
deferred.resolve();
});
});
return deferred.promise;
},
/**

View File

@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
return Services.strings.createBundle(kUrl);
@ -3108,6 +3110,26 @@ OverflowableToolbar.prototype = {
}
},
show: function() {
let deferred = Promise.defer();
if (this._panel.state == "open") {
deferred.resolve();
return deferred.promise;
}
let doc = this._panel.ownerDocument;
this._panel.hidden = false;
let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
this._panel.openPopup(anchor || this._chevron, "bottomcenter topright");
this._chevron.open = true;
this._panel.addEventListener("popupshown", function onPopupShown() {
this.removeEventListener("popupshown", onPopupShown);
deferred.resolve();
});
return deferred.promise;
},
_onClickChevron: function(aEvent) {
if (this._chevron.open)
this._panel.hidePopup();

View File

@ -20,6 +20,7 @@ support-files =
[browser_892955_isWidgetRemovable_for_removed_widgets.js]
[browser_892956_destroyWidget_defaultPlacements.js]
[browser_909779_overflow_toolbars_new_window.js]
[browser_901207_searchbar_in_panel.js]
[browser_913972_currentset_overflow.js]
[browser_914138_widget_API_overflowable_toolbar.js]

View File

@ -0,0 +1,107 @@
/* 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/. */
let gTests = [
{
desc: "Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.",
run: function() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
let shownPanelPromise = promisePanelShown(window);
sendWebSearchKeyCommand();
yield shownPanelPromise;
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
},
teardown: function() {
CustomizableUI.reset();
}
},
{
desc: "Ctrl+K should give focus to the searchbar when the searchbar is in the menupanel and the panel is already opened.",
run: function() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
let shownPanelPromise = promisePanelShown(window);
PanelUI.toggle({type: "command"});
yield shownPanelPromise;
sendWebSearchKeyCommand();
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
},
teardown: function() {
CustomizableUI.reset();
}
},
{
desc: "Ctrl+K should open the overflow panel and focus the search bar if the search bar is overflowed.",
setup: function() {
this.originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(480, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
},
run: function() {
let searchbar = document.getElementById("searchbar");
let shownPanelPromise = promiseOverflowShown(window);
sendWebSearchKeyCommand();
yield shownPanelPromise;
let chevron = document.getElementById("nav-bar-overflow-button");
yield waitForCondition(function() chevron.open);
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
let hiddenPanelPromise = promiseOverflowHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
},
teardown: function() {
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
window.resizeTo(this.originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
}
},
];
function test() {
waitForExplicitFinish();
runTests(gTests);
}
function sendWebSearchKeyCommand() {
if (Services.appinfo.OS === "Darwin")
EventUtils.synthesizeKey("k", { accelKey: true });
else
EventUtils.synthesizeKey("k", { ctrlKey: true });
}
function logActiveElement() {
let element = document.activeElement;
info("Active element: " + element ?
element + " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ")" :
"null");
}

View File

@ -196,31 +196,49 @@ function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
function promisePanelShown(win) {
let panelEl = win.PanelUI.panel;
return promisePanelElementShown(win, panelEl);
}
function promiseOverflowShown(win) {
let panelEl = win.document.getElementById("widget-overflow");
return promisePanelElementShown(win, panelEl);
}
function promisePanelElementShown(win, aPanel) {
let deferred = Promise.defer();
let timeoutId = win.setTimeout(() => {
deferred.reject("Panel did not show within 20 seconds.");
}, 20000);
function onPanelOpen(e) {
panelEl.removeEventListener("popupshown", onPanelOpen);
aPanel.removeEventListener("popupshown", onPanelOpen);
win.clearTimeout(timeoutId);
deferred.resolve();
};
panelEl.addEventListener("popupshown", onPanelOpen);
aPanel.addEventListener("popupshown", onPanelOpen);
return deferred.promise;
}
function promisePanelHidden(win) {
let panelEl = win.PanelUI.panel;
return promisePanelElementHidden(win, panelEl);
}
function promiseOverflowHidden(win) {
let panelEl = document.getElementById("widget-overflow");
return promisePanelElementHidden(win, panelEl);
}
function promisePanelElementHidden(win, aPanel) {
let deferred = Promise.defer();
let timeoutId = win.setTimeout(() => {
deferred.reject("Panel did not hide within 20 seconds.");
}, 20000);
function onPanelClose(e) {
panelEl.removeEventListener("popuphidden", onPanelClose);
aPanel.removeEventListener("popuphidden", onPanelClose);
win.clearTimeout(timeoutId);
deferred.resolve();
}
panelEl.addEventListener("popuphidden", onPanelClose);
aPanel.addEventListener("popuphidden", onPanelClose);
return deferred.promise;
}

View File

@ -1228,7 +1228,7 @@ SourceScripts.prototype = {
* A promize that resolves to [aSource, isBlackBoxed] or rejects to
* [aSource, error].
*/
blackBox: function(aSource, aBlackBoxFlag) {
setBlackBoxing: function(aSource, aBlackBoxFlag) {
const sourceClient = this.activeThread.source(aSource);
const deferred = promise.defer();
@ -1282,11 +1282,9 @@ SourceScripts.prototype = {
// Revert the rejected promise from the cache, so that the original
// source's text may be shown when the source is selected.
this._cache.set(aSource.url, textPromise);
deferred.reject([aSource, message || error]);
return;
}
deferred.resolve([aSource, text]);
};
@ -1455,10 +1453,10 @@ EventListeners.prototype = {
return;
}
promise.all(aResponse.listeners.map(listener => {
let outstandingListenersDefinitionSite = aResponse.listeners.map(aListener => {
const deferred = promise.defer();
gThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
gThreadClient.pauseGrip(aListener.function).getDefinitionSite(aResponse => {
if (aResponse.error) {
const msg = "Error getting function definition site: " + aResponse.message;
DevToolsUtils.reportException("scheduleEventListenersFetch", msg);
@ -1466,13 +1464,15 @@ EventListeners.prototype = {
return;
}
listener.function.url = aResponse.url;
deferred.resolve(listener);
aListener.function.url = aResponse.url;
deferred.resolve(aListener);
});
return deferred.promise;
})).then(listeners => {
this._onEventListeners(listeners);
});
promise.all(outstandingListenersDefinitionSite).then(aListeners => {
this._onEventListeners(aListeners);
// Notify that event listeners were fetched and shown in the view,
// and callback to resume the active thread if necessary.

View File

@ -12,14 +12,16 @@ function SourcesView() {
dumpn("SourcesView was instantiated");
this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
this.toggleBreakpoints = this.toggleBreakpoints.bind(this);
this._onEditorLoad = this._onEditorLoad.bind(this);
this._onEditorUnload = this._onEditorUnload.bind(this);
this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onSourceClick = this._onSourceClick.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
@ -27,6 +29,7 @@ function SourcesView() {
this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
this._onConditionalTextboxInput = this._onConditionalTextboxInput.bind(this);
this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
this.updateToolbarButtonsState = this.updateToolbarButtonsState.bind(this);
}
@ -52,6 +55,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._blackBoxButton = document.getElementById("black-box");
this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
this._prettyPrintButton = document.getElementById("pretty-print");
this._toggleBreakpointsButton = document.getElementById("toggle-breakpoints");
if (Prefs.prettyPrintEnabled) {
this._prettyPrintButton.removeAttribute("hidden");
@ -220,6 +224,16 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
aItem.attachment.line == aLocation.line);
},
/**
* Returns all breakpoints for all sources.
*
* @return array
* The breakpoints for all sources if any, an empty array otherwise.
*/
getAllBreakpoints: function(aStore = []) {
return this.getOtherBreakpoints(undefined, aStore);
},
/**
* Returns all breakpoints which are not at the specified source url and line.
*
@ -274,6 +288,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
document.getElementById(enableSelfId).setAttribute("hidden", "true");
document.getElementById(disableSelfId).removeAttribute("hidden");
// Update the breakpoint toggle button checked state.
this._toggleBreakpointsButton.removeAttribute("checked");
// Update the checkbox state if necessary.
if (!aOptions.silent) {
attachment.view.checkbox.setAttribute("checked", "true");
@ -374,6 +391,29 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._hideConditionalPopup();
},
/**
* Update the checked/unchecked and enabled/disabled states of the buttons in
* the sources toolbar based on the currently selected source's state.
*/
updateToolbarButtonsState: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
if (sourceClient.isBlackBoxed) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
if (sourceClient.isPrettyPrinted) {
this._prettyPrintButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("checked");
}
},
/**
* Toggle the pretty printing of the selected source.
*/
@ -395,11 +435,13 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
DebuggerView.showProgressBar();
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
const shouldPrettyPrint = !sourceClient.isPrettyPrinted;
if (gThreadClient.source(source).isPrettyPrinted) {
this._prettyPrintButton.removeAttribute("checked");
} else {
if (shouldPrettyPrint) {
this._prettyPrintButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("checked");
}
DebuggerController.SourceScripts.togglePrettyPrint(source)
@ -408,6 +450,50 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
.then(this.updateToolbarButtonsState);
},
/**
* Toggle the black boxed state of the selected source.
*/
toggleBlackBoxing: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
const shouldBlackBox = !sourceClient.isBlackBoxed;
// Be optimistic that the (un-)black boxing will succeed, so enable/disable
// the pretty print button and check/uncheck the black box button
// immediately. Then, once we actually get the results from the server, make
// sure that it is in the correct state again by calling
// `updateToolbarButtonsState`.
if (shouldBlackBox) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
DebuggerController.SourceScripts.setBlackBoxing(source, shouldBlackBox)
.then(this.updateToolbarButtonsState,
this.updateToolbarButtonsState);
},
/**
* Toggles all breakpoints enabled/disabled.
*/
toggleBreakpoints: function() {
let breakpoints = this.getAllBreakpoints();
let hasBreakpoints = breakpoints.length > 0;
let hasEnabledBreakpoints = breakpoints.some(e => !e.attachment.disabled);
if (hasBreakpoints && hasEnabledBreakpoints) {
this._toggleBreakpointsButton.setAttribute("checked", true);
this._onDisableAll();
} else {
this._toggleBreakpointsButton.removeAttribute("checked");
this._onEnableAll();
}
},
/**
* Marks a breakpoint as selected in this sources container.
*
@ -694,29 +780,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this.updateToolbarButtonsState();
},
/**
* Update the checked/unchecked and enabled/disabled states of the buttons in
* the sources toolbar based on the currently selected source's state.
*/
updateToolbarButtonsState: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
if (sourceClient.isBlackBoxed) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
if (sourceClient.isPrettyPrinted) {
this._prettyPrintButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("checked");
}
},
/**
* The click listener for the sources container.
*/
@ -725,39 +788,13 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
DebuggerView.Filtering.target = this;
},
/**
* Toggle the black boxed state of the selected source.
*/
toggleBlackBoxing: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
const shouldBlackBox = !sourceClient.isBlackBoxed;
// Be optimistic that the (un-)black boxing will succeed, so enable/disable
// the pretty print button and check/uncheck the black box button
// immediately. Then, once we actually get the results from the server, make
// sure that it is in the correct state again by calling
// `updateToolbarButtonsState`.
if (shouldBlackBox) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
DebuggerController.SourceScripts.blackBox(source, shouldBlackBox)
.then(this.updateToolbarButtonsState,
this.updateToolbarButtonsState);
},
/**
* The click listener for the "stop black boxing" button.
*/
_onStopBlackBoxing: function() {
let sourceForm = this.selectedItem.attachment.source;
DebuggerController.SourceScripts.blackBox(sourceForm, false)
const { source } = this.selectedItem.attachment;
DebuggerController.SourceScripts.setBlackBoxing(source, false)
.then(this.updateToolbarButtonsState,
this.updateToolbarButtonsState);
},

View File

@ -32,12 +32,14 @@
<commandset id="editMenuCommands"/>
<commandset id="debuggerCommands">
<command id="prettyPrintCommand"
oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
<command id="blackBoxCommand"
oncommand="DebuggerView.Sources.toggleBlackBoxing()"/>
<command id="unBlackBoxButton"
oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
<command id="prettyPrintCommand"
oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
<command id="toggleBreakpointsCommand"
oncommand="DebuggerView.Sources.toggleBreakpoints()"/>
<command id="nextSourceCommand"
oncommand="DebuggerView.Sources.selectNextItem()"/>
<command id="prevSourceCommand"
@ -333,16 +335,20 @@
<toolbar id="sources-toolbar" class="devtools-toolbar">
<hbox id="sources-controls">
<toolbarbutton id="black-box"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
command="blackBoxCommand"
class="devtools-toolbarbutton"/>
command="blackBoxCommand"/>
<toolbarbutton id="pretty-print"
class="devtools-toolbarbutton devtools-monospace"
label="{}"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
hidden="true"/>
</hbox>
<toolbarbutton id="toggle-breakpoints"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
command="toggleBreakpointsCommand"/>
</toolbar>
</tabpanel>
<tabpanel id="callstack-tabpanel">
@ -361,7 +367,7 @@
<button id="black-boxed-message-button"
class="devtools-toolbarbutton"
label="&debuggerUI.blackBoxMessage.unBlackBoxButton;"
image="chrome://browser/skin/devtools/blackBoxMessageEye.png"
image="chrome://browser/skin/devtools/debugger-blackboxMessageEye.png"
command="unBlackBoxCommand"/>
</vbox>
<vbox id="source-progress-container" align="center" pack="center">

View File

@ -77,6 +77,8 @@ support-files =
[browser_dbg_break-on-dom-06.js]
[browser_dbg_break-on-dom-07.js]
[browser_dbg_breakpoints-actual-location.js]
[browser_dbg_breakpoints-button-01.js]
[browser_dbg_breakpoints-button-02.js]
[browser_dbg_breakpoints-contextmenu.js]
[browser_dbg_breakpoints-disabled-reload.js]
[browser_dbg_breakpoints-editor.js]

View File

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test if the breakpoints toggle button works as advertised.
*/
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
function test() {
let gTab, gDebuggee, gPanel, gDebugger;
let gSources, gBreakpoints;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
waitForSourceShown(gPanel, "-01.js")
.then(addBreakpoints)
.then(testDisableBreakpoints)
.then(testEnableBreakpoints)
.then(() => ensureThreadClientState(gPanel, "resumed"))
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
function addBreakpoints() {
return promise.resolve(null)
.then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
.then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
.then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
.then(() => ensureThreadClientState(gPanel, "resumed"));
}
function testDisableBreakpoints() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 3);
gSources.toggleBreakpoints();
return finished.then(() => checkBreakpointsDisabled(true));
}
function testEnableBreakpoints() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
gSources.toggleBreakpoints();
return finished.then(() => checkBreakpointsDisabled(false));
}
function checkBreakpointsDisabled(aState, aTotal = 3) {
let breakpoints = gSources.getAllBreakpoints();
is(breakpoints.length, aTotal,
"Breakpoints should still be set.");
is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
"Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
}
}

View File

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test if the breakpoints toggle button works as advertised when there are
* some breakpoints already disabled.
*/
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
function test() {
let gTab, gDebuggee, gPanel, gDebugger;
let gSources, gBreakpoints;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
waitForSourceShown(gPanel, "-01.js")
.then(addBreakpoints)
.then(disableSomeBreakpoints)
.then(testToggleBreakpoints)
.then(testEnableBreakpoints)
.then(() => ensureThreadClientState(gPanel, "resumed"))
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
function addBreakpoints() {
return promise.resolve(null)
.then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
.then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
.then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
.then(() => ensureThreadClientState(gPanel, "resumed"));
}
function disableSomeBreakpoints() {
return promise.all([
gSources.disableBreakpoint({ url: gSources.values[0], line: 5 }),
gSources.disableBreakpoint({ url: gSources.values[1], line: 6 })
]);
}
function testToggleBreakpoints() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 1);
gSources.toggleBreakpoints();
return finished.then(() => checkBreakpointsDisabled(true));
}
function testEnableBreakpoints() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
gSources.toggleBreakpoints();
return finished.then(() => checkBreakpointsDisabled(false));
}
function checkBreakpointsDisabled(aState, aTotal = 3) {
let breakpoints = gSources.getAllBreakpoints();
is(breakpoints.length, aTotal,
"Breakpoints should still be set.");
is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
"Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
}
}

View File

@ -26,7 +26,7 @@ loader.lazyGetter(this, "OptionsPanel", () => require("devtools/framework/toolbo
loader.lazyGetter(this, "InspectorPanel", () => require("devtools/inspector/inspector-panel").InspectorPanel);
loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/webconsole/panel").WebConsolePanel);
loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/debugger/panel").DebuggerPanel);
loader.lazyImporter(this, "StyleEditorPanel", "resource:///modules/devtools/StyleEditorPanel.jsm");
loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/styleeditor/styleeditor-panel").StyleEditorPanel);
loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel);
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel"));
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel);

View File

@ -5,4 +5,6 @@
include $(topsrcdir)/config/rules.mk
libs::
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools/
$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/styleeditor

View File

@ -1,346 +0,0 @@
/* 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";
this.EXPORTED_SYMBOLS = ["StyleEditorDebuggee", "StyleSheet"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
XPCOMUtils.defineLazyModuleGetter(this, "promise",
"resource://gre/modules/commonjs/sdk/core/promise.js", "Promise");
/**
* A StyleEditorDebuggee represents the document the style editor is debugging.
* It maintains a list of StyleSheet objects that represent the stylesheets in
* the target's document. It wraps remote debugging protocol comunications.
*
* It emits these events:
* 'document-load': debuggee's document is loaded, style sheets are argument
* 'stylesheets-cleared': The debuggee's stylesheets have been reset (e.g. the
* page navigated)
*
* @param {Target} target
* The target the debuggee is listening to
*/
let StyleEditorDebuggee = function(target) {
EventEmitter.decorate(this);
this.styleSheets = [];
this.clear = this.clear.bind(this);
this._onNewDocument = this._onNewDocument.bind(this);
this._onDocumentLoad = this._onDocumentLoad.bind(this);
this._target = target;
this._actor = this.target.form.styleEditorActor;
this.client.addListener("documentLoad", this._onDocumentLoad);
this._target.on("navigate", this._onNewDocument);
this._onNewDocument();
}
StyleEditorDebuggee.prototype = {
/**
* list of StyleSheet objects for this target
*/
styleSheets: null,
/**
* baseURIObject for the current document
*/
baseURI: null,
/**
* The target we're debugging
*/
get target() {
return this._target;
},
/**
* Client for communicating with server with remote debug protocol.
*/
get client() {
return this._target.client;
},
/**
* Get the StyleSheet object with the given href.
*
* @param {string} href
* Url of the stylesheet to find
* @return {StyleSheet}
* StyleSheet with the matching href
*/
styleSheetFromHref: function(href) {
for (let sheet of this.styleSheets) {
if (sheet.href == href) {
return sheet;
}
}
return null;
},
/**
* Clear stylesheets and state.
*/
clear: function() {
this.baseURI = null;
this.clearStyleSheets();
},
/**
* Clear stylesheets.
*/
clearStyleSheets: function() {
for (let stylesheet of this.styleSheets) {
stylesheet.destroy();
}
this.styleSheets = [];
this.emit("stylesheets-cleared");
},
/**
* Called when target is created or has navigated.
* Clear previous sheets and request new document's
*/
_onNewDocument: function() {
this.clear();
this._getBaseURI();
let message = { type: "newDocument" };
this._sendRequest(message);
},
/**
* request baseURIObject information from the document
*/
_getBaseURI: function() {
let message = { type: "getBaseURI" };
this._sendRequest(message, (response) => {
this.baseURI = Services.io.newURI(response.baseURI, null, null);
});
},
/**
* Handler for document load, forward event with
* all the stylesheets available on load.
*
* @param {string} type
* Event type
* @param {object} request
* Object with 'styleSheets' array of actor forms
*/
_onDocumentLoad: function(type, request) {
if (this.styleSheets.length > 0) {
this.clearStyleSheets();
}
let sheets = [];
for (let form of request.styleSheets) {
let sheet = this._addStyleSheet(form);
sheets.push(sheet);
}
this.emit("document-load", sheets);
},
/**
* Create a new StyleSheet object from the form
* and add to our stylesheet list.
*
* @param {object} form
* Initial properties of the stylesheet
*/
_addStyleSheet: function(form) {
let sheet = new StyleSheet(form, this);
this.styleSheets.push(sheet);
return sheet;
},
/**
* Create a new stylesheet with the given text
* and attach it to the document.
*
* @param {string} text
* Initial text of the stylesheet
* @param {function} callback
* Function to call when the stylesheet has been added to the document
*/
createStyleSheet: function(text, callback) {
let message = { type: "newStyleSheet", text: text };
this._sendRequest(message, (response) => {
let sheet = this._addStyleSheet(response.styleSheet);
callback(sheet);
});
},
/**
* Send a request to our actor on the server
*
* @param {object} message
* Message to send to the actor
* @param {function} callback
* Function to call with reponse from actor
*/
_sendRequest: function(message, callback) {
message.to = this._actor;
this.client.request(message, callback);
},
/**
* Clean up and remove listeners
*/
destroy: function() {
this.clear();
this._target.off("navigate", this._onNewDocument);
}
}
/**
* A StyleSheet object represents a stylesheet on the debuggee. It wraps
* communication with a complimentary StyleSheetActor on the server.
*
* It emits these events:
* 'source-load' - The full text source of the stylesheet has been fetched
* 'property-change' - Any property (e.g 'disabled') has changed
* 'style-applied' - A change has been applied to the live stylesheet on the server
* 'error' - An error occured when loading or saving stylesheet
*
* @param {object} form
* Initial properties of the stylesheet
* @param {StyleEditorDebuggee} debuggee
* Owner of the stylesheet
*/
let StyleSheet = function(form, debuggee) {
EventEmitter.decorate(this);
this.debuggee = debuggee;
this._client = debuggee.client;
this._actor = form.actor;
this._onSourceLoad = this._onSourceLoad.bind(this);
this._onPropertyChange = this._onPropertyChange.bind(this);
this._onStyleApplied = this._onStyleApplied.bind(this);
this._client.addListener("sourceLoad", this._onSourceLoad);
this._client.addListener("propertyChange", this._onPropertyChange);
this._client.addListener("styleApplied", this._onStyleApplied);
// Backwards compatibility
this._client.addListener("sourceLoad-" + this._actor, this._onSourceLoad);
this._client.addListener("propertyChange-" + this._actor, this._onPropertyChange);
this._client.addListener("styleApplied-" + this._actor, this._onStyleApplied);
// set initial property values
for (let attr in form) {
this[attr] = form[attr];
}
}
StyleSheet.prototype = {
/**
* Toggle the disabled attribute of the stylesheet
*/
toggleDisabled: function() {
let message = { type: "toggleDisabled" };
this._sendRequest(message);
},
/**
* Request that the source of the stylesheet be fetched.
* 'source-load' event will be fired when it's been fetched.
*/
fetchSource: function() {
let message = { type: "fetchSource" };
this._sendRequest(message);
},
/**
* Update the stylesheet in place with the given full source.
*
* @param {string} sheetText
* Full text to update the stylesheet with
*/
update: function(sheetText) {
let message = { type: "update", text: sheetText, transition: true };
this._sendRequest(message);
},
/**
* Handle source load event from the client.
*
* @param {string} type
* Event type
* @param {object} request
* Event details
*/
_onSourceLoad: function(type, request) {
if (request.from == this._actor) {
if (request.error) {
return this.emit("error", request.error);
}
this.emit("source-load", request.source);
}
},
/**
* Handle a property change on the stylesheet
*
* @param {string} type
* Event type
* @param {object} request
* Event details
*/
_onPropertyChange: function(type, request) {
if (request.from == this._actor) {
this[request.property] = request.value;
this.emit("property-change", request.property);
}
},
/**
* Handle event when update has been successfully applied and propogate it.
*/
_onStyleApplied: function(type, request) {
if (request.from == this._actor) {
this.emit("style-applied");
}
},
/**
* Send a request to our actor on the server
*
* @param {object} message
* Message to send to the actor
* @param {function} callback
* Function to call with reponse from actor
*/
_sendRequest: function(message, callback) {
message.to = this._actor;
this._client.request(message, callback);
},
/**
* Clean up and remove event listeners
*/
destroy: function() {
this._client.removeListener("sourceLoad", this._onSourceLoad);
this._client.removeListener("propertyChange", this._onPropertyChange);
this._client.removeListener("styleApplied", this._onStyleApplied);
this._client.removeListener("sourceLoad-" + this._actor, this._onSourceLoad);
this._client.removeListener("propertyChange-" + this._actor, this._onPropertyChange);
this._client.removeListener("styleApplied-" + this._actor, this._onStyleApplied);
}
}

View File

@ -25,6 +25,8 @@ const LOAD_ERROR = "error-load";
const STYLE_EDITOR_TEMPLATE = "stylesheet";
const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
/**
* StyleEditorUI is controls and builds the UI of the Style Editor, including
* maintaining a list of editors for each stylesheet on a debuggee.
@ -34,15 +36,18 @@ const STYLE_EDITOR_TEMPLATE = "stylesheet";
* 'editor-selected': An editor was selected
* 'error': An error occured
*
* @param {StyleEditorDebuggee} debuggee
* Debuggee of whose stylesheets should be shown in the UI
* @param {StyleEditorFront} debuggee
* Client-side front for interacting with the page's stylesheets
* @param {Target} target
* Interface for the page we're debugging
* @param {Document} panelDoc
* Document of the toolbox panel to populate UI in.
*/
function StyleEditorUI(debuggee, panelDoc) {
function StyleEditorUI(debuggee, target, panelDoc) {
EventEmitter.decorate(this);
this._debuggee = debuggee;
this._target = target;
this._panelDoc = panelDoc;
this._window = this._panelDoc.defaultView;
this._root = this._panelDoc.getElementById("style-editor-chrome");
@ -51,14 +56,18 @@ function StyleEditorUI(debuggee, panelDoc) {
this.selectedEditor = null;
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
this._onStyleSheetsCleared = this._onStyleSheetsCleared.bind(this);
this._onDocumentLoad = this._onDocumentLoad.bind(this);
this._onNewDocument = this._onNewDocument.bind(this);
this._clear = this._clear.bind(this);
this._onError = this._onError.bind(this);
debuggee.on("document-load", this._onDocumentLoad);
debuggee.on("stylesheets-cleared", this._onStyleSheetsCleared);
this.createUI();
this._debuggee.getStyleSheets().then((styleSheets) => {
this._resetStyleSheetList(styleSheets);
this._target.on("will-navigate", this._clear);
this._target.on("navigate", this._onNewDocument);
})
}
StyleEditorUI.prototype = {
@ -101,7 +110,7 @@ StyleEditorUI.prototype = {
this._view = new SplitView(viewRoot);
wire(this._view.rootElement, ".style-editor-newButton", function onNew() {
this._debuggee.createStyleSheet(null, this._onStyleSheetCreated);
this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated);
}.bind(this));
wire(this._view.rootElement, ".style-editor-importButton", function onImport() {
@ -109,6 +118,112 @@ StyleEditorUI.prototype = {
}.bind(this));
},
/**
* Refresh editors to reflect the stylesheets in the document.
*
* @param {string} event
* Event name
* @param {StyleSheet} styleSheet
* StyleSheet object for new sheet
*/
_onNewDocument: function() {
this._debuggee.getStyleSheets().then((styleSheets) => {
this._resetStyleSheetList(styleSheets);
})
},
/**
* Remove all editors and add loading indicator.
*/
_clear: function() {
// remember selected sheet and line number for next load
if (this.selectedEditor && this.selectedEditor.sourceEditor) {
let href = this.selectedEditor.styleSheet.href;
let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
this._styleSheetToSelect = {
href: href,
line: line,
col: ch
};
}
this._clearStyleSheetEditors();
this._view.removeAll();
this.selectedEditor = null;
this._root.classList.add("loading");
},
/**
* Add editors for all the given stylesheets to the UI.
*
* @param {array} styleSheets
* Array of StyleSheetFront
*/
_resetStyleSheetList: function(styleSheets) {
this._clear();
for (let sheet of styleSheets) {
this._addStyleSheet(sheet);
}
this._root.classList.remove("loading");
this.emit("stylesheets-reset");
},
/**
* Add an editor for this stylesheet. Add editors for its original sources
* instead (e.g. Sass sources), if applicable.
*
* @param {StyleSheetFront} styleSheet
* Style sheet to add to style editor
*/
_addStyleSheet: function(styleSheet) {
let editor = this._addStyleSheetEditor(styleSheet);
if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
return;
}
styleSheet.getOriginalSources().then((sources) => {
if (sources && sources.length) {
this._removeStyleSheetEditor(editor);
sources.forEach((source) => {
// set so the first sheet will be selected, even if it's a source
source.styleSheetIndex = styleSheet.styleSheetIndex;
this._addStyleSheetEditor(source);
});
}
});
},
/**
* Add a new editor to the UI for a source.
*
* @param {StyleSheet} styleSheet
* Object representing stylesheet
* @param {nsIfile} file
* Optional file object that sheet was imported from
* @param {Boolean} isNew
* Optional if stylesheet is a new sheet created by user
*/
_addStyleSheetEditor: function(styleSheet, file, isNew) {
let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew);
editor.on("property-change", this._summaryChange.bind(this, editor));
editor.on("style-applied", this._summaryChange.bind(this, editor));
editor.on("error", this._onError);
this.editors.push(editor);
editor.fetchSource(this._sourceLoaded.bind(this, editor));
return editor;
},
/**
* Import a style sheet from file and asynchronously create a
* new stylesheet on the debuggee for it.
@ -134,7 +249,7 @@ StyleEditorUI.prototype = {
let source = NetUtil.readInputStreamToString(stream, stream.available());
stream.close();
this._debuggee.createStyleSheet(source, (styleSheet) => {
this._debuggee.addStyleSheet(source).then((styleSheet) => {
this._onStyleSheetCreated(styleSheet, file);
});
});
@ -144,24 +259,6 @@ StyleEditorUI.prototype = {
showFilePicker(file, false, parentWindow, onFileSelected);
},
/**
* Handler for debuggee's 'stylesheets-cleared' event. Remove all editors.
*/
_onStyleSheetsCleared: function() {
// remember selected sheet and line number for next load
if (this.selectedEditor && this.selectedEditor.sourceEditor) {
let href = this.selectedEditor.styleSheet.href;
let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
this.selectStyleSheet(href, line, ch);
}
this._clearStyleSheetEditors();
this._view.removeAll();
this.selectedEditor = null;
this._root.classList.add("loading");
},
/**
* When a new or imported stylesheet has been added to the document.
@ -171,35 +268,6 @@ StyleEditorUI.prototype = {
this._addStyleSheetEditor(styleSheet, file, true);
},
/**
* Handler for debuggee's 'document-load' event. Add editors
* for all style sheets in the document
*
* @param {string} event
* Event name
* @param {StyleSheet} styleSheet
* StyleSheet object for new sheet
*/
_onDocumentLoad: function(event, styleSheets) {
if (this._styleSheetToSelect) {
// if selected stylesheet from previous load isn't here,
// just set first stylesheet to be selected instead
let selectedExists = styleSheets.some((sheet) => {
return this._styleSheetToSelect.href == sheet.href;
})
if (!selectedExists) {
this._styleSheetToSelect = null;
}
}
for (let sheet of styleSheets) {
this._addStyleSheetEditor(sheet);
}
this._root.classList.remove("loading");
this.emit("document-load");
},
/**
* Forward any error from a stylesheet.
*
@ -207,34 +275,35 @@ StyleEditorUI.prototype = {
* Event name
* @param {string} errorCode
* Code represeting type of error
* @param {string} message
* The full error message
*/
_onError: function(event, errorCode) {
this.emit("error", errorCode);
_onError: function(event, errorCode, message) {
this.emit("error", errorCode, message);
},
/**
* Add a new editor to the UI for a stylesheet.
* Remove a particular stylesheet editor from the UI
*
* @param {StyleSheet} styleSheet
* Object representing stylesheet
* @param {nsIfile} file
* Optional file object that sheet was imported from
* @param {Boolean} isNew
* Optional if stylesheet is a new sheet created by user
* @param {StyleSheetEditor} editor
* The editor to remove.
*/
_addStyleSheetEditor: function(styleSheet, file, isNew) {
let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew);
_removeStyleSheetEditor: function(editor) {
if (editor.summary) {
this._view.removeItem(editor.summary);
}
else {
let self = this;
this.on("editor-added", function onAdd(event, added) {
if (editor == added) {
self.off("editor-added", onAdd);
self._view.removeItem(editor.summary);
}
})
}
editor.once("source-load", this._sourceLoaded.bind(this, editor));
editor.on("property-change", this._summaryChange.bind(this, editor));
editor.on("style-applied", this._summaryChange.bind(this, editor));
editor.on("error", this._onError);
this.editors.push(editor);
// Queue editor loading. This helps responsivity during loading when
// there are many heavy stylesheets.
this._window.setTimeout(editor.fetchSource.bind(editor), 0);
editor.destroy();
this.editors.splice(this.editors.indexOf(editor), 1);
},
/**
@ -248,8 +317,8 @@ StyleEditorUI.prototype = {
},
/**
* Handler for an StyleSheetEditor's 'source-load' event.
* Create a summary UI for the editor.
* Called when a StyleSheetEditor's source has been fetched. Create a
* summary UI for the editor.
*
* @param {StyleSheetEditor} editor
* Editor to create UI for.
@ -310,8 +379,7 @@ StyleEditorUI.prototype = {
}
// If this is the first stylesheet, select it
if (this.selectedStyleSheetIndex == -1
&& !this._styleSheetToSelect
if (!this.selectedEditor
&& editor.styleSheet.styleSheetIndex == 0) {
this._selectEditor(editor);
}
@ -322,7 +390,6 @@ StyleEditorUI.prototype = {
onShow: function(summary, details, data) {
let editor = data.editor;
this.selectedEditor = editor;
this._styleSheetToSelect = null;
if (!editor.sourceEditor) {
// only initialize source editor when we switch to this view
@ -345,7 +412,8 @@ StyleEditorUI.prototype = {
for each (let editor in this.editors) {
if (editor.styleSheet.href == sheet.href) {
this._selectEditor(editor, sheet.line, sheet.col);
break;
this._styleSheetToSelect = null;
return;
}
}
},
@ -367,7 +435,6 @@ StyleEditorUI.prototype = {
editor.getSourceEditor().then(() => {
editor.sourceEditor.setCursor({line: line, ch: col});
});
this._view.activeSummary = editor.summary;
},
@ -466,8 +533,5 @@ StyleEditorUI.prototype = {
destroy: function() {
this._clearStyleSheetEditors();
this._debuggee.off("document-load", this._onDocumentLoad);
this._debuggee.off("stylesheets-cleared", this._onStyleSheetsCleared);
}
}

View File

@ -14,6 +14,7 @@ const Cu = Components.utils;
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const Editor = require("devtools/sourceeditor/editor");
const promise = require("sdk/core/promise");
const {CssLogic} = require("devtools/styleinspector/css-logic");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
@ -21,6 +22,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
const LOAD_ERROR = "error-load";
const SAVE_ERROR = "error-save";
// max update frequency in ms (avoid potential typing lag and/or flicker)
@ -32,12 +34,12 @@ const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
* object.
*
* Emits events:
* 'source-load': The source of the stylesheet has been fetched
* 'property-change': A property on the underlying stylesheet has changed
* 'source-editor-load': The source editor for this editor has been loaded
* 'error': An error has occured
*
* @param {StyleSheet} styleSheet
* @param {StyleSheet|OriginalSource} styleSheet
* Stylesheet or original source to show
* @param {DOMWindow} win
* panel window for style editor
* @param {nsIFile} file
@ -57,13 +59,19 @@ function StyleSheetEditor(styleSheet, win, file, isNew) {
this.errorMessage = null;
let readOnly = false;
if (styleSheet.isOriginalSource) {
// live-preview won't work with sources that need compilation
readOnly = true;
}
this._state = { // state to use when inputElement attaches
text: "",
selection: {
start: {line: 0, ch: 0},
end: {line: 0, ch: 0}
},
readOnly: false,
readOnly: readOnly,
topIndex: 0, // the first visible line
};
@ -73,30 +81,21 @@ function StyleSheetEditor(styleSheet, win, file, isNew) {
this._styleSheetFilePath = this.styleSheet.href;
}
this._onSourceLoad = this._onSourceLoad.bind(this);
this._onPropertyChange = this._onPropertyChange.bind(this);
this._onError = this._onError.bind(this);
this._focusOnSourceEditorReady = false;
this.styleSheet.once("source-load", this._onSourceLoad);
this.styleSheet.on("property-change", this._onPropertyChange);
this.styleSheet.on("error", this._onError);
}
StyleSheetEditor.prototype = {
/**
* This editor's source editor
*/
get sourceEditor() {
return this._sourceEditor;
},
/**
* Whether there are unsaved changes in the editor
*/
get unsaved() {
return this._sourceEditor && !this._sourceEditor.isClean();
return this.sourceEditor && !this.sourceEditor.isClean();
},
/**
@ -113,37 +112,23 @@ StyleSheetEditor.prototype = {
* @return string
*/
get friendlyName() {
if (this.savedFile) { // reuse the saved filename if any
if (this.savedFile) {
return this.savedFile.leafName;
}
if (this._isNew) {
let index = this.styleSheet.styleSheetIndex + 1; // 0-indexing only works for devs
let index = this.styleSheet.styleSheetIndex + 1;
return _("newStyleSheet", index);
}
if (!this.styleSheet.href) {
let index = this.styleSheet.styleSheetIndex + 1; // 0-indexing only works for devs
let index = this.styleSheet.styleSheetIndex + 1;
return _("inlineStyleSheet", index);
}
if (!this._friendlyName) {
let sheetURI = this.styleSheet.href;
let contentURI = this.styleSheet.debuggee.baseURI;
let contentURIScheme = contentURI.scheme;
let contentURILeafIndex = contentURI.specIgnoringRef.lastIndexOf("/");
contentURI = contentURI.specIgnoringRef;
// get content base URI without leaf name (if any)
if (contentURILeafIndex > contentURIScheme.length) {
contentURI = contentURI.substring(0, contentURILeafIndex + 1);
}
// avoid verbose repetition of absolute URI when the style sheet URI
// is relative to the content URI
this._friendlyName = (sheetURI.indexOf(contentURI) == 0)
? sheetURI.substring(contentURI.length)
: sheetURI;
this._friendlyName = CssLogic.shortSource({ href: sheetURI });
try {
this._friendlyName = decodeURI(this._friendlyName);
} catch (ex) {
@ -155,22 +140,17 @@ StyleSheetEditor.prototype = {
/**
* Start fetching the full text source for this editor's sheet.
*/
fetchSource: function() {
this.styleSheet.fetchSource();
},
fetchSource: function(callback) {
this.styleSheet.getText().then((longStr) => {
longStr.string().then((source) => {
this._state.text = prettifyCSS(source);
this.sourceLoaded = true;
/**
* Handle source fetched event. Forward source-load event.
*
* @param {string} event
* Event type
* @param {string} source
* Full-text source of the stylesheet
*/
_onSourceLoad: function(event, source) {
this._state.text = prettifyCSS(source);
this.sourceLoaded = true;
this.emit("source-load");
callback(source);
});
}, e => {
this.emit("error", LOAD_ERROR, this.styleSheet.href);
})
},
/**
@ -181,8 +161,8 @@ StyleSheetEditor.prototype = {
* @param {string} property
* Property that has changed on sheet
*/
_onPropertyChange: function(event, property) {
this.emit("property-change", property);
_onPropertyChange: function(property, value) {
this.emit("property-change", property, value);
},
/**
@ -220,7 +200,7 @@ StyleSheetEditor.prototype = {
this.updateStyleSheet();
});
this._sourceEditor = sourceEditor;
this.sourceEditor = sourceEditor;
if (this._focusOnSourceEditorReady) {
this._focusOnSourceEditorReady = false;
@ -320,7 +300,7 @@ StyleSheetEditor.prototype = {
this._state.text = this.sourceEditor.getText();
}
this.styleSheet.update(this._state.text);
this.styleSheet.update(this._state.text, true);
},
/**
@ -374,6 +354,8 @@ StyleSheetEditor.prototype = {
callback(returnFile);
}
this.sourceEditor.setClean();
this.emit("property-change");
}.bind(this));
};
@ -404,7 +386,6 @@ StyleSheetEditor.prototype = {
* Clean up for this editor.
*/
destroy: function() {
this.styleSheet.off("source-load", this._onSourceLoad);
this.styleSheet.off("property-change", this._onPropertyChange);
this.styleSheet.off("error", this._onError);
}

View File

@ -4,21 +4,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
this.EXPORTED_SYMBOLS = ["StyleEditorPanel"];
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
Cu.import("resource:///modules/devtools/StyleEditorDebuggee.jsm");
let promise = require("sdk/core/promise");
let EventEmitter = require("devtools/shared/event-emitter");
Cu.import("resource:///modules/devtools/StyleEditorUI.jsm");
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorChrome",
"resource:///modules/devtools/StyleEditorChrome.jsm");
loader.lazyGetter(this, "StyleSheetsFront",
() => require("devtools/server/actors/styleeditor").StyleSheetsFront);
this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
EventEmitter.decorate(this);
@ -32,6 +30,8 @@ this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
this._showError = this._showError.bind(this);
}
exports.StyleEditorPanel = StyleEditorPanel;
StyleEditorPanel.prototype = {
get target() this._toolbox.target,
@ -54,9 +54,9 @@ StyleEditorPanel.prototype = {
targetPromise.then(() => {
this.target.on("close", this.destroy);
this._debuggee = new StyleEditorDebuggee(this.target);
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
this.UI = new StyleEditorUI(this._debuggee, this._panelDoc);
this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
this.UI.on("error", this._showError);
this.isReady = true;
@ -99,7 +99,6 @@ StyleEditorPanel.prototype = {
if (!this._debuggee || !this.UI) {
return;
}
let stylesheet = this._debuggee.styleSheetFromHref(href);
this.UI.selectStyleSheet(href, line - 1, col ? col - 1 : 0);
},

View File

@ -19,6 +19,10 @@ support-files =
simple.css.gz^headers^
simple.gz.html
simple.html
sourcemaps.css
sourcemaps.css.map
sourcemaps.scss
sourcemaps.html
test_private.css
test_private.html
@ -42,3 +46,4 @@ skip-if = true
[browser_styleeditor_sv_keynav.js]
[browser_styleeditor_sv_resize.js]
[browser_styleeditor_selectstylesheet.js]
[browser_styleeditor_sourcemaps.js]

View File

@ -13,7 +13,8 @@ function test() {
gUI = panel.UI;
gUI.on("editor-added", function(event, editor) {
count++;
if (count == 2) {
if (count == 4) {
info("all editors added");
runTests();
}
})
@ -35,6 +36,8 @@ function getStylesheetNameLinkFor(aEditor) {
}
function onEditor0Attach(aEditor) {
info("first editor selected");
waitForFocus(function () {
// left mouse click should focus editor 1
EventUtils.synthesizeMouseAtCenter(
@ -45,6 +48,8 @@ function onEditor0Attach(aEditor) {
}
function onEditor1Attach(aEditor) {
info("second editor selected");
ok(aEditor.sourceEditor.hasFocus(),
"left mouse click has given editor 1 focus");

View File

@ -19,25 +19,25 @@ function test()
waitForExplicitFinish();
addTabAndOpenStyleEditor(function (aPanel) {
let debuggee = aPanel._debuggee;
let UI = aPanel.UI;
// Spam the _onNewDocument callback multiple times before the
// StyleEditorActor has a chance to respond to the first one.
const SPAM_COUNT = 2;
for (let i=0; i<SPAM_COUNT; ++i) {
debuggee._onNewDocument();
UI._onNewDocument();
}
// Wait for the StyleEditorActor to respond to each "newDocument"
// message.
let loadCount = 0;
debuggee.on("document-load", function () {
UI.on("stylesheets-reset", function () {
++loadCount;
if (loadCount == SPAM_COUNT) {
// No matter how large SPAM_COUNT is, the number of style
// sheets should never be more than the number of style sheets
// in the document.
is(debuggee.styleSheets.length, 1, "correct style sheet count");
is(UI.editors.length, 1, "correct style sheet count");
finish();
}
});

View File

@ -58,7 +58,7 @@ function testEditorAdded(aEvent, aEditor)
}
});
aEditor.styleSheet.on("property-change", function(event, property) {
aEditor.styleSheet.on("property-change", function(property) {
if (property == "ruleCount") {
let ruleCount = aEditor.summary.querySelector(".stylesheet-rule-count").textContent;
is(parseInt(ruleCount), 1,

View File

@ -15,7 +15,7 @@ function test()
// it is loaded until the accompanying content page is loaded.
addTabAndOpenStyleEditor(function(panel) {
panel.UI.once("document-load", testDocumentLoad);
panel.UI.once("stylesheets-reset", testDocumentLoad);
content.location = TESTCASE_URI;
});

View File

@ -33,12 +33,7 @@ function test() {
}
function onEditorAdded(aEvent, aEditor) {
if (aEditor.sourceLoaded) {
checkCache();
}
else {
aEditor.on("source-load", checkCache);
}
aEditor.getSourceEditor().then(checkCache);
}
function testOnWindow(options, callback) {

View File

@ -22,6 +22,7 @@ function test()
let count = 0;
gUI.on("editor-added", function editorAdded(event, editor) {
if (++count == 2) {
info("all editors added to UI");
gUI.off("editor-added", editorAdded);
gUI.editors[0].getSourceEditor().then(runTests);
}
@ -41,6 +42,7 @@ function runTests()
gUI.on("editor-added", function editorAdded(event, editor) {
if (++count == 2) {
info("all editors added after reload");
gUI.off("editor-added", editorAdded);
gUI.editors[1].getSourceEditor().then(testRemembered);
}
@ -67,6 +69,7 @@ function testNewPage()
gUI.on("editor-added", function editorAdded(event, editor) {
info("editor added here")
if (++count == 2) {
info("all editors added after navigating page");
gUI.off("editor-added", editorAdded);
gUI.editors[0].getSourceEditor().then(testNotRemembered);
}

View File

@ -0,0 +1,86 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// https rather than chrome to improve coverage
const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps.html";
const PREF = "devtools.styleeditor.source-maps-enabled";
function test()
{
waitForExplicitFinish();
Services.prefs.setBoolPref(PREF, true);
let count = 0;
addTabAndOpenStyleEditor(function(panel) {
let UI = panel.UI;
UI.on("editor-added", (event, editor) => {
if (++count >= 3) {
// wait for 3 editors - 1 for first style sheet, 1 for the
// generated style sheet, and 1 for original source after it
// loads and replaces the generated style sheet.
runTests(UI);
}
})
});
content.location = TESTCASE_URI;
}
function runTests(UI)
{
is(UI.editors.length, 2);
let firstEditor = UI.editors[0];
testFirstEditor(firstEditor);
let ScssEditor = UI.editors[1];
let link = getStylesheetNameLinkFor(ScssEditor);
link.click();
ScssEditor.getSourceEditor().then(() => {
testScssEditor(ScssEditor);
finishUp();
});
}
function testFirstEditor(editor) {
let name = getStylesheetNameFor(editor);
is(name, "simple.css", "First style sheet display name is correct");
}
function testScssEditor(editor) {
let name = getStylesheetNameFor(editor);
is(name, "sourcemaps.scss", "Original source display name is correct");
let text = editor.sourceEditor.getText();
is(text, "\n\
$paulrougetpink: #f06;\n\
\n\
div {\n\
color: $paulrougetpink;\n\
}\n\
\n\
span {\n\
background-color: #EEE;\n\
}", "Original source text is correct");
}
/* Helpers */
function getStylesheetNameLinkFor(editor) {
return editor.summary.querySelector(".stylesheet-name");
}
function getStylesheetNameFor(editor) {
return editor.summary.querySelector(".stylesheet-name > label")
.getAttribute("value")
}
function finishUp() {
Services.prefs.clearUserPref(PREF);
finish();
}

View File

@ -46,46 +46,10 @@ function openStyleEditorInWindow(win, callback) {
gPanelWindow = panel._panelWin;
panel.UI._alwaysDisableAnimations = true;
/*
if (aSheet) {
panel.selectStyleSheet(aSheet, aLine, aCol);
} */
callback(panel);
});
}
/*
function launchStyleEditorChrome(aCallback, aSheet, aLine, aCol)
{
launchStyleEditorChromeFromWindow(window, aCallback, aSheet, aLine, aCol);
}
function launchStyleEditorChromeFromWindow(aWindow, aCallback, aSheet, aLine, aCol)
{
let target = TargetFactory.forTab(aWindow.gBrowser.selectedTab);
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
let panel = toolbox.getCurrentPanel();
gPanelWindow = panel._panelWin;
gPanelWindow.styleEditorChrome._alwaysDisableAnimations = true;
if (aSheet) {
panel.selectStyleSheet(aSheet, aLine, aCol);
}
aCallback(gPanelWindow.styleEditorChrome);
});
}
function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback, aSheet, aLine, aCol)
{
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
}, true);
}
*/
function checkDiskCacheFor(host, done)
{
let foundPrivateData = false;

View File

@ -0,0 +1,7 @@
div {
color: #ff0066; }
span {
background-color: #EEE; }
/*# sourceMappingURL=sourcemaps.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAGA,GAAI;EACF,KAAK,EAHU,OAAI;;AAMrB,IAAK;EACH,gBAAgB,EAAE,IAAI",
"sources": ["sourcemaps.scss"],
"names": [],
"file": "sourcemaps.css"
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title>testcase for testing CSS source maps</title>
<link rel="stylesheet" type="text/css" href="simple.css"/>
<link rel="stylesheet" type="text/css" href="sourcemaps.css"/>
</head>
<body>
<div>source maps <span>testcase</span></div>
</body>
</html>

View File

@ -0,0 +1,10 @@
$paulrougetpink: #f06;
div {
color: $paulrougetpink;
}
span {
background-color: #EEE;
}

View File

@ -26,6 +26,8 @@ const FILTER_CHANGED_TIMEOUT = 300;
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
/**
* Helper for long-running processes that should yield occasionally to
* the mainloop.
@ -1096,6 +1098,25 @@ function SelectorView(aTree, aSelectorInfo)
if (rule && rule.parentStyleSheet) {
this.sheet = rule.parentStyleSheet;
this.source = CssLogic.shortSource(this.sheet) + ":" + rule.line;
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
if (showOrig && rule.type != ELEMENT_STYLE) {
rule.getOriginalLocation().then(({href, line, column}) => {
let newSource = CssLogic.shortSource({href: href}) + ":" + line;
// Really hacky. Setting the 'source' property won't change the
// link's text if the link's already been loaded via template, so we
// have to retroactively mutate the DOM.
if (newSource != this.source && this.tree.propertyContainer) {
let selector = '[sourcelocation="' + this.source + '"]';
let link = this.tree.propertyContainer.querySelector(selector);
if (link) {
link.textContent = newSource;
}
}
this.source = newSource;
});
}
} else {
this.source = CssLogic.l10n("rule.sourceElement");
this.href = "#";
@ -1217,37 +1238,42 @@ SelectorView.prototype = {
{
let inspector = this.tree.styleInspector.inspector;
let rule = this.selectorInfo.rule;
let line = rule.line || 0;
// The style editor can only display stylesheets coming from content because
// chrome stylesheets are not listed in the editor's stylesheet selector.
//
// If the stylesheet is a content stylesheet we send it to the style
// editor else we display it in the view source window.
//
let href = rule.href;
let sheet = rule.parentStyleSheet;
if (sheet && href && !sheet.isSystem) {
if (!sheet || sheet.isSystem) {
let contentDoc = null;
if (this.tree.viewedElement.isLocal_toBeDeprecated()) {
let rawNode = this.tree.viewedElement.rawNode();
if (rawNode) {
contentDoc = rawNode.ownerDocument;
}
}
let viewSourceUtils = inspector.viewSourceUtils;
viewSourceUtils.viewSource(rule.href, null, contentDoc, rule.line);
return;
}
let location = promise.resolve({
href: rule.href,
line: rule.line
});
if (rule.href && Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
location = rule.getOriginalLocation();
}
location.then(({href, line}) => {
let target = inspector.target;
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(href, line);
});
}
return;
}
let contentDoc = null;
if (this.tree.viewedElement.isLocal_toBeDeprecated()) {
let rawNode = this.tree.viewedElement.rawNode();
if (rawNode) {
contentDoc = rawNode.ownerDocument;
}
}
let viewSourceUtils = inspector.viewSourceUtils;
viewSourceUtils.viewSource(href, null, contentDoc, line);
});
}
};

View File

@ -99,6 +99,7 @@
onclick="${selector.openStyleEditor}"
onkeydown="${selector.maybeOpenStyleEditor}"
title="${selector.href}"
sourcelocation="${selector.source}"
tabindex="0">${selector.source}</a>
</span>
<span dir="ltr" class="rule-text ${selector.statusClass} theme-fg-color3" title="${selector.statusText}">

View File

@ -22,6 +22,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
/**
* These regular expressions are adapted from firebug's css.js, and are
* used to parse CSSStyleDeclaration's cssText attribute.
@ -487,6 +489,33 @@ Rule.prototype = {
return this.domRule ? this.domRule.line : null;
},
/**
* The rule's column within a stylesheet
*/
get ruleColumn()
{
return this.domRule ? this.domRule.column : null;
},
/**
* Get display name for this rule based on the original source
* for this rule's style sheet.
*
* @return {Promise}
* Promise which resolves with location as a string.
*/
getOriginalSourceString: function Rule_getOriginalSourceString()
{
if (this._originalSourceString) {
return promise.resolve(this._originalSourceString);
}
return this.domRule.getOriginalLocation().then(({href, line}) => {
let string = CssLogic.shortSource({href: href}) + ":" + line;
this._originalSourceString = string;
return string;
});
},
/**
* Returns true if the rule matches the creation options
* specified.
@ -1586,6 +1615,14 @@ RuleEditor.prototype = {
sourceLabel.setAttribute("tooltiptext", this.rule.title);
source.appendChild(sourceLabel);
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
if (showOrig && this.rule.domRule.type != ELEMENT_STYLE) {
this.rule.getOriginalSourceString().then((string) => {
sourceLabel.setAttribute("value", string);
sourceLabel.setAttribute("tooltiptext", string);
})
}
let code = createChild(this.element, "div", {
class: "ruleview-code"
});

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {Cc, Cu, Ci} = require("chrome");
const promise = require("sdk/core/promise");
let ToolDefinitions = require("main").Tools;
@ -16,6 +17,8 @@ loader.lazyGetter(this, "ComputedView", () => require("devtools/styleinspector/c
loader.lazyGetter(this, "_strings", () => Services.strings
.createBundle("chrome://global/locale/devtools/styleinspector.properties"));
const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
// This module doesn't currently export any symbols directly, it only
// registers inspector tools.
@ -42,29 +45,30 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
this._cssLinkHandler = (aEvent) => {
let rule = aEvent.detail.rule;
let line = rule.line || 0;
// The style editor can only display stylesheets coming from content because
// chrome stylesheets are not listed in the editor's stylesheet selector.
//
// If the stylesheet is a content stylesheet we send it to the style
// editor else we display it in the view source window.
//
let href = rule.href;
let sheet = rule.parentStyleSheet;
if (sheet && href && !sheet.isSystem) {
let target = this.inspector.target;
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(href, line);
});
}
// Chrome stylesheets are not listed in the style editor, so show
// these sheets in the view source window instead.
if (!sheet || !rule.href || sheet.isSystem) {
let contentDoc = this.inspector.selection.document;
let viewSourceUtils = this.inspector.viewSourceUtils;
viewSourceUtils.viewSource(rule.href, null, contentDoc, rule.line || 0);
return;
}
let contentDoc = this.inspector.selection.document;
let viewSourceUtils = this.inspector.viewSourceUtils;
viewSourceUtils.viewSource(href, null, contentDoc, line);
let location = promise.resolve(rule.location);
if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
location = rule.getOriginalLocation();
}
location.then(({ href, line, column }) => {
let target = this.inspector.target;
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(href, line, column);
});
}
return;
})
}
this.view.element.addEventListener("CssRuleViewCSSLinkClicked",

View File

@ -54,3 +54,10 @@ support-files = browser_ruleview_pseudoelement.html
[browser_bug765105_background_image_tooltip.js]
[browser_bug889638_rule_view_color_picker.js]
[browser_bug940500_rule_view_pick_gradient_color.js]
[browser_ruleview_original_source_link.js]
support-files =
sourcemaps.html
sourcemaps.css
sourcemaps.css.map
sourcemaps.scss
[browser_computedview_original_source_link.js]

View File

@ -0,0 +1,119 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let win;
let doc;
let inspector;
let computedView;
let toolbox;
const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps.html";
const PREF = "devtools.styleeditor.source-maps-enabled";
function test()
{
waitForExplicitFinish();
Services.prefs.setBoolPref(PREF, true);
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
true);
doc = content.document;
waitForFocus(function () { openComputedView(highlightNode); }, content);
}, true);
content.location = TESTCASE_URI;
}
function highlightNode(aInspector, aComputedView)
{
inspector = aInspector;
computedView = aComputedView;
// Highlight a node.
let div = content.document.getElementsByTagName("div")[0];
ok(div, "div to select exists")
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div, "selection matches the div element");
expandProperty(0, testComputedViewLink);
}).then(null, console.error);
}
function testComputedViewLink() {
let link = getLinkByIndex(0);
waitForSuccess({
name: "link text changed to display original source location",
validatorFn: function()
{
return link.textContent == "sourcemaps.scss:4";
},
successFn: linkChanged,
failureFn: linkChanged,
});
}
function linkChanged() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
toolbox.once("styleeditor-ready", function(id, aToolbox) {
let panel = toolbox.getCurrentPanel();
panel.UI.on("editor-selected", (event, editor) => {
// The style editor selects the first sheet at first load before
// selecting the desired sheet.
if (editor.styleSheet.href.endsWith("scss")) {
info("original source editor selected");
editor.getSourceEditor().then(editorSelected);
}
});
});
let link = getLinkByIndex(0);
info("clicking rule view link");
link.scrollIntoView();
link.click();
}
function editorSelected(editor) {
let href = editor.styleSheet.href;
ok(href.endsWith("sourcemaps.scss"), "selected stylesheet is correct one");
let {line, col} = editor.sourceEditor.getCursor();
is(line, 3, "cursor is at correct line number in original source");
finishUp();
}
/* Helpers */
function expandProperty(aIndex, aCallback)
{
info("expanding property " + aIndex);
let contentDoc = computedView.styleDocument;
let expando = contentDoc.querySelectorAll(".expandable")[aIndex];
expando.click();
inspector.once("computed-view-property-expanded", aCallback);
}
function getLinkByIndex(aIndex)
{
let contentDoc = computedView.styleDocument;
let links = contentDoc.querySelectorAll(".rule-link .link");
return links[aIndex];
}
function finishUp()
{
gBrowser.removeCurrentTab();
doc = inspector = computedView = toolbox = win = null;
Services.prefs.clearUserPref(PREF);
finish();
}

View File

@ -0,0 +1,112 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let win;
let doc;
let contentWindow;
let inspector;
let toolbox;
const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps.html";
const PREF = "devtools.styleeditor.source-maps-enabled";
function test()
{
waitForExplicitFinish();
Services.prefs.setBoolPref(PREF, true);
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
true);
doc = content.document;
waitForFocus(openToolbox, content);
}, true);
content.location = TESTCASE_URI;
}
function openToolbox() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(aToolbox) {
toolbox = aToolbox;
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("ruleview");
highlightNode();
});
}
function highlightNode()
{
// Highlight a node.
let div = content.document.getElementsByTagName("div")[0];
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div, "selection matches the div element");
testRuleViewLink();
}).then(null, console.error);
}
function testRuleViewLink() {
let label = getLinkByIndex(1).querySelector("label");
waitForSuccess({
name: "link text changed to display original source location",
validatorFn: function()
{
return label.getAttribute("value") == "sourcemaps.scss:4";
},
successFn: linkChanged,
failureFn: linkChanged,
});
}
function linkChanged() {
toolbox.once("styleeditor-ready", function(id, aToolbox) {
let panel = toolbox.getCurrentPanel();
panel.UI.on("editor-selected", (event, editor) => {
// The style editor selects the first sheet at first load before
// selecting the desired sheet.
if (editor.styleSheet.href.endsWith("scss")) {
info("original source editor selected");
editor.getSourceEditor().then(editorSelected);
}
});
});
let link = getLinkByIndex(1);
info("clicking rule view link");
link.scrollIntoView();
link.click();
}
function editorSelected(editor) {
let href = editor.styleSheet.href;
ok(href.endsWith("sourcemaps.scss"), "selected stylesheet is correct one");
let {line, col} = editor.sourceEditor.getCursor();
is(line, 3, "cursor is at correct line number in original source");
finishUp();
}
function getLinkByIndex(aIndex)
{
let contentDoc = ruleView().doc;
contentWindow = contentDoc.defaultView;
let links = contentDoc.querySelectorAll(".ruleview-rule-source");
return links[aIndex];
}
function finishUp()
{
gBrowser.removeCurrentTab();
contentWindow = doc = inspector = toolbox = win = null;
Services.prefs.clearUserPref(PREF);
finish();
}

View File

@ -3,6 +3,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/. */
const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleinspector/test/";
const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleinspector/test/";
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
@ -187,6 +190,53 @@ function getComputedPropertyValue(aName)
}
}
/**
* Polls a given function waiting for it to become true.
*
* @param object aOptions
* Options object with the following properties:
* - validatorFn
* A validator function that returns a boolean. This is called every few
* milliseconds to check if the result is true. When it is true, succesFn
* is called and polling stops. If validatorFn never returns true, then
* polling timeouts after several tries and a failure is recorded.
* - successFn
* A function called when the validator function returns true.
* - failureFn
* A function called if the validator function timeouts - fails to return
* true in the given time.
* - name
* Name of test. This is used to generate the success and failure
* messages.
* - timeout
* Timeout for validator function, in milliseconds. Default is 5000.
*/
function waitForSuccess(aOptions)
{
let start = Date.now();
let timeout = aOptions.timeout || 5000;
function wait(validatorFn, successFn, failureFn)
{
if ((Date.now() - start) > timeout) {
// Log the failure.
ok(false, "Timed out while waiting for: " + aOptions.name);
failureFn(aOptions);
return;
}
if (validatorFn(aOptions)) {
ok(true, aOptions.name);
successFn();
}
else {
setTimeout(function() wait(validatorFn, successFn, failureFn), 100);
}
}
wait(aOptions.validatorFn, aOptions.successFn, aOptions.failureFn);
}
registerCleanupFunction(tearDown);
waitForExplicitFinish();

View File

@ -0,0 +1,7 @@
div {
color: #ff0066; }
span {
background-color: #EEE; }
/*# sourceMappingURL=sourcemaps.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAGA,GAAI;EACF,KAAK,EAHU,OAAI;;AAMrB,IAAK;EACH,gBAAgB,EAAE,IAAI",
"sources": ["sourcemaps.scss"],
"names": [],
"file": "sourcemaps.css"
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title>testcase for testing CSS source maps</title>
<link rel="stylesheet" type="text/css" href="simple.css"/>
<link rel="stylesheet" type="text/css" href="sourcemaps.css"/>
</head>
<body>
<div>source maps <span>testcase</span></div>
</body>
</html>

View File

@ -0,0 +1,10 @@
$paulrougetpink: #f06;
div {
color: $paulrougetpink;
}
span {
background-color: #EEE;
}

View File

@ -41,6 +41,10 @@
- button that pretty prints the selected source. -->
<!ENTITY debuggerUI.sources.prettyPrint "Prettify Source">
<!-- LOCALIZATION NOTE (debuggerUI.sources.toggleBreakpoints): This is the tooltip for the
- button that toggles all breakpoints for all sources. -->
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
- checkbox that toggles pausing on exceptions. -->
<!ENTITY debuggerUI.pauseExceptions "Pause on exceptions">

View File

@ -20,6 +20,7 @@
<html:canvas anonid="progressRing" xbl:inherits="progress" class="circularprogressindicator-progressRing" width="40" height="40"></html:canvas>
</xul:stack>
</content>
<implementation>
<field name="_progressCanvas">
document.getAnonymousElementByAttribute(this, "anonid", "progressRing");
@ -29,7 +30,6 @@
<constructor>
<![CDATA[
this._progressCircleCtx = this._progressCanvas.getContext('2d');
this._img = new Image();
]]>
</constructor>
<method name="updateProgress">
@ -44,31 +44,35 @@
let startAngle = 1.5 * Math.PI;
let endAngle = startAngle + (2 * Math.PI * (percentComplete / 100));
let ctx = this._progressCircleCtx;
ctx.clearRect(0, 0,
this._progressCanvas.width, this._progressCanvas.height);
// Save the state, so we can undo the clipping
ctx.save();
ctx.beginPath();
let center = this._progressCanvas.width / 2;
ctx.arc(center, center, center, startAngle, endAngle, false);
ctx.lineTo(center, center);
ctx.closePath();
ctx.clip();
// Draw circle image.
if (this._img && this._img.src) {
ctx.drawImage(this._img, 0, 0);
} else {
this._img.onload = function() {
ctx.drawImage(this._img, 0, 0);
}.bind(this);
if (!this._img) {
this._img = new Image();
this._img.onload = () => {
this.updateProgress(this.getAttribute("progress"))
}
this._img.src = PROGRESS_RING_IMG;
}
else if (this._img.complete) {
let ctx = this._progressCircleCtx;
ctx.clearRect(0, 0,
this._progressCanvas.width, this._progressCanvas.height);
ctx.restore();
// Save the state, so we can undo the clipping
ctx.save();
ctx.beginPath();
let center = this._progressCanvas.width / 2;
ctx.arc(center, center, center, startAngle, endAngle, false);
ctx.lineTo(center, center);
ctx.closePath();
ctx.clip();
// Draw circle image.
ctx.drawImage(this._img, 0, 0);
ctx.restore();
} else {
// Image is still loading
}
return [startAngle, endAngle];
]]>
</body>
@ -76,6 +80,10 @@
<method name="reset">
<body>
<![CDATA[
if(this._img && !this._img.complete) {
// cancel any pending updateProgress
this._img.onload = () => {};
}
this._progressCircleCtx.clearRect(0, 0,
this._progressCanvas.width, this._progressCanvas.height);
this.removeAttribute("progress");

View File

@ -46,6 +46,7 @@ function setPortraitViewstate() {
ContentAreaObserver._updateViewState("portrait");
ContentAreaObserver._dispatchBrowserEvent("SizeChanged");
yield waitForMessage("Content:SetWindowSize:Complete", browser.messageManager);
// Make sure it renders the new mode properly
yield waitForMs(0);
@ -54,6 +55,7 @@ function setPortraitViewstate() {
function restoreViewstate() {
ContentAreaObserver._updateViewState("landscape");
ContentAreaObserver._dispatchBrowserEvent("SizeChanged");
yield waitForMessage("Content:SetWindowSize:Complete", Browser.selectedBrowser.messageManager);
ok(isLandscapeMode(), "restoreViewstate should restore landscape mode.");

View File

@ -27,18 +27,18 @@ function makeURI(aURL, aOriginCharset, aBaseURI) {
function View(aSet) {
this._set = aSet;
this._set.controller = this;
this._window = aSet.ownerDocument.defaultView;
this.onResize = () => this._adjustDOMforViewState();
this._window.addEventListener("resize", this.onResize);
this.viewStateObserver = {
observe: (aSubject, aTopic, aData) => this._adjustDOMforViewState(aData)
};
Services.obs.addObserver(this.viewStateObserver, "metro_viewstate_changed", false);
ColorUtils.init();
this._adjustDOMforViewState();
}
View.prototype = {
destruct: function () {
Services.obs.removeObserver(this.viewStateObserver, "metro_viewstate_changed");
this._window.removeEventListener("resize", this.onResize);
},
_adjustDOMforViewState: function _adjustDOMforViewState(aState) {

View File

@ -86,9 +86,9 @@ this.BrowserUITelemetry = {
},
_countableEvents: {},
_countMouseUpEvent: function(aCategory, aAction, aMouseUpEvent) {
_countMouseUpEvent: function(aCategory, aAction, aButton) {
const BUTTONS = ["left", "middle", "right"];
let buttonKey = BUTTONS[aMouseUpEvent.button];
let buttonKey = BUTTONS[aButton];
if (buttonKey) {
let countObject =
this._ensureObjectChain([aCategory, aAction, buttonKey], 0);
@ -134,7 +134,7 @@ this.BrowserUITelemetry = {
if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
// Base case - we clicked directly on one of our built-in items,
// and we can go ahead and register that click.
this._countMouseUpEvent("click-builtin-item", item.id, aEvent);
this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
}
},

View File

@ -2,17 +2,22 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
"use strict";
this.EXPORTED_SYMBOLS = ["UITour"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
"resource://gre/modules/PermissionsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
const UITOUR_PERMISSION = "uitour";
@ -23,20 +28,45 @@ this.UITour = {
originTabs: new WeakMap(),
pinnedTabs: new WeakMap(),
urlbarCapture: new WeakMap(),
appMenuOpenForAnnotation: new Set(),
highlightEffects: ["wobble", "zoom", "color"],
highlightEffects: ["random", "wobble", "zoom", "color"],
targets: new Map([
["backforward", "#back-button"],
["appmenu", "#PanelUI-menu-button"],
["home", "#home-button"],
["urlbar", "#urlbar"],
["bookmarks", "#bookmarks-menu-button"],
["search", "#searchbar"],
["searchprovider", function UITour_target_searchprovider(aDocument) {
let searchbar = aDocument.getElementById("searchbar");
return aDocument.getAnonymousElementByAttribute(searchbar,
"anonid",
"searchbar-engine-button");
["addons", {query: "#add-ons-button"}],
["appMenu", {query: "#PanelUI-menu-button"}],
["backForward", {
query: "#back-button",
widgetName: "urlbar-container",
}],
["bookmarks", {query: "#bookmarks-menu-button"}],
["customize", {
query: (aDocument) => {
let customizeButton = aDocument.getElementById("PanelUI-customize");
return aDocument.getAnonymousElementByAttribute(customizeButton,
"class",
"toolbarbutton-icon");
},
widgetName: "PanelUI-customize",
}],
["help", {query: "#PanelUI-help"}],
["home", {query: "#home-button"}],
["quit", {query: "#PanelUI-quit"}],
["search", {
query: "#searchbar",
widgetName: "search-container",
}],
["searchProvider", {
query: (aDocument) => {
let searchbar = aDocument.getElementById("searchbar");
return aDocument.getAnonymousElementByAttribute(searchbar,
"anonid",
"searchbar-engine-button");
},
widgetName: "search-container",
}],
["urlbar", {
query: "#urlbar",
widgetName: "urlbar-container",
}],
]),
@ -68,10 +98,18 @@ this.UITour = {
switch (action) {
case "showHighlight": {
let target = this.getTarget(window, data.target);
if (!target)
return false;
this.showHighlight(target);
let targetPromise = this.getTarget(window, data.target);
targetPromise.then(target => {
if (!target.node) {
Cu.reportError("UITour: Target could not be resolved: " + data.target);
return;
}
let effect = undefined;
if (this.highlightEffects.indexOf(data.effect) !== -1) {
effect = data.effect;
}
this.showHighlight(target, effect);
}).then(null, Cu.reportError);
break;
}
@ -81,10 +119,14 @@ this.UITour = {
}
case "showInfo": {
let target = this.getTarget(window, data.target, true);
if (!target)
return false;
this.showInfo(target, data.title, data.text);
let targetPromise = this.getTarget(window, data.target, true);
targetPromise.then(target => {
if (!target.node) {
Cu.reportError("UITour: Target could not be resolved: " + data.target);
return;
}
this.showInfo(target, data.title, data.text);
}).then(null, Cu.reportError);
break;
}
@ -224,6 +266,7 @@ this.UITour = {
if (!aWindowClosing) {
this.hideHighlight(aWindow);
this.hideInfo(aWindow);
aWindow.PanelUI.panel.removeAttribute("noautohide");
}
this.endUrlbarCapture(aWindow);
@ -273,20 +316,93 @@ this.UITour = {
},
getTarget: function(aWindow, aTargetName, aSticky = false) {
if (typeof aTargetName != "string" || !aTargetName)
return null;
let deferred = Promise.defer();
if (typeof aTargetName != "string" || !aTargetName) {
deferred.reject("Invalid target name specified");
return deferred.promise;
}
if (aTargetName == "pinnedtab")
return this.ensurePinnedTab(aWindow, aSticky);
if (aTargetName == "pinnedTab") {
deferred.resolve({node: this.ensurePinnedTab(aWindow, aSticky)});
return deferred.promise;
}
let targetQuery = this.targets.get(aTargetName);
if (!targetQuery)
return null;
let targetObject = this.targets.get(aTargetName);
if (!targetObject) {
deferred.reject("The specified target name is not in the allowed set");
return deferred.promise;
}
if (typeof targetQuery == "function")
return targetQuery(aWindow.document);
let targetQuery = targetObject.query;
aWindow.PanelUI.ensureReady().then(() => {
if (typeof targetQuery == "function") {
deferred.resolve({
node: targetQuery(aWindow.document),
widgetName: targetObject.widgetName,
});
return;
}
deferred.resolve({
node: aWindow.document.querySelector(targetQuery),
widgetName: targetObject.widgetName,
});
}).then(null, Cu.reportError);
return deferred.promise;
},
targetIsInAppMenu: function(aTarget) {
let placement = CustomizableUI.getPlacementOfWidget(aTarget.widgetName || aTarget.node.id);
if (placement && placement.area == CustomizableUI.AREA_PANEL) {
return true;
}
let targetElement = aTarget.node;
// Use the widget for filtering if it exists since the target may be the icon inside.
if (aTarget.widgetName) {
targetElement = aTarget.node.ownerDocument.getElementById(aTarget.widgetName);
}
// Handle the non-customizable buttons at the bottom of the menu which aren't proper widgets.
return targetElement.id.startsWith("PanelUI-")
&& targetElement.id != "PanelUI-menu-button";
},
/**
* Called before opening or after closing a highlight or info panel to see if
* we need to open or close the appMenu to see the annotation's anchor.
*/
_setAppMenuStateForAnnotation: function(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
// If the panel is in the desired state, we're done.
let panelIsOpen = aWindow.PanelUI.panel.state != "closed";
if (aShouldOpenForHighlight == panelIsOpen) {
if (aCallback)
aCallback();
return;
}
// Don't close the menu if it wasn't opened by us (e.g. via showmenu instead).
if (!aShouldOpenForHighlight && !this.appMenuOpenForAnnotation.has(aAnnotationType)) {
if (aCallback)
aCallback();
return;
}
if (aShouldOpenForHighlight) {
this.appMenuOpenForAnnotation.add(aAnnotationType);
} else {
this.appMenuOpenForAnnotation.delete(aAnnotationType);
}
// Actually show or hide the menu
if (this.appMenuOpenForAnnotation.size) {
this.showMenu(aWindow, "appMenu", aCallback);
} else {
this.hideMenu(aWindow, "appMenu");
if (aCallback)
aCallback();
}
return aWindow.document.querySelector(targetQuery);
},
previewTheme: function(aTheme) {
@ -330,25 +446,51 @@ this.UITour = {
aWindow.gBrowser.removeTab(tabInfo.tab);
},
showHighlight: function(aTarget) {
let highlighter = aTarget.ownerDocument.getElementById("UITourHighlight");
/**
* @param aTarget The element to highlight.
* @param aEffect (optional) The effect to use from UITour.highlightEffects or "none".
* @see UITour.highlightEffects
*/
showHighlight: function(aTarget, aEffect = "none") {
function showHighlightPanel(aTargetEl) {
let highlighter = aTargetEl.ownerDocument.getElementById("UITourHighlight");
let randomEffect = Math.floor(Math.random() * this.highlightEffects.length);
if (randomEffect == this.highlightEffects.length)
randomEffect--; // On the order of 1 in 2^62 chance of this happening.
highlighter.setAttribute("active", this.highlightEffects[randomEffect]);
let effect = aEffect;
if (effect == "random") {
// Exclude "random" from the randomly selected effects.
let randomEffect = 1 + Math.floor(Math.random() * (this.highlightEffects.length - 1));
if (randomEffect == this.highlightEffects.length)
randomEffect--; // On the order of 1 in 2^62 chance of this happening.
effect = this.highlightEffects[randomEffect];
}
highlighter.setAttribute("active", effect);
let targetRect = aTarget.getBoundingClientRect();
let targetRect = aTargetEl.getBoundingClientRect();
highlighter.style.height = targetRect.height + "px";
highlighter.style.width = targetRect.width + "px";
highlighter.style.height = targetRect.height + "px";
highlighter.style.width = targetRect.width + "px";
let highlighterRect = highlighter.getBoundingClientRect();
// Close a previous highlight so we can relocate the panel.
if (highlighter.parentElement.state == "open") {
highlighter.parentElement.hidePopup();
}
/* The "overlap" position anchors from the top-left but we want to centre highlights at their
minimum size. */
let highlightWindow = aTargetEl.ownerDocument.defaultView;
let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
let highlightStyle = highlightWindow.getComputedStyle(highlighter);
let offsetX = paddingTopPx
- (Math.max(0, parseFloat(highlightStyle.minWidth) - targetRect.width) / 2);
let offsetY = paddingLeftPx
- (Math.max(0, parseFloat(highlightStyle.minHeight) - targetRect.height) / 2);
highlighter.parentElement.openPopup(aTargetEl, "overlap", offsetX, offsetY);
}
let top = targetRect.top + (targetRect.height / 2) - (highlighterRect.height / 2);
highlighter.style.top = top + "px";
let left = targetRect.left + (targetRect.width / 2) - (highlighterRect.width / 2);
highlighter.style.left = left + "px";
this._setAppMenuStateForAnnotation(aTarget.node.ownerDocument.defaultView, "highlight",
this.targetIsInAppMenu(aTarget),
showHighlightPanel.bind(this, aTarget.node));
},
hideHighlight: function(aWindow) {
@ -357,45 +499,70 @@ this.UITour = {
this.removePinnedTab(aWindow);
let highlighter = aWindow.document.getElementById("UITourHighlight");
highlighter.parentElement.hidePopup();
highlighter.removeAttribute("active");
this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
},
showInfo: function(aAnchor, aTitle, aDescription) {
aAnchor.focus();
function showInfoPanel(aAnchorEl) {
aAnchorEl.focus();
let document = aAnchor.ownerDocument;
let tooltip = document.getElementById("UITourTooltip");
let tooltipTitle = document.getElementById("UITourTooltipTitle");
let tooltipDesc = document.getElementById("UITourTooltipDescription");
let document = aAnchorEl.ownerDocument;
let tooltip = document.getElementById("UITourTooltip");
let tooltipTitle = document.getElementById("UITourTooltipTitle");
let tooltipDesc = document.getElementById("UITourTooltipDescription");
tooltip.hidePopup();
tooltip.hidePopup();
tooltipTitle.textContent = aTitle;
tooltipDesc.textContent = aDescription;
tooltipTitle.textContent = aTitle;
tooltipDesc.textContent = aDescription;
let alignment = "bottomcenter topright";
let anchorRect = aAnchor.getBoundingClientRect();
let alignment = "bottomcenter topright";
tooltip.hidden = false;
tooltip.openPopup(aAnchor, alignment);
if (tooltip.state == "open") {
tooltip.hidePopup();
}
tooltip.openPopup(aAnchorEl, alignment);
}
this._setAppMenuStateForAnnotation(aAnchor.node.ownerDocument.defaultView, "info",
this.targetIsInAppMenu(aAnchor),
showInfoPanel.bind(this, aAnchor.node));
},
hideInfo: function(aWindow) {
let tooltip = aWindow.document.getElementById("UITourTooltip");
tooltip.hidePopup();
this._setAppMenuStateForAnnotation(aWindow, "info", false);
},
showMenu: function(aWindow, aMenuName) {
showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
function openMenuButton(aId) {
let menuBtn = aWindow.document.getElementById(aId);
if (menuBtn && menuBtn.boxObject)
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
if (!menuBtn || !menuBtn.boxObject) {
aOpenCallback();
return;
}
if (aOpenCallback)
menuBtn.addEventListener("popupshown", onPopupShown);
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
}
function onPopupShown(event) {
this.removeEventListener("popupshown", onPopupShown);
aOpenCallback(event);
}
if (aMenuName == "appmenu")
if (aMenuName == "appMenu") {
aWindow.PanelUI.panel.setAttribute("noautohide", "true");
if (aOpenCallback) {
aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
}
aWindow.PanelUI.show();
else if (aMenuName == "bookmarks")
} else if (aMenuName == "bookmarks") {
openMenuButton("bookmarks-menu-button");
}
},
hideMenu: function(aWindow, aMenuName) {
@ -405,10 +572,12 @@ this.UITour = {
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(false);
}
if (aMenuName == "appmenu")
if (aMenuName == "appMenu") {
aWindow.PanelUI.panel.removeAttribute("noautohide");
aWindow.PanelUI.hide();
else if (aMenuName == "bookmarks")
} else if (aMenuName == "bookmarks") {
closeMenuButton("bookmarks-menu-button");
}
},
startUrlbarCapture: function(aWindow, aExpectedText, aUrl) {

View File

@ -1,4 +1,6 @@
[DEFAULT]
support-files =
head.js
[browser_NetworkPrioritizer.js]
[browser_SignInToWebsite.js]

View File

@ -14,6 +14,8 @@ function is_hidden(element) {
return true;
if (style.visibility != "visible")
return true;
if (style.display == "-moz-popup")
return ["hiding","closed"].indexOf(element.state) != -1;
// Hiding a parent element will hide all its children
if (element.parentNode != element.ownerDocument)
@ -27,6 +29,25 @@ function is_element_visible(element, msg) {
ok(!is_hidden(element), msg);
}
function waitForElementToBeVisible(element, nextTest, msg) {
waitForCondition(() => !is_hidden(element),
() => {
ok(true, msg);
nextTest();
},
"Timeout waiting for visibility: " + msg);
}
function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
waitForCondition(() => popup.popupBoxObject.anchorNode == anchorNode,
() => {
ok(true, msg);
is_element_visible(popup);
nextTest();
},
"Timeout waiting for popup at anchor: " + msg);
}
function is_element_hidden(element, msg) {
isnot(element, null, "Element should not be null, when checking visibility");
ok(is_hidden(element), msg);
@ -74,11 +95,13 @@ function test() {
gBrowser.removeTab(gTestTab);
gTestTab = null;
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should be hidden after UITour tab is closed");
let highlight = document.getElementById("UITourHighlightContainer");
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
let popup = document.getElementById("UITourTooltip");
isnot(["hidding","closed"].indexOf(popup.state), -1, "Popup should be closed/hidding after UITour tab is closed");
let tooltip = document.getElementById("UITourTooltip");
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
@ -91,7 +114,7 @@ function test() {
return;
}
let test = tests.shift();
info("Starting " + test.name);
loadTestPage(function() {
test(done);
});
@ -102,22 +125,22 @@ function test() {
let tests = [
function test_untrusted_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a untrusted host");
gContentAPI.showMenu("bookmarks");
ise(bookmarksMenu.open, false, "Bookmark menu should not open on a untrusted host");
done();
}, "http://mochi.test:8888/");
},
function test_unsecure_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a unsecure host");
gContentAPI.showMenu("bookmarks");
ise(bookmarksMenu.open, false, "Bookmark menu should not open on a unsecure host");
done();
}, "http://example.com/");
@ -129,40 +152,134 @@ let tests = [
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_visible(highlight, "Highlight should be shown on a unsecure host when override pref is set");
waitForElementToBeVisible(highlight, done, "Highlight should be shown on a unsecure host when override pref is set");
Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
done();
}, "http://example.com/");
},
function test_disabled(done) {
Services.prefs.setBoolPref("browser.uitour.enabled", false);
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown when feature is disabled");
gContentAPI.showMenu("bookmarks");
ise(bookmarksMenu.open, false, "Bookmark menu should not open when feature is disabled");
Services.prefs.setBoolPref("browser.uitour.enabled", true);
done();
},
function test_highlight(done) {
function test_highlight_2() {
let highlight = document.getElementById("UITourHighlight");
gContentAPI.hideHighlight();
is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
gContentAPI.showHighlight("urlbar");
waitForElementToBeVisible(highlight, test_highlight_3, "Highlight should be shown after showHighlight()");
}
function test_highlight_3() {
let highlight = document.getElementById("UITourHighlight");
gContentAPI.showHighlight("backForward");
waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
}
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_visible(highlight, "Highlight should be shown after showHighlight()");
waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
},
function test_highlight_customize_auto_open_close(done) {
let highlight = document.getElementById("UITourHighlight");
gContentAPI.showHighlight("customize");
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
gContentAPI.hideHighlight();
is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
// Move the highlight outside which should close the app menu.
gContentAPI.showHighlight("appMenu");
waitForElementToBeVisible(highlight, function checkPanelIsClosed() {
isnot(PanelUI.panel.state, "open",
"Panel should have closed after the highlight moved elsewhere.");
done();
}, "Highlight should move to the appMenu button");
}, "Highlight should be shown after showHighlight() for fixed panel items");
},
function test_highlight_customize_manual_open_close(done) {
let highlight = document.getElementById("UITourHighlight");
// Manually open the app menu then show a highlight there. The menu should remain open.
gContentAPI.showMenu("appMenu");
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
gContentAPI.showHighlight("customize");
waitForElementToBeVisible(highlight, function checkPanelIsStillOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
// Move the highlight outside which shouldn't close the app menu since it was manually opened.
gContentAPI.showHighlight("appMenu");
waitForElementToBeVisible(highlight, function () {
isnot(PanelUI.panel.state, "closed",
"Panel should remain open since UITour didn't open it in the first place");
gContentAPI.hideMenu("appMenu");
done();
}, "Highlight should move to the appMenu button");
}, "Highlight should be shown after showHighlight() for fixed panel items");
},
function test_highlight_effect(done) {
function waitForHighlightWithEffect(highlightEl, effect, next, error) {
return waitForCondition(() => highlightEl.getAttribute("active") == effect,
next,
error);
}
function checkDefaultEffect() {
is(highlight.getAttribute("active"), "none", "The default should be no effect");
gContentAPI.showHighlight("urlbar", "none");
waitForHighlightWithEffect(highlight, "none", checkZoomEffect, "There should be no effect");
}
function checkZoomEffect() {
gContentAPI.showHighlight("urlbar", "zoom");
waitForHighlightWithEffect(highlight, "zoom", () => {
let style = window.getComputedStyle(highlight);
is(style.animationName, "uitour-zoom", "The animation-name should be uitour-zoom");
checkRandomEffect();
}, "There should be a zoom effect");
}
function checkRandomEffect() {
function waitForActiveHighlight(highlightEl, next, error) {
return waitForCondition(() => highlightEl.hasAttribute("active"),
next,
error);
}
gContentAPI.hideHighlight();
gContentAPI.showHighlight("urlbar", "random");
waitForActiveHighlight(highlight, () => {
ok(highlight.hasAttribute("active"), "The highlight should be active");
isnot(highlight.getAttribute("active"), "none", "A random effect other than none should have been chosen");
isnot(highlight.getAttribute("active"), "random", "The random effect shouldn't be 'random'");
isnot(UITour.highlightEffects.indexOf(highlight.getAttribute("active")), -1, "Check that a supported effect was randomly chosen");
done();
}, "There should be an active highlight with a random effect");
}
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_visible(highlight, "Highlight should be shown after showHighlight()");
gContentAPI.showHighlight("backforward");
is_element_visible(highlight, "Highlight should be shown after showHighlight()");
waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
},
function test_highlight_effect_unsupported(done) {
function checkUnsupportedEffect() {
is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
done();
}
done();
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
waitForElementToBeVisible(highlight, checkUnsupportedEffect, "Highlight should be shown after showHighlight()");
},
function test_info_1(done) {
let popup = document.getElementById("UITourTooltip");
@ -212,6 +329,54 @@ let tests = [
gContentAPI.showInfo("urlbar", "urlbar title", "urlbar text");
},
function test_info_customize_auto_open_close(done) {
let popup = document.getElementById("UITourTooltip");
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
UITour.getTarget(window, "customize").then((customizeTarget) => {
waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
// Move the info outside which should close the app menu.
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
UITour.getTarget(window, "appMenu").then((target) => {
waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
isnot(PanelUI.panel.state, "open",
"Panel should have closed after the info moved elsewhere.");
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
done();
}, "Info should move to the appMenu button");
});
}, "Info panel should be anchored to the customize button");
});
},
function test_info_customize_manual_open_close(done) {
let popup = document.getElementById("UITourTooltip");
// Manually open the app menu then show an info panel there. The menu should remain open.
gContentAPI.showMenu("appMenu");
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
UITour.getTarget(window, "customize").then((customizeTarget) => {
waitForPopupAtAnchor(popup, customizeTarget.node, function checkMenuIsStillOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should still be set");
// Move the info outside which shouldn't close the app menu since it was manually opened.
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
UITour.getTarget(window, "appMenu").then((target) => {
waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
isnot(PanelUI.panel.state, "closed",
"Menu should remain open since UITour didn't open it in the first place");
gContentAPI.hideMenu("appMenu");
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
done();
}, "Info should move to the appMenu button");
});
}, "Info should be shown after showInfo() for fixed menu panel items");
});
},
function test_pinnedTab(done) {
is(UITour.pinnedTabs.get(window), null, "Should not already have a pinned tab");

View File

@ -0,0 +1,24 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}

View File

@ -40,9 +40,10 @@ if (typeof Mozilla == 'undefined') {
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
Mozilla.UITour.showHighlight = function(target) {
Mozilla.UITour.showHighlight = function(target, effect) {
_sendEvent('showHighlight', {
target: target
target: target,
effect: effect
});
};

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

View File

@ -24,20 +24,28 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
#sources-toolbar > .devtools-toolbarbutton,
#sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
}
#pretty-print {
font-weight: bold;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
#toggle-breakpoints {
list-style-image: url(debugger-toggleBreakpoints.png);
}
#sources-toolbar .devtools-toolbarbutton:not([label]) {
-moz-image-region: rect(0px,16px,16px,0px);
}
#black-box[checked] {
#sources-toolbar .devtools-toolbarbutton:not([label])[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}

View File

@ -202,7 +202,6 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass.png (devtools/magnifying-glass.png)
skin/classic/browser/devtools/option-icon.png (devtools/option-icon.png)
skin/classic/browser/devtools/itemToggle.png (devtools/itemToggle.png)
skin/classic/browser/devtools/blackBoxMessageEye.png (devtools/blackBoxMessageEye.png)
skin/classic/browser/devtools/itemArrow-rtl.png (devtools/itemArrow-rtl.png)
skin/classic/browser/devtools/itemArrow-ltr.png (devtools/itemArrow-ltr.png)
skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
@ -219,6 +218,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

View File

@ -26,20 +26,28 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
#sources-toolbar > .devtools-toolbarbutton,
#sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
}
#pretty-print {
font-weight: bold;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
#toggle-breakpoints {
list-style-image: url(debugger-toggleBreakpoints.png);
}
#sources-toolbar .devtools-toolbarbutton:not([label]) {
-moz-image-region: rect(0px,16px,16px,0px);
}
#black-box[checked] {
#sources-toolbar .devtools-toolbarbutton:not([label])[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}

View File

@ -304,7 +304,6 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass.png (devtools/magnifying-glass.png)
skin/classic/browser/devtools/option-icon.png (devtools/option-icon.png)
skin/classic/browser/devtools/itemToggle.png (devtools/itemToggle.png)
skin/classic/browser/devtools/blackBoxMessageEye.png (devtools/blackBoxMessageEye.png)
skin/classic/browser/devtools/itemArrow-rtl.png (devtools/itemArrow-rtl.png)
skin/classic/browser/devtools/itemArrow-ltr.png (devtools/itemArrow-ltr.png)
skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
@ -321,6 +320,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)

View File

@ -6,7 +6,14 @@
/* UI Tour */
html|div#UITourHighlight {
#UITourHighlightContainer {
-moz-appearance: none;
background-color: transparent;
/* This is a buffer to compensate for the movement in the "wobble" effect */
padding: 4px;
}
#UITourHighlight {
background-image: radial-gradient(50% 100%, rgba(0,149,220,0.4) 50%, rgba(0,149,220,0.6) 100%);
border-radius: 40px;
border: 1px solid white;

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

View File

@ -24,20 +24,28 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
#sources-toolbar > .devtools-toolbarbutton,
#sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
}
#pretty-print {
font-weight: bold;
}
#black-box {
list-style-image: url(debugger-blackbox.png);
#toggle-breakpoints {
list-style-image: url(debugger-toggleBreakpoints.png);
}
#sources-toolbar .devtools-toolbarbutton:not([label]) {
-moz-image-region: rect(0px,16px,16px,0px);
}
#black-box[checked] {
#sources-toolbar .devtools-toolbarbutton:not([label])[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}

View File

@ -229,7 +229,6 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass.png (devtools/magnifying-glass.png)
skin/classic/browser/devtools/option-icon.png (devtools/option-icon.png)
skin/classic/browser/devtools/itemToggle.png (devtools/itemToggle.png)
skin/classic/browser/devtools/blackBoxMessageEye.png (devtools/blackBoxMessageEye.png)
skin/classic/browser/devtools/itemArrow-rtl.png (devtools/itemArrow-rtl.png)
skin/classic/browser/devtools/itemArrow-ltr.png (devtools/itemArrow-ltr.png)
skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
@ -246,6 +245,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
@ -534,7 +535,6 @@ browser.jar:
skin/classic/aero/browser/devtools/magnifying-glass.png (devtools/magnifying-glass.png)
skin/classic/aero/browser/devtools/option-icon.png (devtools/option-icon.png)
skin/classic/aero/browser/devtools/itemToggle.png (devtools/itemToggle.png)
skin/classic/aero/browser/devtools/blackBoxMessageEye.png (devtools/blackBoxMessageEye.png)
skin/classic/aero/browser/devtools/itemArrow-rtl.png (devtools/itemArrow-rtl.png)
skin/classic/aero/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
skin/classic/aero/browser/devtools/noise.png (devtools/noise.png)
@ -551,6 +551,8 @@ browser.jar:
skin/classic/aero/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/aero/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/aero/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/aero/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/aero/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

View File

@ -171,7 +171,7 @@ BluetoothOppManager::BluetoothOppManager() : mConnected(false)
, mRemoteMaxPacketLength(0)
, mLastCommand(0)
, mPacketLength(0)
, mPacketReceivedLength(0)
, mPutPacketReceivedLength(0)
, mBodySegmentLength(0)
, mAbortFlag(false)
, mNewFileFlag(false)
@ -447,8 +447,6 @@ BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
NS_ENSURE_TRUE(mConnected, false);
NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
MOZ_ASSERT(mPacketReceivedLength == 0);
mWaitingForConfirmationFlag = false;
// For the first packet of first file
@ -467,6 +465,7 @@ BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
}
ReplyToPut(mPutFinalFlag, success);
return true;
}
@ -475,7 +474,7 @@ BluetoothOppManager::AfterFirstPut()
{
mUpdateProgressCounter = 1;
mPutFinalFlag = false;
mPacketReceivedLength = 0;
mPutPacketReceivedLength = 0;
mSentFileLength = 0;
mWaitingToSendPutFinal = false;
mSuccessFlag = false;
@ -509,7 +508,7 @@ BluetoothOppManager::AfterOppDisconnected()
mConnected = false;
mLastCommand = 0;
mPacketReceivedLength = 0;
mPutPacketReceivedLength = 0;
mDsFile = nullptr;
// We can't reset mSuccessFlag here since this function may be called
@ -554,13 +553,14 @@ BluetoothOppManager::DeleteReceivedFile()
bool
BluetoothOppManager::CreateFile()
{
MOZ_ASSERT(mPacketReceivedLength == mPacketLength);
MOZ_ASSERT(mPutPacketReceivedLength == mPacketLength);
nsString path;
path.AssignLiteral(TARGET_SUBDIR);
path.Append(mFileName);
mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
mDsFile = DeviceStorageFile::CreateUnique(
path, nsIFile::NORMAL_FILE_TYPE, 0644);
NS_ENSURE_TRUE(mDsFile, false);
nsCOMPtr<nsIFile> f;
@ -736,7 +736,7 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
int frameHeaderLength = 0;
// See if this is the first part of each Put packet
if (mPacketReceivedLength == 0) {
if (mPutPacketReceivedLength == 0) {
// Section 3.3.3 "Put", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
frameHeaderLength = 3;
@ -746,8 +746,8 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
/**
* A PUT request from remote devices may be divided into multiple parts.
* In other words, one request may need to be received multiple times,
* so here we keep a variable mPacketLeftLength to indicate if current
* PUT request is done.
* so here we keep a variable mPutPacketReceivedLength to indicate if
* current PUT request is done.
*/
mReceivedDataBuffer = new uint8_t[mPacketLength];
mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal);
@ -757,7 +757,7 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
// Check length before memcpy to prevent from memory pollution
if (dataLength < 0 ||
mPacketReceivedLength + dataLength > mPacketLength) {
mPutPacketReceivedLength + dataLength > mPacketLength) {
BT_LOGR("Received packet size is unreasonable");
ReplyToPut(mPutFinalFlag, false);
@ -767,12 +767,12 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
return false;
}
memcpy(mReceivedDataBuffer.get() + mPacketReceivedLength,
memcpy(mReceivedDataBuffer.get() + mPutPacketReceivedLength,
&aMessage->mData[frameHeaderLength], dataLength);
mPacketReceivedLength += dataLength;
mPutPacketReceivedLength += dataLength;
return (mPacketReceivedLength == mPacketLength);
return (mPutPacketReceivedLength == mPacketLength);
}
void
@ -783,7 +783,7 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
uint8_t opCode;
int receivedLength = aMessage->mSize;
if (mPacketReceivedLength > 0) {
if (mPutPacketReceivedLength > 0) {
opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
} else {
opCode = aMessage->mData[0];
@ -838,11 +838,12 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
}
// A Put packet is received completely
ParseHeaders(mReceivedDataBuffer.get(), mPacketReceivedLength, &pktHeaders);
ParseHeaders(mReceivedDataBuffer.get(),
mPutPacketReceivedLength, &pktHeaders);
ExtractPacketHeaders(pktHeaders);
ValidateFileName();
mPacketReceivedLength = 0;
mPutPacketReceivedLength = 0;
// When we cancel the transfer, delete the file and notify completion
if (mAbortFlag) {
@ -1183,6 +1184,10 @@ BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
{
if (!mConnected) return;
// The received length can be reset here because this is where we reply to a
// complete put packet.
mPutPacketReceivedLength = 0;
// Section 3.3.2 "Disconnect", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
uint8_t req[255];

View File

@ -141,7 +141,7 @@ private:
int mLastCommand;
int mPacketLength;
int mPacketReceivedLength;
int mPutPacketReceivedLength;
int mBodySegmentLength;
int mUpdateProgressCounter;

View File

@ -87,6 +87,7 @@ class mozilla::dom::bluetooth::DroidSocketImpl
public:
DroidSocketImpl(BluetoothSocket* aConsumer, int aFd)
: mConsumer(aConsumer)
, mReadMsgForClientFd(false)
, mIOLoop(nullptr)
, mFd(aFd)
, mShuttingDownOnIOThread(false)
@ -173,6 +174,11 @@ public:
*/
RefPtr<BluetoothSocket> mConsumer;
/**
* If true, read message header to get client fd.
*/
bool mReadMsgForClientFd;
private:
/**
* libevent triggered functions that reads data from socket when available and
@ -446,7 +452,7 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
ssize_t ret;
if (!mConsumer->IsWaitingForClientFd()) {
if (!mReadMsgForClientFd) {
ret = read(aFd, incoming->mData, incoming->mSize);
} else {
ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
@ -643,16 +649,11 @@ BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData)
return true;
}
bool
BluetoothSocket::IsWaitingForClientFd()
{
return (mIsServer &&
mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH);
}
bool
BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
{
MOZ_ASSERT(NS_IsMainThread());
/**
* 2 socket info messages (20 bytes) to receive at the beginning:
* - 1st message: [channel:4]
@ -668,8 +669,10 @@ BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) {
// 1st message: [channel:4]
int32_t channel = ReadInt32(aMessage->mData, &offset);
BT_LOGR("channel %d", channel);
// If this is server socket, read header of next message for client fd
mImpl->mReadMsgForClientFd = mIsServer;
} else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) {
// 2nd message: [size:2][bd address:6][channel:4][connection status:4]
int16_t size = ReadInt16(aMessage->mData, &offset);
@ -686,6 +689,7 @@ BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
}
if (mIsServer) {
mImpl->mReadMsgForClientFd = false;
// Connect client fd on IO thread
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketConnectClientFdTask(mImpl));

View File

@ -66,7 +66,6 @@ public:
}
void CloseDroidSocket();
bool IsWaitingForClientFd();
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
private:

View File

@ -175,7 +175,7 @@ BluetoothOppManager::BluetoothOppManager() : mConnected(false)
, mRemoteMaxPacketLength(0)
, mLastCommand(0)
, mPacketLength(0)
, mPacketReceivedLength(0)
, mPutPacketReceivedLength(0)
, mBodySegmentLength(0)
, mAbortFlag(false)
, mNewFileFlag(false)
@ -463,8 +463,6 @@ BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
NS_ENSURE_TRUE(mConnected, false);
NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
MOZ_ASSERT(mPacketReceivedLength == 0);
mWaitingForConfirmationFlag = false;
// For the first packet of first file
@ -483,6 +481,7 @@ BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
}
ReplyToPut(mPutFinalFlag, success);
return true;
}
@ -491,7 +490,7 @@ BluetoothOppManager::AfterFirstPut()
{
mUpdateProgressCounter = 1;
mPutFinalFlag = false;
mPacketReceivedLength = 0;
mPutPacketReceivedLength = 0;
mSentFileLength = 0;
mWaitingToSendPutFinal = false;
mSuccessFlag = false;
@ -525,7 +524,7 @@ BluetoothOppManager::AfterOppDisconnected()
mConnected = false;
mLastCommand = 0;
mPacketReceivedLength = 0;
mPutPacketReceivedLength = 0;
mDsFile = nullptr;
// We can't reset mSuccessFlag here since this function may be called
@ -570,13 +569,14 @@ BluetoothOppManager::DeleteReceivedFile()
bool
BluetoothOppManager::CreateFile()
{
MOZ_ASSERT(mPacketReceivedLength == mPacketLength);
MOZ_ASSERT(mPutPacketReceivedLength == mPacketLength);
nsString path;
path.AssignLiteral(TARGET_SUBDIR);
path.Append(mFileName);
mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
mDsFile = DeviceStorageFile::CreateUnique(
path, nsIFile::NORMAL_FILE_TYPE, 0644);
NS_ENSURE_TRUE(mDsFile, false);
nsCOMPtr<nsIFile> f;
@ -752,7 +752,7 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
int frameHeaderLength = 0;
// See if this is the first part of each Put packet
if (mPacketReceivedLength == 0) {
if (mPutPacketReceivedLength == 0) {
// Section 3.3.3 "Put", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
frameHeaderLength = 3;
@ -762,8 +762,8 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
/**
* A PUT request from remote devices may be divided into multiple parts.
* In other words, one request may need to be received multiple times,
* so here we keep a variable mPacketLeftLength to indicate if current
* PUT request is done.
* so here we keep a variable mPutPacketReceivedLength to indicate if
* current PUT request is done.
*/
mReceivedDataBuffer = new uint8_t[mPacketLength];
mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal);
@ -773,7 +773,7 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
// Check length before memcpy to prevent from memory pollution
if (dataLength < 0 ||
mPacketReceivedLength + dataLength > mPacketLength) {
mPutPacketReceivedLength + dataLength > mPacketLength) {
BT_LOGR("Received packet size is unreasonable");
ReplyToPut(mPutFinalFlag, false);
@ -783,12 +783,12 @@ BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
return false;
}
memcpy(mReceivedDataBuffer.get() + mPacketReceivedLength,
memcpy(mReceivedDataBuffer.get() + mPutPacketReceivedLength,
&aMessage->mData[frameHeaderLength], dataLength);
mPacketReceivedLength += dataLength;
mPutPacketReceivedLength += dataLength;
return (mPacketReceivedLength == mPacketLength);
return (mPutPacketReceivedLength == mPacketLength);
}
void
@ -799,7 +799,7 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
uint8_t opCode;
int receivedLength = aMessage->mSize;
if (mPacketReceivedLength > 0) {
if (mPutPacketReceivedLength > 0) {
opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
} else {
opCode = aMessage->mData[0];
@ -854,12 +854,11 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
}
// A Put packet is received completely
ParseHeaders(mReceivedDataBuffer.get(), mPacketReceivedLength, &pktHeaders);
ParseHeaders(mReceivedDataBuffer.get(),
mPutPacketReceivedLength, &pktHeaders);
ExtractPacketHeaders(pktHeaders);
ValidateFileName();
mPacketReceivedLength = 0;
// When we cancel the transfer, delete the file and notify completion
if (mAbortFlag) {
ReplyToPut(mPutFinalFlag, false);
@ -1199,6 +1198,10 @@ BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
{
if (!mConnected) return;
// The received length can be reset here because this is where we reply to a
// complete put packet.
mPutPacketReceivedLength = 0;
// Section 3.3.2 "Disconnect", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
uint8_t req[255];

View File

@ -141,7 +141,7 @@ private:
int mLastCommand;
int mPacketLength;
int mPacketReceivedLength;
int mPutPacketReceivedLength;
int mBodySegmentLength;
int mUpdateProgressCounter;

View File

@ -899,10 +899,11 @@ void
ContentParent::OnChannelError()
{
nsRefPtr<ContentParent> content(this);
PContentParent::OnChannelError();
#ifdef MOZ_NUWA_PROCESS
// Handle app or Nuwa process exit before normal channel error handling.
PreallocatedProcessManager::MaybeForgetSpare(this);
#endif
PContentParent::OnChannelError();
}
void
@ -2724,7 +2725,8 @@ ContentParent::OnProcessNextEvent(nsIThreadInternal *thread,
/* void afterProcessNextEvent (in nsIThreadInternal thread, in unsigned long recursionDepth); */
NS_IMETHODIMP
ContentParent::AfterProcessNextEvent(nsIThreadInternal *thread,
uint32_t recursionDepth)
uint32_t recursionDepth,
bool eventWasProcessed)
{
return NS_OK;
}

View File

@ -1,2 +1,2 @@
[test_NuwaProcessCreation.html]
run-if = toolkit == 'gonk'
skip-if = true # re-enable when nuwa is enabled.

View File

@ -11,7 +11,7 @@ interface nsIDOMMozMobileNetworkInfo;
interface nsIDOMMozMobileCellInfo;
interface nsIDOMMozMobileCFInfo;
[scriptable, builtinclass, uuid(4c8331f9-45f3-479d-ac3f-acb60fcc0583)]
[scriptable, builtinclass, uuid(f55510a9-8dfc-44f5-90ab-355d1e721808)]
interface nsIDOMMozMobileConnection : nsIDOMEventTarget
{
const long ICC_SERVICE_CLASS_VOICE = (1 << 0);
@ -129,6 +129,40 @@ interface nsIDOMMozMobileConnection : nsIDOMEventTarget
*/
nsIDOMDOMRequest selectNetworkAutomatically();
/**
* Set preferred network type
*
* @param type
* DOMString indicates the desired preferred network type.
* Possible values: 'wcdma/gsm', 'gsm', 'wcdma', 'wcdma/gsm-auto',
'cdma/evdo', 'cdma', 'evdo', or
'wcdma/gsm/cdma/evdo'.
*
* If successful, the request's onsuccess will be called.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported',
* 'InvalidParameter', 'ModeNotSupported' or 'GenericFailure'
*
* TODO: param "type" should be a WebIDL enum when this interface is converted
* to WebIDL
*/
nsIDOMDOMRequest setPreferredNetworkType(in DOMString type);
/**
* Query current preferred network type
*
* If successful, the request's onsuccess will be called. And the request's
* result will be a string indicating the current preferred network type.
* The value will be either 'wcdma/gsm', 'gsm', 'wcdma', 'wcdma/gsm-auto',
* 'cdma/evdo', 'cdma', 'evdo', or 'wcdma/gsm/cdma/evdo'.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported',
* or 'GenericFailure'
*/
nsIDOMDOMRequest getPreferredNetworkType();
/**
* Set roaming preference
*

View File

@ -35,7 +35,7 @@ interface nsIMobileConnectionListener : nsISupports
* XPCOM component (in the content process) that provides the mobile
* network information.
*/
[scriptable, uuid(9a804dc4-6900-46af-8c38-3d0f424672b5)]
[scriptable, uuid(0e027520-dd87-461d-88a6-c3e46369c03c)]
interface nsIMobileConnectionProvider : nsISupports
{
/**
@ -62,6 +62,12 @@ interface nsIMobileConnectionProvider : nsISupports
nsIDOMDOMRequest selectNetworkAutomatically(in unsigned long clientId,
in nsIDOMWindow window);
nsIDOMDOMRequest setPreferredNetworkType(in unsigned long clientId,
in nsIDOMWindow window,
in DOMString type);
nsIDOMDOMRequest getPreferredNetworkType(in unsigned long clientId,
in nsIDOMWindow window);
nsIDOMDOMRequest setRoamingPreference(in unsigned long clientId,
in nsIDOMWindow window,
in DOMString mode);

View File

@ -271,6 +271,39 @@ MobileConnection::SelectNetworkAutomatically(nsIDOMDOMRequest** aRequest)
return mProvider->SelectNetworkAutomatically(mClientId, GetOwner(), aRequest);
}
NS_IMETHODIMP
MobileConnection::SetPreferredNetworkType(const nsAString& aType,
nsIDOMDOMRequest** aDomRequest)
{
*aDomRequest = nullptr;
if (!CheckPermission("mobileconnection")) {
return NS_OK;
}
if (!mProvider) {
return NS_ERROR_FAILURE;
}
return mProvider->SetPreferredNetworkType(mClientId, GetOwner(), aType, aDomRequest);
}
NS_IMETHODIMP
MobileConnection::GetPreferredNetworkType(nsIDOMDOMRequest** aDomRequest)
{
*aDomRequest = nullptr;
if (!CheckPermission("mobileconnection")) {
return NS_OK;
}
if (!mProvider) {
return NS_ERROR_FAILURE;
}
return mProvider->GetPreferredNetworkType(mClientId, GetOwner(), aDomRequest);
}
NS_IMETHODIMP
MobileConnection::SetRoamingPreference(const nsAString& aMode, nsIDOMDOMRequest** aDomRequest)
{

View File

@ -8,6 +8,7 @@ disabled = Bug 808783
[test_mobile_voice_state.js]
[test_mobile_operator_names.js]
[test_mobile_preferred_network_type.js]
[test_mobile_preferred_network_type_by_setting.js]
disabled = Bug 808783
[test_mobile_data_connection.js]
[test_mobile_data_location.js]

View File

@ -1,65 +1,141 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
const KEY = "ril.radio.preferredNetworkType";
SpecialPowers.addPermission("mobileconnection", true, document);
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document);
let settings = window.navigator.mozSettings;
let connection = navigator.mozMobileConnections[0];
ok(connection instanceof MozMobileConnection,
"connection is instanceof " + connection.constructor);
function test_revert_previous_setting_on_invalid_value() {
log("Testing reverting to previous setting on invalid value received");
let getLock = settings.createLock();
let getReq = getLock.get(KEY);
getReq.addEventListener("success", function onGetSuccess() {
let originalValue = getReq.result[KEY] || "wcdma/gsm";
function setPreferredNetworkType(type, callback) {
log("setPreferredNetworkType: " + type);
let setDone = false;
settings.addObserver(KEY, function observer(setting) {
// Mark if the invalid value has been set in db and wait.
if (setting.settingValue == obj[KEY]) {
setDone = true;
return;
}
let request = connection.setPreferredNetworkType(type);
ok(request instanceof DOMRequest,
"request instanceof " + request.constructor);
// Skip any change before marking but keep it as original value.
if (!setDone) {
originalValue = setting.settingValue;
return;
}
request.onsuccess = function onsuccess() {
ok(true, "request success");
callback();
}
request.onerror = function onerror() {
ok(false, request.error);
callback();
}
}
settings.removeObserver(KEY, observer);
is(setting.settingValue, originalValue, "Settings reverted");
window.setTimeout(cleanUp, 0);
});
function getPreferredNetworkType(callback) {
log("getPreferredNetworkType");
let obj = {};
obj[KEY] = "AnInvalidValue";
let setLock = settings.createLock();
setLock.set(obj);
setLock.addEventListener("error", function onSetError() {
ok(false, "cannot set '" + KEY + "'");
let request = connection.getPreferredNetworkType();
ok(request instanceof DOMRequest,
"request instanceof " + request.constructor);
request.onsuccess = function onsuccess() {
ok(true, "request success");
log("getPreferredNetworkType: " + request.result);
callback(request.result);
}
request.onerror = function onerror() {
ok(false, request.error);
callback();
}
}
function failToSetPreferredNetworkType(type, expectedError, callback) {
log("failToSetPreferredNetworkType: " + type + ", expected error: "
+ expectedError);
let request = connection.setPreferredNetworkType(type);
ok(request instanceof DOMRequest,
"request instanceof " + request.constructor);
request.onsuccess = function onsuccess() {
ok(false, "request should not succeed");
callback();
}
request.onerror = function onerror() {
ok(true, "request error");
is(request.error.name, expectedError);
callback();
}
}
function setAndVerifyNetworkType(type) {
setPreferredNetworkType(type, function() {
getPreferredNetworkType(function(result) {
is(result, type);
testPreferredNetworkTypes();
});
});
getReq.addEventListener("error", function onGetError() {
ok(false, "cannot get default value of '" + KEY + "'");
}
function testPreferredNetworkTypes() {
let networkType = supportedTypes.shift();
if (!networkType) {
runNextTest();
return;
}
setAndVerifyNetworkType(networkType);
}
function failToSetAndVerifyNetworkType(type, expectedError, previousType) {
failToSetPreferredNetworkType(type, expectedError, function() {
getPreferredNetworkType(function(result) {
// should return the previous selected type.
is(result, previousType);
testInvalidNetworkTypes();
});
});
}
function testInvalidNetworkTypes() {
let networkType = invalidTypes.shift();
if (!networkType) {
runNextTest();
return;
}
failToSetAndVerifyNetworkType(networkType, "InvalidParameter",
"wcdma/gsm");
}
let supportedTypes = [
'gsm',
'wcdma',
'wcdma/gsm-auto',
'cdma/evdo',
'evdo',
'cdma',
'wcdma/gsm/cdma/evdo',
'wcdma/gsm' // restore to default
];
let invalidTypes = [
' ',
'AnInvalidType'
];
let tests = [
testPreferredNetworkTypes,
testInvalidNetworkTypes
];
function runNextTest() {
let test = tests.shift();
if (!test) {
cleanUp();
return;
}
test();
}
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
SpecialPowers.removePermission("settings-write", document);
SpecialPowers.removePermission("settings-read", document);
finish();
}
waitFor(test_revert_previous_setting_on_invalid_value, function () {
return navigator.mozMobileConnections[0].voice.connected;
});
runNextTest();

View File

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
const KEY = "ril.radio.preferredNetworkType";
SpecialPowers.addPermission("mobileconnection", true, document);
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document);
let settings = window.navigator.mozSettings;
function test_revert_previous_setting_on_invalid_value() {
log("Testing reverting to previous setting on invalid value received");
let getLock = settings.createLock();
let getReq = getLock.get(KEY);
getReq.addEventListener("success", function onGetSuccess() {
let originalValue = getReq.result[KEY] || "wcdma/gsm";
let setDone = false;
settings.addObserver(KEY, function observer(setting) {
// Mark if the invalid value has been set in db and wait.
if (setting.settingValue == obj[KEY]) {
setDone = true;
return;
}
// Skip any change before marking but keep it as original value.
if (!setDone) {
originalValue = setting.settingValue;
return;
}
settings.removeObserver(KEY, observer);
is(setting.settingValue, originalValue, "Settings reverted");
window.setTimeout(cleanUp, 0);
});
let obj = {};
obj[KEY] = "AnInvalidValue";
let setLock = settings.createLock();
setLock.set(obj);
setLock.addEventListener("error", function onSetError() {
ok(false, "cannot set '" + KEY + "'");
});
});
getReq.addEventListener("error", function onGetError() {
ok(false, "cannot get default value of '" + KEY + "'");
});
}
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
SpecialPowers.removePermission("settings-write", document);
SpecialPowers.removePermission("settings-read", document);
finish();
}
waitFor(test_revert_previous_setting_on_invalid_value, function () {
return navigator.mozMobileConnections[0].voice.connected;
});

View File

@ -71,6 +71,8 @@ const RIL_IPC_MSG_NAMES = [
"RIL:NetworkSelectionModeChanged",
"RIL:SelectNetwork",
"RIL:SelectNetworkAuto",
"RIL:SetPreferredNetworkType",
"RIL:GetPreferredNetworkType",
"RIL:EmergencyCbModeChanged",
"RIL:VoicemailNotification",
"RIL:VoicemailInfoChanged",
@ -769,6 +771,43 @@ RILContentHelper.prototype = {
return request;
},
setPreferredNetworkType: function setPreferredNetworkType(clientId, window, type) {
if (window == null) {
throw Components.Exception("Can't get window object",
Cr.NS_ERROR_UNEXPECTED);
}
let request = Services.DOMRequest.createRequest(window);
let requestId = this.getRequestId(request);
cpmm.sendAsyncMessage("RIL:SetPreferredNetworkType", {
clientId: clientId,
data: {
requestId: requestId,
type: type
}
});
return request;
},
getPreferredNetworkType: function getPreferredNetworkType(clientId, window) {
if (window == null) {
throw Components.Exception("Can't get window object",
Cr.NS_ERROR_UNEXPECTED);
}
let request = Services.DOMRequest.createRequest(window);
let requestId = this.getRequestId(request);
cpmm.sendAsyncMessage("RIL:GetPreferredNetworkType", {
clientId: clientId,
data: {
requestId: requestId
}
});
return request;
},
setRoamingPreference: function setRoamingPreference(clientId, window, mode) {
if (window == null) {
throw Components.Exception("Can't get window object",
@ -1663,6 +1702,12 @@ RILContentHelper.prototype = {
this.handleSelectNetwork(clientId, data,
RIL.GECKO_NETWORK_SELECTION_AUTOMATIC);
break;
case "RIL:SetPreferredNetworkType":
this.handleSimpleRequest(data.requestId, data.errorMsg, null);
break;
case "RIL:GetPreferredNetworkType":
this.handleSimpleRequest(data.requestId, data.errorMsg, data.type);
break;
case "RIL:VoicemailNotification":
this.handleVoicemailNotification(clientId, data);
break;

View File

@ -92,6 +92,8 @@ const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [
"RIL:GetAvailableNetworks",
"RIL:SelectNetwork",
"RIL:SelectNetworkAuto",
"RIL:SetPreferredNetworkType",
"RIL:GetPreferredNetworkType",
"RIL:SendMMI",
"RIL:CancelMMI",
"RIL:RegisterMobileConnectionMsg",
@ -1053,6 +1055,12 @@ RadioInterface.prototype = {
case "RIL:SelectNetworkAuto":
this.workerMessenger.sendWithIPCMessage(msg, "selectNetworkAuto");
break;
case "RIL:SetPreferredNetworkType":
this.setPreferredNetworkType(msg.target, msg.json.data);
break;
case "RIL:GetPreferredNetworkType":
this.getPreferredNetworkType(msg.target, msg.json.data);
break;
case "RIL:GetCardLockState":
this.workerMessenger.sendWithIPCMessage(msg, "iccGetCardLockState",
"RIL:CardLockResult");
@ -1487,7 +1495,58 @@ RadioInterface.prototype = {
},
_preferredNetworkType: null,
setPreferredNetworkType: function setPreferredNetworkType(value) {
getPreferredNetworkType: function getPreferredNetworkType(target, message) {
this.workerMessenger.send("getPreferredNetworkType", message, (function(response) {
if (response.success) {
this._preferredNetworkType = response.networkType;
response.type = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[this._preferredNetworkType];
if (DEBUG) {
this.debug("_preferredNetworkType is now " +
RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[this._preferredNetworkType]);
}
}
target.sendAsyncMessage("RIL:GetPreferredNetworkType", {
clientId: this.clientId,
data: response
});
return false;
}).bind(this));
},
setPreferredNetworkType: function setPreferredNetworkType(target, message) {
if (DEBUG) this.debug("setPreferredNetworkType: " + JSON.stringify(message));
let networkType = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(message.type);
if (networkType < 0) {
message.errorMsg = RIL.GECKO_ERROR_INVALID_PARAMETER;
target.sendAsyncMessage("RIL:SetPreferredNetworkType", {
clientId: this.clientId,
data: message
});
return false;
}
message.networkType = networkType;
this.workerMessenger.send("setPreferredNetworkType", message, (function(response) {
if (response.success) {
this._preferredNetworkType = response.networkType;
if (DEBUG) {
this.debug("_preferredNetworkType is now " +
RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[this._preferredNetworkType]);
}
}
target.sendAsyncMessage("RIL:SetPreferredNetworkType", {
clientId: this.clientId,
data: response
});
return false;
}).bind(this));
},
// TODO: Bug 946589 - B2G RIL: follow-up to bug 944225 - remove
// 'ril.radio.preferredNetworkType' setting handler
setPreferredNetworkTypeBySetting: function setPreferredNetworkTypeBySetting(value) {
let networkType = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(value);
if (networkType < 0) {
networkType = (this._preferredNetworkType != null)
@ -2498,9 +2557,11 @@ RadioInterface.prototype = {
// nsISettingsServiceCallback
handle: function handle(aName, aResult) {
switch(aName) {
// TODO: Bug 946589 - B2G RIL: follow-up to bug 944225 - remove
// 'ril.radio.preferredNetworkType' setting handler
case "ril.radio.preferredNetworkType":
if (DEBUG) this.debug("'ril.radio.preferredNetworkType' is now " + aResult);
this.setPreferredNetworkType(aResult);
this.setPreferredNetworkTypeBySetting(aResult);
break;
case "ril.data.enabled":
if (DEBUG) this.debug("'ril.data.enabled' is now " + aResult);

View File

@ -1112,8 +1112,8 @@ let RIL = {
/**
* Get the preferred network type.
*/
getPreferredNetworkType: function getPreferredNetworkType() {
Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE);
getPreferredNetworkType: function getPreferredNetworkType(options) {
Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
},
/**
@ -5886,13 +5886,11 @@ RIL[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK
if (responseLen) {
this.preferredNetworkType = networkType = Buf.readInt32();
}
options.networkType = networkType;
}
this.sendChromeMessage({
rilMessageType: "getPreferredNetworkType",
networkType: networkType,
success: options.rilRequestError == ERROR_SUCCESS
});
options.success = (options.rilRequestError == ERROR_SUCCESS);
this.sendChromeMessage(options);
};
RIL[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;
RIL[REQUEST_SET_LOCATION_UPDATES] = null;

View File

@ -454,6 +454,7 @@ var interfaceNamesInGlobalScope =
"SettingsLock",
"SettingsManager",
{name: "ShadowRoot", pref: "dom.webcomponents.enabled"},
{name: "SharedWorker", pref: "dom.workers.sharedWorkers.enabled"},
"SimpleGestureEvent",
{name: "SimpleTest", xbl: false},
"SmartCardEvent",

View File

@ -30,9 +30,9 @@
let testGenerator = (function() {
SimpleTest.waitForExplicitFinish();
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
ok(!("WorkerMessagePort" in window),
"No WorkerMessagePort without pref set");
if (!SpecialPowers.getBoolPref(swPref)) {
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
}
SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, sendToGenerator);
yield undefined;

View File

@ -22,9 +22,9 @@
let testGenerator = (function() {
SimpleTest.waitForExplicitFinish();
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
ok(!("WorkerMessagePort" in window),
"No WorkerMessagePort without pref set");
if (!SpecialPowers.getBoolPref(swPref)) {
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
}
SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, sendToGenerator);
yield undefined;

View File

@ -27,7 +27,9 @@
const errorLine = 86;
const errorColumn = 0;
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
if (!SpecialPowers.getBoolPref(swPref)) {
ok(!("SharedWorker" in window), "No SharedWorker without pref set");
}
SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, function() {
var worker = new SharedWorker(filename);

View File

@ -118,6 +118,10 @@ MessageLoop::MessageLoop(Type type)
run_depth_base_ = 2;
return;
}
if (type_ == TYPE_MOZILLA_NONMAINTHREAD) {
pump_ = new mozilla::ipc::MessagePumpForNonMainThreads();
return;
}
#if defined(OS_WIN)
// TODO(rvargas): Get rid of the OS guards.

View File

@ -203,12 +203,17 @@ public:
// This type of ML is used in Mozilla parent processes which initialize
// XPCOM and use the gecko event loop.
//
// TYPE_MOZILLA_NONMAINTHREAD
// This type of ML is used in Mozilla parent processes which initialize
// XPCOM and use the nsThread event loop.
//
enum Type {
TYPE_DEFAULT,
TYPE_UI,
TYPE_IO,
TYPE_MOZILLA_CHILD,
TYPE_MOZILLA_UI
TYPE_MOZILLA_UI,
TYPE_MOZILLA_NONMAINTHREAD
};
// Normally, it is not necessary to instantiate a MessageLoop. Instead, it

View File

@ -4,16 +4,24 @@
#include "MessagePump.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/scoped_nsautorelease_pool.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsTimerImpl.h"
#include "nsXULAppAPI.h"
#include "prthread.h"
#include "base/logging.h"
#include "base/scoped_nsautorelease_pool.h"
#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#endif
@ -22,42 +30,39 @@
#include "ipc/Nuwa.h"
#endif
using mozilla::ipc::DoWorkRunnable;
using mozilla::ipc::MessagePump;
using mozilla::ipc::MessagePumpForChildProcess;
using base::TimeTicks;
using namespace mozilla::ipc;
NS_IMPL_ISUPPORTS2(DoWorkRunnable, nsIRunnable, nsITimerCallback)
NS_DEFINE_NAMED_CID(NS_TIMER_CID);
NS_IMETHODIMP
DoWorkRunnable::Run()
static mozilla::DebugOnly<MessagePump::Delegate*> gFirstDelegate;
namespace mozilla {
namespace ipc {
class DoWorkRunnable MOZ_FINAL : public nsIRunnable,
public nsITimerCallback
{
MessageLoop* loop = MessageLoop::current();
NS_ASSERTION(loop, "Shouldn't be null!");
if (loop) {
bool nestableTasksAllowed = loop->NestableTasksAllowed();
// MessageLoop::RunTask() disallows nesting, but our Frankenventloop
// will always dispatch DoWork() below from what looks to
// MessageLoop like a nested context. So we unconditionally allow
// nesting here.
loop->SetNestableTasksAllowed(true);
loop->DoWork();
loop->SetNestableTasksAllowed(nestableTasksAllowed);
public:
DoWorkRunnable(MessagePump* aPump)
: mPump(aPump)
{
MOZ_ASSERT(aPump);
}
return NS_OK;
}
NS_IMETHODIMP
DoWorkRunnable::Notify(nsITimer* aTimer)
{
MessageLoop* loop = MessageLoop::current();
NS_ASSERTION(loop, "Shouldn't be null!");
if (loop) {
mPump->DoDelayedWork(loop);
}
return NS_OK;
}
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIRUNNABLE
NS_DECL_NSITIMERCALLBACK
private:
~DoWorkRunnable()
{ }
MessagePump* mPump;
};
} /* namespace ipc */
} /* namespace mozilla */
MessagePump::MessagePump()
: mThread(nullptr)
@ -65,17 +70,22 @@ MessagePump::MessagePump()
mDoWorkEvent = new DoWorkRunnable(this);
}
MessagePump::~MessagePump()
{
}
void
MessagePump::Run(MessagePump::Delegate* aDelegate)
{
NS_ASSERTION(keep_running_, "Quit must have been called outside of Run!");
NS_ASSERTION(NS_IsMainThread(), "Called Run on the wrong thread!");
MOZ_ASSERT(keep_running_);
MOZ_ASSERT(NS_IsMainThread(),
"Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
mThread = NS_GetCurrentThread();
NS_ASSERTION(mThread, "This should never be null!");
MOZ_ASSERT(mThread);
mDelayedWorkTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
NS_ASSERTION(mDelayedWorkTimer, "Failed to create timer!");
mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
MOZ_ASSERT(mDelayedWorkTimer);
base::ScopedNSAutoreleasePool autoReleasePool;
@ -165,7 +175,7 @@ MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime)
#endif
if (!mDelayedWorkTimer) {
mDelayedWorkTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
if (!mDelayedWorkTimer) {
// Called before XPCOM has started up? We can't do this correctly.
NS_WARNING("Delayed task might not run!");
@ -199,34 +209,56 @@ MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate)
}
}
#ifdef DEBUG
namespace {
MessagePump::Delegate* gFirstDelegate = nullptr;
NS_IMPL_ISUPPORTS2(DoWorkRunnable, nsIRunnable, nsITimerCallback)
NS_IMETHODIMP
DoWorkRunnable::Run()
{
MessageLoop* loop = MessageLoop::current();
MOZ_ASSERT(loop);
bool nestableTasksAllowed = loop->NestableTasksAllowed();
// MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
// always dispatch DoWork() below from what looks to MessageLoop like a nested
// context. So we unconditionally allow nesting here.
loop->SetNestableTasksAllowed(true);
loop->DoWork();
loop->SetNestableTasksAllowed(nestableTasksAllowed);
return NS_OK;
}
NS_IMETHODIMP
DoWorkRunnable::Notify(nsITimer* aTimer)
{
MessageLoop* loop = MessageLoop::current();
MOZ_ASSERT(loop);
mPump->DoDelayedWork(loop);
return NS_OK;
}
#endif
void
MessagePumpForChildProcess::Run(MessagePump::Delegate* aDelegate)
MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate)
{
if (mFirstRun) {
#ifdef DEBUG
NS_ASSERTION(aDelegate && gFirstDelegate == nullptr, "Huh?!");
MOZ_ASSERT(aDelegate && !gFirstDelegate);
gFirstDelegate = aDelegate;
#endif
mFirstRun = false;
if (NS_FAILED(XRE_RunAppShell())) {
NS_WARNING("Failed to run app shell?!");
}
#ifdef DEBUG
NS_ASSERTION(aDelegate && aDelegate == gFirstDelegate, "Huh?!");
MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
gFirstDelegate = nullptr;
#endif
return;
}
#ifdef DEBUG
NS_ASSERTION(aDelegate && aDelegate == gFirstDelegate, "Huh?!");
#endif
MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
// We can get to this point in startup with Tasks in our loop's
// incoming_queue_ or pending_queue_, but without a matching
@ -245,7 +277,60 @@ MessagePumpForChildProcess::Run(MessagePump::Delegate* aDelegate)
loop->SetNestableTasksAllowed(nestableTasksAllowed);
// Really run.
mozilla::ipc::MessagePump::Run(aDelegate);
}
void
MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate)
{
MOZ_ASSERT(keep_running_);
MOZ_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!");
mThread = NS_GetCurrentThread();
MOZ_ASSERT(mThread);
mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
MOZ_ASSERT(mDelayedWorkTimer);
if (NS_FAILED(mDelayedWorkTimer->SetTarget(mThread))) {
MOZ_CRASH("Failed to set timer target!");
}
for (;;) {
bool didWork = NS_ProcessNextEvent(mThread, false) ? true : false;
if (!keep_running_) {
break;
}
didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);
if (didWork && delayed_work_time_.is_null()) {
mDelayedWorkTimer->Cancel();
}
if (!keep_running_) {
break;
}
if (didWork) {
continue;
}
didWork = aDelegate->DoIdleWork();
if (!keep_running_) {
break;
}
if (didWork) {
continue;
}
// This will either sleep or process an event.
NS_ProcessNextEvent(mThread, true);
}
mDelayedWorkTimer->Cancel();
keep_running_ = true;
}

View File

@ -5,72 +5,90 @@
#ifndef __IPC_GLUE_MESSAGEPUMP_H__
#define __IPC_GLUE_MESSAGEPUMP_H__
#include "base/basictypes.h"
#include "base/message_pump_default.h"
#include "base/time.h"
#include "mozilla/Attributes.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "mozilla/Attributes.h"
class nsIThread;
class nsITimer;
namespace mozilla {
namespace ipc {
class MessagePump;
class DoWorkRunnable MOZ_FINAL : public nsIRunnable,
public nsITimerCallback
{
public:
DoWorkRunnable(MessagePump* aPump)
: mPump(aPump) { }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIRUNNABLE
NS_DECL_NSITIMERCALLBACK
private:
MessagePump* mPump;
};
class DoWorkRunnable;
class MessagePump : public base::MessagePumpDefault
{
friend class DoWorkRunnable;
public:
MessagePump();
virtual void Run(base::MessagePump::Delegate* aDelegate);
virtual void ScheduleWork();
virtual void ScheduleWorkForNestedLoop();
virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time);
// From base::MessagePump.
virtual void
Run(base::MessagePump::Delegate* aDelegate) MOZ_OVERRIDE;
void DoDelayedWork(base::MessagePump::Delegate* aDelegate);
// From base::MessagePump.
virtual void
ScheduleWork() MOZ_OVERRIDE;
// From base::MessagePump.
virtual void
ScheduleWorkForNestedLoop() MOZ_OVERRIDE;
// From base::MessagePump.
virtual void
ScheduleDelayedWork(const base::TimeTicks& aDelayedWorkTime) MOZ_OVERRIDE;
protected:
virtual ~MessagePump();
private:
nsRefPtr<DoWorkRunnable> mDoWorkEvent;
nsCOMPtr<nsITimer> mDelayedWorkTimer;
// Only called by DoWorkRunnable.
void DoDelayedWork(base::MessagePump::Delegate* aDelegate);
// Weak!
protected:
// mDelayedWorkTimer and mThread are set in Run() by this class or its
// subclasses.
nsCOMPtr<nsITimer> mDelayedWorkTimer;
nsIThread* mThread;
private:
// Only accessed by this class.
nsRefPtr<DoWorkRunnable> mDoWorkEvent;
};
class MessagePumpForChildProcess : public MessagePump
class MessagePumpForChildProcess MOZ_FINAL: public MessagePump
{
public:
MessagePumpForChildProcess()
: mFirstRun(true)
{ }
virtual void Run(base::MessagePump::Delegate* aDelegate);
virtual void Run(base::MessagePump::Delegate* aDelegate) MOZ_OVERRIDE;
private:
~MessagePumpForChildProcess()
{ }
bool mFirstRun;
};
class MessagePumpForNonMainThreads MOZ_FINAL : public MessagePump
{
public:
MessagePumpForNonMainThreads()
{ }
virtual void Run(base::MessagePump::Delegate* aDelegate) MOZ_OVERRIDE;
private:
~MessagePumpForNonMainThreads()
{ }
};
} /* namespace ipc */
} /* namespace mozilla */

View File

@ -103,6 +103,11 @@ IPDL_SOURCES = [
'URIParams.ipdlh',
]
LOCAL_INCLUDES += [
'/xpcom/threads',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View File

@ -4128,7 +4128,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
msgvar = self.msgvar
tdvar = ExprVar('td')
pidvar = ExprVar('pid')
pvar = ExprVar('p')
pvar = ExprVar('protocolid')
iffail = StmtIf(ExprNot(ExprCall(
ExprVar('mozilla::ipc::UnpackChannelOpened'),
args=[ _backstagePass(),
@ -4180,7 +4180,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
StmtBreak()
])
label = _messageStartName(actor.ptype)
if actor.side is 'child':
if actor.side == 'child':
label += 'Child'
return CaseLabel(label), case

View File

@ -3,6 +3,8 @@ include protocol PTestActorPunningPunned;
include protocol PTestActorPunningSub;
include "mozilla/_ipdltest/IPDLUnitTestUtils.h";
using struct mozilla::_ipdltest::Bad from "mozilla/_ipdltest/IPDLUnitTestUtils.h";
namespace mozilla {
namespace _ipdltest {

Some files were not shown because too many files have changed in this diff Show More