Merge m-c to inbound.
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "2e4d09abb604dab914f1f29001012d872b57ef9e",
|
||||
"revision": "336e9464c144268a8902bbb2d9026be3ff2b327f",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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"
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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">
|
||||
|
@ -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]
|
||||
|
@ -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") + ".");
|
||||
}
|
||||
}
|
@ -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") + ".");
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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;
|
||||
|
7
browser/devtools/styleeditor/test/sourcemaps.css
Normal file
@ -0,0 +1,7 @@
|
||||
div {
|
||||
color: #ff0066; }
|
||||
|
||||
span {
|
||||
background-color: #EEE; }
|
||||
|
||||
/*# sourceMappingURL=sourcemaps.css.map */
|
7
browser/devtools/styleeditor/test/sourcemaps.css.map
Normal 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"
|
||||
}
|
11
browser/devtools/styleeditor/test/sourcemaps.html
Normal 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>
|
10
browser/devtools/styleeditor/test/sourcemaps.scss
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
$paulrougetpink: #f06;
|
||||
|
||||
div {
|
||||
color: $paulrougetpink;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: #EEE;
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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}">
|
||||
|
@ -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"
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
|
7
browser/devtools/styleinspector/test/sourcemaps.css
Normal file
@ -0,0 +1,7 @@
|
||||
div {
|
||||
color: #ff0066; }
|
||||
|
||||
span {
|
||||
background-color: #EEE; }
|
||||
|
||||
/*# sourceMappingURL=sourcemaps.css.map */
|
7
browser/devtools/styleinspector/test/sourcemaps.css.map
Normal 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"
|
||||
}
|
11
browser/devtools/styleinspector/test/sourcemaps.html
Normal 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>
|
10
browser/devtools/styleinspector/test/sourcemaps.scss
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
$paulrougetpink: #f06;
|
||||
|
||||
div {
|
||||
color: $paulrougetpink;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: #EEE;
|
||||
}
|
@ -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">
|
||||
|
@ -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");
|
||||
|
@ -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.");
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[browser_NetworkPrioritizer.js]
|
||||
[browser_SignInToWebsite.js]
|
||||
|
@ -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");
|
||||
|
||||
|
24
browser/modules/test/head.js
Normal 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(); };
|
||||
}
|
@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
BIN
browser/themes/linux/devtools/debugger-toggleBreakpoints.png
Normal file
After Width: | Height: | Size: 886 B |
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
BIN
browser/themes/osx/devtools/debugger-toggleBreakpoints.png
Normal file
After Width: | Height: | Size: 886 B |
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
BIN
browser/themes/windows/devtools/debugger-toggleBreakpoints.png
Normal file
After Width: | Height: | Size: 886 B |
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
|
@ -141,7 +141,7 @@ private:
|
||||
int mLastCommand;
|
||||
|
||||
int mPacketLength;
|
||||
int mPacketReceivedLength;
|
||||
int mPutPacketReceivedLength;
|
||||
int mBodySegmentLength;
|
||||
int mUpdateProgressCounter;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -66,7 +66,6 @@ public:
|
||||
}
|
||||
|
||||
void CloseDroidSocket();
|
||||
bool IsWaitingForClientFd();
|
||||
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
|
||||
|
||||
private:
|
||||
|
@ -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];
|
||||
|
@ -141,7 +141,7 @@ private:
|
||||
int mLastCommand;
|
||||
|
||||
int mPacketLength;
|
||||
int mPacketReceivedLength;
|
||||
int mPutPacketReceivedLength;
|
||||
int mBodySegmentLength;
|
||||
int mUpdateProgressCounter;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
[test_NuwaProcessCreation.html]
|
||||
run-if = toolkit == 'gonk'
|
||||
skip-if = true # re-enable when nuwa is enabled.
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -103,6 +103,11 @@ IPDL_SOURCES = [
|
||||
'URIParams.ipdlh',
|
||||
]
|
||||
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/xpcom/threads',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|