Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-01-07 12:20:27 +01:00
Родитель 00b9637b05 c3c9e7c565
Коммит 99af1f8707
144 изменённых файлов: 2622 добавлений и 1692 удалений

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

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1216817 - Run |mach artifact| automatically when --disable-compile-environment is set
Bug 1209344 - Remove debug button from about:addons. r=mossop

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "81c021654c657f6995e5e70ef02ee067d4bfedbd",
"git_revision": "676237f80cf72182500356fabc49365d3471c0e6",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "e651f5b571034436b7bc8eb8099033dff5623a20",
"revision": "8e1054115b6145d46f0f2a8fa1b7ef8434b19f95",
"repo_path": "integration/gaia-central"
}

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="81c021654c657f6995e5e70ef02ee067d4bfedbd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="676237f80cf72182500356fabc49365d3471c0e6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

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

@ -311,10 +311,6 @@ body[narrow] #restorePreviousSession {
content: url("chrome://browser/content/abouthome/history.png");
}
#apps::before {
content: url("chrome://browser/content/abouthome/apps.png");
}
#addons::before {
content: url("chrome://browser/content/abouthome/addons.png");
}
@ -415,10 +411,6 @@ body[narrow] #restorePreviousSession::before {
content: url("chrome://browser/content/abouthome/history@2x.png");
}
#apps::before {
content: url("chrome://browser/content/abouthome/apps@2x.png");
}
#addons::before {
content: url("chrome://browser/content/abouthome/addons@2x.png");
}

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

@ -63,7 +63,6 @@
<button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
<button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
<button class="launchButton" id="history">&abouthome.historyButton.label;</button>
<button class="launchButton" id="apps">&abouthome.appsButton2.label;</button>
<button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
<button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
#ifdef XP_WIN

Двоичные данные
browser/base/content/abouthome/apps.png

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

До

Ширина:  |  Высота:  |  Размер: 961 B

Двоичные данные
browser/base/content/abouthome/apps@2x.png

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

До

Ширина:  |  Высота:  |  Размер: 2.5 KiB

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

@ -492,10 +492,6 @@
accesskey="&addons.accesskey;"
key="key_openAddons"
command="Tools:Addons"/>
<menuitem id="menu_openApps"
label="&webapps.label;"
accesskey="&webapps.accesskey;"
oncommand="BrowserOpenApps();"/>
<!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
<menuitem id="sync-setup"

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

@ -1815,3 +1815,44 @@ var BookmarkingUI = {
Ci.nsINavBookmarkObserver
])
};
var AutoShowBookmarksToolbar = {
init() {
PlacesUtils.addLazyBookmarkObserver(this, false);
},
uninit() {
PlacesUtils.removeLazyBookmarkObserver(this);
},
onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
aGuid, aParentGuid) {
this._autoshow(aParentGuid);
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemRemoved() {},
onItemChanged() {},
onItemVisited() {},
onItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex, aItemType,
aGuid, aOldParentGuid, aNewParentGuid) {
this._autoshow(aNewParentGuid);
},
_autoshow(aParentGuid) {
if (aParentGuid != PlacesUtils.bookmarks.toolbarGuid)
return;
let toolbar = document.getElementById("PersonalToolbar");
if (!toolbar.collapsed)
return;
let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
let area = placement && placement.area;
if (area != CustomizableUI.AREA_BOOKMARKS)
return;
setToolbarVisibility(toolbar, true);
}
};

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

@ -1188,6 +1188,7 @@ var gBrowserInit = {
gBrowser.tabContainer.updateVisibility();
BookmarkingUI.init();
AutoShowBookmarksToolbar.init();
gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
@ -1486,6 +1487,7 @@ var gBrowserInit = {
IndexedDBPromptHelper.uninit();
LightweightThemeListener.uninit();
PanelUI.uninit();
AutoShowBookmarksToolbar.uninit();
}
// Final window teardown, do this last.

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

@ -195,10 +195,6 @@ var AboutHomeListener = {
sendAsyncMessage("AboutHome:History");
break;
case "apps":
sendAsyncMessage("AboutHome:Apps");
break;
case "addons":
sendAsyncMessage("AboutHome:Addons");
break;

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

@ -3101,6 +3101,9 @@
// Map from tabs to STATE_* (below).
tabState: new Map(),
// True if we're in the midst of switching tabs.
switchInProgress: false,
// Keep an exact list of content processes (tabParent) in which
// we're actively suppressing the display port. This gives a robust
// way to make sure we don't forget to un-suppress.
@ -3410,7 +3413,7 @@
let tab = this.tabbrowser.getTabForBrowser(browser);
this.setTabState(tab, this.STATE_LOADED);
this.finishTabSwitch();
this.maybeFinishTabSwitch();
if (this.loadingTab === tab) {
clearTimeout(this.loadTimer);
@ -3424,7 +3427,7 @@
// around.
onPaint: function() {
this.maybeVisibleTabs.clear();
this.finishTabSwitch();
this.maybeFinishTabSwitch();
},
// Called when we're done clearing the layers for a tab.
@ -3523,10 +3526,18 @@
TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
this.addMarker("AsyncTabSwitch:Start");
this.switchInProgress = true;
},
finishTabSwitch: function () {
if (this.requestedTab && this.getTabState(this.requestedTab) == this.STATE_LOADED) {
/**
* Something has occurred that might mean that we've completed
* the tab switch (layers are ready, paints are done, spinners
* are hidden). This checks to make sure all conditions are
* satisfied, and then records the tab switch as finished.
*/
maybeFinishTabSwitch: function () {
if (this.switchInProgress && this.requestedTab &&
this.getTabState(this.requestedTab) == this.STATE_LOADED) {
// After this point the tab has switched from the content thread's point of view.
// The changes will be visible after the next refresh driver tick + composite.
let event = new CustomEvent("TabSwitched", {
@ -3540,6 +3551,7 @@
this.log("DEBUG: tab switch time = " + time);
this.addMarker("AsyncTabSwitch:Finish");
}
this.switchInProgress = false;
}
},
@ -3556,7 +3568,7 @@
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
this.addMarker("AsyncTabSwitch:SpinnerHidden");
// we do not get a onPaint after displaying the spinner
this.finishTabSwitch();
this.maybeFinishTabSwitch();
},
addMarker: function(marker) {

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

@ -27,7 +27,6 @@ browser.jar:
content/browser/abouthome/downloads.png (content/abouthome/downloads.png)
content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png)
content/browser/abouthome/history.png (content/abouthome/history.png)
content/browser/abouthome/apps.png (content/abouthome/apps.png)
content/browser/abouthome/addons.png (content/abouthome/addons.png)
content/browser/abouthome/sync.png (content/abouthome/sync.png)
content/browser/abouthome/settings.png (content/abouthome/settings.png)
@ -39,7 +38,6 @@ browser.jar:
content/browser/abouthome/downloads@2x.png (content/abouthome/downloads@2x.png)
content/browser/abouthome/bookmarks@2x.png (content/abouthome/bookmarks@2x.png)
content/browser/abouthome/history@2x.png (content/abouthome/history@2x.png)
content/browser/abouthome/apps@2x.png (content/abouthome/apps@2x.png)
content/browser/abouthome/addons@2x.png (content/abouthome/addons@2x.png)
content/browser/abouthome/sync@2x.png (content/abouthome/sync@2x.png)
content/browser/abouthome/settings@2x.png (content/abouthome/settings@2x.png)

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

@ -138,7 +138,7 @@ var gSearchPane = {
gSearchPane.onRestoreDefaults();
break;
case "removeEngineButton":
gSearchPane.remove();
Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
break;
}
break;
@ -180,7 +180,11 @@ var gSearchPane = {
gEngineView.invalidate();
break;
case "engine-removed":
gSearchPane.remove(aEngine);
break;
case "engine-current":
gSearchPane.buildDefaultEngineDropDown();
break;
case "engine-default":
// Not relevant
break;
@ -228,9 +232,8 @@ var gSearchPane = {
document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
},
remove: function() {
gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
let index = gEngineView.selectedIndex;
remove: function(aEngine) {
let index = gEngineView._engineStore.removeEngine(aEngine);
gEngineView.rowCountChanged(index, -1);
gEngineView.invalidate();
gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
@ -369,16 +372,18 @@ EngineStore.prototype = {
},
removeEngine: function ES_removeEngine(aEngine) {
var index = this._getIndexForEngine(aEngine);
let engineName = aEngine.name;
let index = this._engines.findIndex(element => element.name == engineName);
if (index == -1)
throw new Error("invalid engine?");
this._engines.splice(index, 1);
Services.search.removeEngine(aEngine.originalEngine);
if (this._defaultEngines.some(this._isSameEngine, aEngine))
if (this._defaultEngines.some(this._isSameEngine, this._engines[index]))
gSearchPane.showRestoreDefaults(true);
gSearchPane.buildDefaultEngineDropDown();
return index;
},
restoreDefaultEngines: function ES_restoreDefaultEngines() {

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

@ -31,9 +31,5 @@
preferences/options item on about:home on Linux and OS X -->
<!ENTITY abouthome.preferencesButtonUnix.label "Preferences">
<!ENTITY abouthome.addonsButton.label "Add-ons">
<!-- LOCALIZATION NOTE (abouthome.appsButton2.label): This string should be consistent with
the Apps menu item in the Tools menu (webapps.label in browser.dtd) and the Apps toolbar button in
Firefox's customization palette (web-apps-button.label in customizableWidgets.properties) -->
<!ENTITY abouthome.appsButton2.label "Apps">
<!ENTITY abouthome.downloadsButton.label "Downloads">
<!ENTITY abouthome.syncButton.label "&syncBrand.shortName.label;">

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

@ -250,8 +250,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY addons.label "Add-ons">
<!ENTITY addons.accesskey "A">
<!ENTITY addons.commandkey "A">
<!ENTITY webapps.label "Apps">
<!ENTITY webapps.accesskey "p">
<!ENTITY webDeveloperMenu.label "Web Developer">
<!ENTITY webDeveloperMenu.accesskey "W">

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

@ -95,7 +95,6 @@ var AboutHome = {
"AboutHome:Downloads",
"AboutHome:Bookmarks",
"AboutHome:History",
"AboutHome:Apps",
"AboutHome:Addons",
"AboutHome:Sync",
"AboutHome:Settings",
@ -134,10 +133,6 @@ var AboutHome = {
window.PlacesCommandHook.showPlacesOrganizer("History");
break;
case "AboutHome:Apps":
window.BrowserOpenApps();
break;
case "AboutHome:Addons":
window.BrowserOpenAddonsMgr();
break;

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

@ -20,6 +20,23 @@ function update(state = initialState, action, emitChange) {
emitChange('source', action.source);
return mergeIn(state, ['sources', action.source.actor], action.source);
case constants.LOAD_SOURCES:
if (action.status === "done") {
const sources = action.value;
if (!sources) {
return state;
}
const sourcesByActor = {};
sources.forEach(source => {
if (!state.sources[source.actor]) {
emitChange('source', source);
}
sourcesByActor[source.actor] = source;
});
return mergeIn(state, ['sources'], state.sources.merge(sourcesByActor))
}
break;
case constants.SELECT_SOURCE:
emitChange('source-selected', action.source);
return state.merge({

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

@ -224,7 +224,7 @@ var DebuggerController = {
return;
}
DebuggerView.initialize();
yield DebuggerView.initialize();
this._startup = true;
}),
@ -259,26 +259,23 @@ var DebuggerController = {
* A promise that is resolved when the debugger finishes connecting.
*/
connect: Task.async(function*() {
if (this._connected) {
return;
}
let target = this._target;
let { client, form: { chromeDebugger, actor } } = target;
let { client } = target;
target.on("close", this._onTabDetached);
target.on("navigate", this._onNavigate);
target.on("will-navigate", this._onWillNavigate);
this.client = client;
this.activeThread = this._toolbox.threadClient;
if (target.isAddon) {
yield this._startAddonDebugging(actor);
} else if (!target.isTabActor) {
// Some actors like AddonActor or RootActor for chrome debugging
// do not support attach/detach and can be used directly
yield this._startChromeDebugging(chromeDebugger);
} else {
yield this._startDebuggingTab();
}
// Disable asm.js so that we can set breakpoints and other things
// on asm.js scripts
yield this.reconfigureThread({ observeAsmJS: true });
yield this.connectThread();
// We need to call this to sync the state of the resume
// button in the toolbar with the state of the thread.
this.ThreadState._update();
this._hideUnsupportedFeatures();
}),
@ -307,7 +304,20 @@ var DebuggerController = {
// emit individual `newSource` notifications, which trigger
// separate actions, so this won't do anything other than force
// the server to traverse sources.
this.dispatch(actions.loadSources());
this.dispatch(actions.loadSources()).then(() => {
// If the engine is already paused, update the UI to represent the
// paused state
if (this.activeThread) {
const pausedPacket = this.activeThread.getLastPausePacket();
DebuggerView.Toolbar.toggleResumeButtonState(
this.activeThread.state,
!!pausedPacket
);
if (pausedPacket) {
this.StackFrames._onPaused("paused", pausedPacket);
}
}
});
},
/**
@ -379,117 +389,37 @@ var DebuggerController = {
}
},
/**
* Sets up a debugging session.
*
* @return object
* A promise resolved once the client attaches to the active thread.
*/
_startDebuggingTab: function() {
let deferred = promise.defer();
let threadOptions = {
useSourceMaps: Prefs.sourceMapsEnabled,
autoBlackBox: Prefs.autoBlackBox
};
this._target.activeTab.attachThread(threadOptions, (aResponse, aThreadClient) => {
if (!aThreadClient) {
deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
return;
}
this.activeThread = aThreadClient;
this.connectThread();
if (aThreadClient.paused) {
aThreadClient.resume(res => {
this._ensureResumptionOrder(res)
});
}
deferred.resolve();
});
return deferred.promise;
},
/**
* Sets up an addon debugging session.
*
* @param object aAddonActor
* The actor for the addon that is being debugged.
* @return object
* A promise resolved once the client attaches to the active thread.
*/
_startAddonDebugging: function(aAddonActor) {
let deferred = promise.defer();
this.client.attachAddon(aAddonActor, aResponse => {
this._startChromeDebugging(aResponse.threadActor).then(deferred.resolve);
});
return deferred.promise;
},
/**
* Sets up a chrome debugging session.
*
* @param object aChromeDebugger
* The remote protocol grip of the chrome debugger.
* @return object
* A promise resolved once the client attaches to the active thread.
*/
_startChromeDebugging: function(aChromeDebugger) {
let deferred = promise.defer();
let threadOptions = {
useSourceMaps: Prefs.sourceMapsEnabled,
autoBlackBox: Prefs.autoBlackBox
};
this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
if (!aThreadClient) {
deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
return;
}
this.activeThread = aThreadClient;
this.connectThread();
if (aThreadClient.paused) {
aThreadClient.resume(this._ensureResumptionOrder);
}
deferred.resolve();
}, threadOptions);
return deferred.promise;
},
/**
* Detach and reattach to the thread actor with useSourceMaps true, blow
* away old sources and get them again.
*/
reconfigureThread: function({ useSourceMaps, autoBlackBox }) {
this.activeThread.reconfigure({
useSourceMaps: useSourceMaps,
autoBlackBox: autoBlackBox
}, aResponse => {
if (aResponse.error) {
let msg = "Couldn't reconfigure thread: " + aResponse.message;
Cu.reportError(msg);
dumpn(msg);
return;
}
reconfigureThread: function(opts) {
const deferred = promise.defer();
this.activeThread.reconfigure(
opts,
aResponse => {
if (aResponse.error) {
deferred.reject(aResponse.error);
return;
}
// Reset the view and fetch all the sources again.
DebuggerView.handleTabNavigation();
this.dispatch(actions.unload());
this.dispatch(actions.loadSources());
if (('useSourceMaps' in opts) || ('autoBlackBox' in opts)) {
// Reset the view and fetch all the sources again.
DebuggerView.handleTabNavigation();
this.dispatch(actions.unload());
this.dispatch(actions.loadSources());
// Update the stack frame list.
if (this.activeThread.paused) {
this.activeThread._clearFrames();
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
// Update the stack frame list.
if (this.activeThread.paused) {
this.activeThread._clearFrames();
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
}
}
deferred.resolve();
}
});
);
return deferred.promise;
},
waitForSourcesLoaded: function() {
@ -606,8 +536,6 @@ ThreadState.prototype = {
dumpn("ThreadState is connecting...");
this.activeThread.addListener("paused", this._update);
this.activeThread.addListener("resumed", this._update);
this.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions,
Prefs.ignoreCaughtExceptions);
},
/**
@ -637,13 +565,12 @@ ThreadState.prototype = {
* Update the UI after a thread state change.
*/
_update: function(aEvent, aPacket) {
// Ignore "interrupted" events, to avoid UI flicker. These are generated
// by the slow script dialog and internal events such as setting
// breakpoints. Pressing the resume button does need to be shown, though.
if (aEvent == "paused") {
if (aPacket.why.type == "interrupted" &&
!this.interruptedByResumeButton) {
return;
this.interruptedByResumeButton) {
// Interrupt requests suppressed by default, but if this is an
// explicit interrupt by the pause button we want to emit it.
gTarget.emit("thread-paused", aPacket);
} else if (aPacket.why.type == "breakpointConditionThrown" && aPacket.why.message) {
let where = aPacket.frame.where;
let aLocation = {
@ -663,10 +590,6 @@ ThreadState.prototype = {
this.activeThread.state,
aPacket ? aPacket.frame : false
);
if (gTarget && (aEvent == "paused" || aEvent == "resumed")) {
gTarget.emit("thread-" + aEvent);
}
}
};

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

@ -60,10 +60,11 @@ var DebuggerView = {
* A promise that is resolved when the view finishes initializing.
*/
initialize: function() {
if (this._hasStartup) {
return;
if (this._startup) {
return this._startup;
}
this._hasStartup = true;
const deferred = promise.defer();
this._startup = deferred.promise;
this._initializePanes();
this.Toolbar.initialize();
@ -78,7 +79,8 @@ var DebuggerView = {
this.EventListeners.initialize();
this.GlobalSearch.initialize();
this._initializeVariablesView();
this._initializeEditor();
this._initializeEditor(deferred.resolve);
this._editorSource = {};
document.title = L10N.getStr("DebuggerWindowTitle");
@ -108,6 +110,8 @@ var DebuggerView = {
}
}
}, this);
return deferred.promise;
},
/**
@ -279,7 +283,7 @@ var DebuggerView = {
* @param function aCallback
* Called after the editor finishes initializing.
*/
_initializeEditor: function() {
_initializeEditor: function(callback) {
dumpn("Initializing the DebuggerView editor");
let extraKeys = {};
@ -311,6 +315,7 @@ var DebuggerView = {
this.editor.appendTo(document.getElementById("editor")).then(() => {
this.editor.extend(DebuggerEditor);
this._loadingText = L10N.getStr("loadingText");
callback();
});
this.editor.on("gutterClick", (ev, line, button) => {

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

@ -22,9 +22,6 @@ function DebuggerPanel(iframeWindow, toolbox) {
this._controller._toolbox = this._toolbox;
this.handleHostChanged = this.handleHostChanged.bind(this);
this.highlightWhenPaused = this.highlightWhenPaused.bind(this);
this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
EventEmitter.decorate(this);
}
@ -55,8 +52,6 @@ DebuggerPanel.prototype = {
.then(() => this._controller.connect())
.then(() => {
this._toolbox.on("host-changed", this.handleHostChanged);
this.target.on("thread-paused", this.highlightWhenPaused);
this.target.on("thread-resumed", this.unhighlightWhenResumed);
// Add keys from this document's keyset to the toolbox, so they
// can work when the split console is focused.
let keysToClone = ["resumeKey", "resumeKey2", "stepOverKey",
@ -87,9 +82,6 @@ DebuggerPanel.prototype = {
return this._destroyer;
}
this.target.off("thread-paused", this.highlightWhenPaused);
this.target.off("thread-resumed", this.unhighlightWhenResumed);
if (!this.target.isRemote) {
this.target.tab.removeEventListener('TabSelect', this);
}
@ -125,18 +117,6 @@ DebuggerPanel.prototype = {
this._view.handleHostChanged(this._toolbox.hostType);
},
highlightWhenPaused: function() {
this._toolbox.highlightTool("jsdebugger");
// Also raise the toolbox window if it is undocked or select the
// corresponding tab when toolbox is docked.
this._toolbox.raise();
},
unhighlightWhenResumed: function() {
this._toolbox.unhighlightTool("jsdebugger");
},
// nsIDOMEventListener API
handleEvent: function(aEvent) {

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

@ -165,6 +165,7 @@ skip-if = e10s || os == "mac" || e10s # Bug 895426
skip-if = e10s # TODO
[browser_dbg_break-on-dom-event-03.js]
skip-if = e10s # TODO
[browser_dbg_break-unselected.js]
[browser_dbg_breakpoints-actual-location.js]
[browser_dbg_breakpoints-actual-location2.js]
[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]

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

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test breaking in code and jumping to the debugger before
* the debugger UI has been initialized.
*/
const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
function test() {
Task.spawn(function*() {
const tab = yield getTab(TAB_URL);
const target = TargetFactory.forTab(tab);
const toolbox = yield gDevTools.showToolbox(target, "webconsole");
is(toolbox.currentToolId, "webconsole", "Console is the current panel");
toolbox.target.on('thread-paused', Task.async(function*() {
// Wait for the toolbox to handle the event and switch tools
yield waitForTick();
is(toolbox.currentToolId, "jsdebugger", "Debugger is the current panel");
// Wait until it's actually fully loaded
yield toolbox.loadTool("jsdebugger");
const panel = toolbox.getCurrentPanel();
const queries = panel.panelWin.require('./content/queries');
const getState = panel.panelWin.DebuggerController.getState;
is(panel.panelWin.gThreadClient.state, "paused",
"Thread is still paused");
yield waitForSourceAndCaret(panel, "debugger-statement.html", 16);
is(queries.getSelectedSource(getState()).url, TAB_URL,
"Selected source is the current tab url");
is(queries.getSelectedSourceOpts(getState()).line, 16,
"Line 16 is highlighted in the editor");
resumeDebuggerThenCloseAndFinish(panel);
}));
callInTab(tab, "runDebuggerStatement");
});
};

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

@ -18,7 +18,13 @@ function test() {
let [tab,, panel] = yield initDebugger(EXAMPLE_URL + TAB_URL);
let win = panel.panelWin;
yield waitForSourceShown(panel, SCRIPT_URL);
let Sources = win.DebuggerView.Sources;
yield waitForDebuggerEvents(panel, win.EVENTS.SOURCE_SHOWN);
if (Sources.selectedItem.attachment.source.url.indexOf(SCRIPT_URL) === -1) {
Sources.selectedValue = getSourceActor(win.DebuggerView.Sources, EXAMPLE_URL + SCRIPT_URL)
}
yield panel.addBreakpoint({
actor: getSourceActor(win.DebuggerView.Sources, EXAMPLE_URL + SCRIPT_URL),
line: 6

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

@ -93,14 +93,8 @@ function testPause() {
}).then(testResume);
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
// Evaluate a script to fully pause the debugger
once(gDebugger.gClient, "willInterrupt").then(() => {
evalInTab(gTab, "1+1;");
});
evalInTab(gTab, "debugger;");
}
function testResume() {

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

@ -24,8 +24,8 @@ function test() {
function testPause() {
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
ok(gTarget.isThreadPaused,
"target.isThreadPaused has been updated to true.");
ok(gDebugger.gThreadClient.paused,
"threadClient.paused has been updated to true.");
gToolbox.once("inspector-selected").then(inspector => {
inspector.once("inspector-updated").then(testNotificationIsUp1);
@ -77,8 +77,8 @@ function testNotificationIsUp2() {
function testResume() {
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
ok(!gTarget.isThreadPaused,
"target.isThreadPaused has been updated to false.");
ok(!gDebugger.gThreadClient.paused,
"threadClient.paused has been updated to false.");
let notificationBox = gToolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");

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

@ -36,7 +36,7 @@ add_task(function* () {
is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
"Correct set of tools supported by worker");
yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
yield toolbox.destroy();
terminateWorkerInTab(tab, WORKER_URL);
yield waitForWorkerClose(workerClient);
yield close(client);

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

@ -526,13 +526,8 @@ function initDebugger(aTarget, aWindow) {
let debuggerPanel = aToolbox.getCurrentPanel();
let panelWin = debuggerPanel.panelWin;
// Wait for the initial resume...
panelWin.gClient.addOneTimeListener("resumed", () => {
info("Debugger client resumed successfully.");
prepareDebugger(debuggerPanel);
deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
});
prepareDebugger(debuggerPanel);
deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
});
return deferred.promise;
@ -595,7 +590,6 @@ AddonDebugger.prototype = {
this.debuggerPanel = toolbox.getCurrentPanel();
// Wait for the initial resume...
yield waitForClientEvents(this.debuggerPanel, "resumed");
yield prepareDebugger(this.debuggerPanel);
yield this._attachConsole();
}),
@ -1032,7 +1026,7 @@ function connect(client) {
}
function close(client) {
info("Closing client.\n");
info("Waiting for client to close.\n");
return new Promise(function (resolve) {
client.close(() => {
resolve();

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

@ -0,0 +1,100 @@
const {Cc, Ci, Cu} = require("chrome");
const Services = Cu.import("resource://gre/modules/Services.jsm", {}).Services;
const promise = require("promise");
function l10n(name) {
const bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
try {
return bundle.GetStringFromName(name);
} catch (e) {
throw new Error("Failed loading l10n string: " + name);
}
}
function handleThreadState(toolbox, event, packet) {
// Suppress interrupted events by default because the thread is
// paused/resumed a lot for various actions.
if (event !== "paused" || packet.why.type !== "interrupted") {
// TODO: Bug 1225492, we continue emitting events on the target
// like we used to, but we should emit these only on the
// threadClient now.
toolbox.target.emit("thread-" + event);
}
if (event === "paused") {
toolbox.highlightTool("jsdebugger");
if (packet.why.type === 'debuggerStatement' ||
packet.why.type === 'breakpoint' ||
packet.why.type === 'exception') {
toolbox.raise();
toolbox.selectTool("jsdebugger");
}
} else if (event === "resumed") {
toolbox.unhighlightTool("jsdebugger");
}
}
function attachThread(toolbox) {
let deferred = promise.defer();
let target = toolbox.target;
let { form: { chromeDebugger, actor } } = target;
let threadOptions = {
useSourceMaps: Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled"),
autoBlackBox: Services.prefs.getBoolPref("devtools.debugger.auto-black-box"),
pauseOnExceptions: Services.prefs.getBoolPref("devtools.debugger.pause-on-exceptions"),
ignoreCaughtExceptions: Services.prefs.getBoolPref("devtools.debugger.ignore-caught-exceptions")
};
let handleResponse = (res, threadClient) => {
if (res.error) {
deferred.reject(new Error("Couldn't attach to thread: " + res.error));
return;
}
threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));
if (!threadClient.paused) {
deferred.reject(
new Error("Thread in wrong state when starting up, should be paused")
);
}
threadClient.resume(res => {
if (res.error === "wrongOrder") {
const box = toolbox.getNotificationBox();
box.appendNotification(
l10n("toolbox.resumeOrderWarning"),
"wrong-resume-order",
"",
box.PRIORITY_WARNING_HIGH
);
}
deferred.resolve(threadClient)
});
}
if (target.isAddon) {
// Attaching an addon
target.client.attachAddon(actor, res => {
target.client.attachThread(res.threadActor, handleResponse);
});
} else if (target.isTabActor) {
// Attaching a normal thread
target.activeTab.attachThread(threadOptions, handleResponse);
} else {
// Attaching the browser debugger
target.client.attachThread(chromeDebugger, handleResponse);
}
return deferred.promise;
}
function detachThread(threadClient) {
threadClient.removeListener("paused");
threadClient.removeListener("resumed");
}
module.exports = { attachThread, detachThread };

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

@ -7,6 +7,7 @@
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
DevToolsModules(
'attach-thread.js',
'gDevTools.jsm',
'selection.js',
'sidebar.js',

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

@ -117,9 +117,6 @@ exports.TargetFactory = {
function TabTarget(tab) {
EventEmitter.decorate(this);
this.destroy = this.destroy.bind(this);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
this.activeTab = this.activeConsole = null;
// Only real tabs need initialization here. Placeholder objects for remote
// targets will be initialized after a makeRemote method call.
@ -362,10 +359,6 @@ TabTarget.prototype = {
return !this.window;
},
get isThreadPaused() {
return !!this._isThreadPaused;
},
/**
* Adds remote protocol capabilities to the target, so that it can be used
* for tools that support the Remote Debugging Protocol even for local
@ -533,20 +526,6 @@ TabTarget.prototype = {
}
},
/**
* Handle script status.
*/
_handleThreadState: function(event) {
switch (event) {
case "thread-resumed":
this._isThreadPaused = false;
break;
case "thread-paused":
this._isThreadPaused = true;
break;
}
},
/**
* Target is not alive anymore.
*/
@ -562,11 +541,6 @@ TabTarget.prototype = {
// Before taking any action, notify listeners that destruction is imminent.
this.emit("close");
// First of all, do cleanup tasks that pertain to both remoted and
// non-remoted targets.
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
if (this._tab) {
this._teardownListeners();
}
@ -612,6 +586,7 @@ TabTarget.prototype = {
} else {
promiseTargets.delete(this._form);
}
this.activeTab = null;
this.activeConsole = null;
this._client = null;
@ -718,8 +693,6 @@ function WorkerTarget(workerClient) {
* requiring only minimal changes to the rest of the frontend.
*/
WorkerTarget.prototype = {
destroy: function () {},
get isRemote() {
return true;
},

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

@ -2,40 +2,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
function test() {
gBrowser.selectedTab = gBrowser.addTab();
let target = TargetFactory.forTab(gBrowser.selectedTab);
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
gDevTools.showToolbox(target).then(testReady);
}, true);
const onLoad = Task.async(function *(evt) {
gBrowser.selectedBrowser.removeEventListener("load", onLoad);
content.location = "data:text/html,test for dynamically registering and unregistering tools";
}
const toolbox = yield gDevTools.showToolbox(target, "webconsole");
ok(toolbox.isReady, "toolbox isReady is set");
ok(toolbox.threadClient, "toolbox has a thread client");
function testReady(toolbox)
{
ok(toolbox.isReady, "toolbox isReady is set");
testDouble(toolbox);
}
function testDouble(toolbox)
{
let target = toolbox.target;
let toolId = toolbox.currentToolId;
gDevTools.showToolbox(target, toolId).then(function(toolbox2) {
const toolbox2 = yield gDevTools.showToolbox(toolbox.target, toolbox.toolId);
is(toolbox2, toolbox, "same toolbox");
cleanup(toolbox);
});
}
function cleanup(toolbox)
{
toolbox.destroy().then(function() {
yield toolbox.destroy();
gBrowser.removeCurrentTab();
finish();
});
gBrowser.selectedBrowser.addEventListener("load", onLoad, true);
content.location = "data:text/html,test for toolbox being ready";
}

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

@ -20,6 +20,7 @@ var EventEmitter = require("devtools/shared/event-emitter");
var Telemetry = require("devtools/client/shared/telemetry");
var HUDService = require("devtools/client/webconsole/hudservice");
var viewSource = require("devtools/client/shared/view-source");
var { attachThread, detachThread } = require("./attach-thread");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
@ -251,6 +252,10 @@ Toolbox.prototype = {
return this._target;
},
get threadClient() {
return this._threadClient;
},
/**
* Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
* tab. See HostType for more details.
@ -348,12 +353,18 @@ Toolbox.prototype = {
let iframe = yield this._host.create();
let domReady = promise.defer();
// Load the toolbox-level actor fronts and utilities now
yield this._target.makeRemote();
iframe.setAttribute("src", this._URL);
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
let domHelper = new DOMHelpers(iframe.contentWindow);
domHelper.onceDOMReady(() => domReady.resolve());
// Optimization: fire up a few other things before waiting on
// the iframe being ready (makes startup faster)
// Load the toolbox-level actor fronts and utilities now
yield this._target.makeRemote();
// Attach the thread
this._threadClient = yield attachThread(this);
yield domReady.promise;
@ -1952,6 +1963,10 @@ Toolbox.prototype = {
// Destroy the profiler connection
outstanding.push(this.destroyPerformance());
// Detach the thread
detachThread(this._threadClient);
this._threadClient = null;
// We need to grab a reference to win before this._host is destroyed.
let win = this.frame.ownerGlobal;

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

@ -172,7 +172,7 @@ InspectorPanel.prototype = {
let notificationBox = this._toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
if (!notification && this._toolbox.currentToolId == "inspector" &&
this.target.isThreadPaused) {
this._toolbox.threadClient.paused) {
let message = strings.GetStringFromName("debuggerPausedWarning.message");
notificationBox.appendNotification(message,
"inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
@ -182,7 +182,7 @@ InspectorPanel.prototype = {
notificationBox.removeNotification(notification);
}
if (notification && !this.target.isThreadPaused) {
if (notification && !this._toolbox.threadClient.paused) {
notificationBox.removeNotification(notification);
}

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

@ -0,0 +1,10 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
'rule.js',
'text-property.js',
)

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

@ -0,0 +1,662 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci, Cu} = require("chrome");
const promise = require("promise");
const {CssLogic} = require("devtools/shared/styleinspector/css-logic");
const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
const {TextProperty} =
require("devtools/client/inspector/rules/models/text-property");
const {promiseWarn} = require("devtools/client/styleinspector/utils");
const {parseDeclarations} = require("devtools/client/shared/css-parsing-utils");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "osString", function() {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
});
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
/**
* A single style rule or declaration.
*
* @param {ElementStyle} elementStyle
* The ElementStyle to which this rule belongs.
* @param {Object} options
* The information used to construct this rule. Properties include:
* rule: A StyleRuleActor
* inherited: An element this rule was inherited from. If omitted,
* the rule applies directly to the current element.
* isSystem: Is this a user agent style?
*/
function Rule(elementStyle, options) {
this.elementStyle = elementStyle;
this.domRule = options.rule || null;
this.style = options.rule;
this.matchedSelectors = options.matchedSelectors || [];
this.pseudoElement = options.pseudoElement || "";
this.isSystem = options.isSystem;
this.inherited = options.inherited || null;
this.keyframes = options.keyframes || null;
this._modificationDepth = 0;
if (this.domRule && this.domRule.mediaText) {
this.mediaText = this.domRule.mediaText;
}
// Populate the text properties with the style's current authoredText
// value, and add in any disabled properties from the store.
this.textProps = this._getTextProperties();
this.textProps = this.textProps.concat(this._getDisabledProperties());
}
Rule.prototype = {
mediaText: "",
get title() {
let title = CssLogic.shortSource(this.sheet);
if (this.domRule.type !== ELEMENT_STYLE && this.ruleLine > 0) {
title += ":" + this.ruleLine;
}
return title + (this.mediaText ? " @media " + this.mediaText : "");
},
get inheritedSource() {
if (this._inheritedSource) {
return this._inheritedSource;
}
this._inheritedSource = "";
if (this.inherited) {
let eltText = this.inherited.tagName.toLowerCase();
if (this.inherited.id) {
eltText += "#" + this.inherited.id;
}
this._inheritedSource =
CssLogic._strings.formatStringFromName("rule.inheritedFrom",
[eltText], 1);
}
return this._inheritedSource;
},
get keyframesName() {
if (this._keyframesName) {
return this._keyframesName;
}
this._keyframesName = "";
if (this.keyframes) {
this._keyframesName =
CssLogic._strings.formatStringFromName("rule.keyframe",
[this.keyframes.name], 1);
}
return this._keyframesName;
},
get selectorText() {
return this.domRule.selectors ? this.domRule.selectors.join(", ") :
CssLogic.l10n("rule.sourceElement");
},
/**
* The rule's stylesheet.
*/
get sheet() {
return this.domRule ? this.domRule.parentStyleSheet : null;
},
/**
* The rule's line within a stylesheet
*/
get ruleLine() {
return this.domRule ? this.domRule.line : "";
},
/**
* 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 an object containing
* both the full and short version of the source string.
*/
getOriginalSourceStrings: function() {
return this.domRule.getOriginalLocation().then(({href,
line, mediaText}) => {
let mediaString = mediaText ? " @" + mediaText : "";
let linePart = line > 0 ? (":" + line) : "";
let sourceStrings = {
full: (href || CssLogic.l10n("rule.sourceInline")) + linePart +
mediaString,
short: CssLogic.shortSource({href: href}) + linePart + mediaString
};
return sourceStrings;
});
},
/**
* Returns true if the rule matches the creation options
* specified.
*
* @param {Object} options
* Creation options. See the Rule constructor for documentation.
*/
matches: function(options) {
return this.style === options.rule;
},
/**
* Create a new TextProperty to include in the rule.
*
* @param {String} name
* The text property name (such as "background" or "border-top").
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
* @param {TextProperty} siblingProp
* Optional, property next to which the new property will be added.
*/
createProperty: function(name, value, priority, siblingProp) {
let prop = new TextProperty(this, name, value, priority);
let ind;
if (siblingProp) {
ind = this.textProps.indexOf(siblingProp) + 1;
this.textProps.splice(ind, 0, prop);
} else {
ind = this.textProps.length;
this.textProps.push(prop);
}
this.applyProperties((modifications) => {
modifications.createProperty(ind, name, value, priority);
});
return prop;
},
/**
* Helper function for applyProperties that is called when the actor
* does not support as-authored styles. Store disabled properties
* in the element style's store.
*/
_applyPropertiesNoAuthored: function(modifications) {
this.elementStyle.markOverriddenAll();
let disabledProps = [];
for (let prop of this.textProps) {
if (prop.invisible) {
continue;
}
if (!prop.enabled) {
disabledProps.push({
name: prop.name,
value: prop.value,
priority: prop.priority
});
continue;
}
if (prop.value.trim() === "") {
continue;
}
modifications.setProperty(-1, prop.name, prop.value, prop.priority);
prop.updateComputed();
}
// Store disabled properties in the disabled store.
let disabled = this.elementStyle.store.disabled;
if (disabledProps.length > 0) {
disabled.set(this.style, disabledProps);
} else {
disabled.delete(this.style);
}
return modifications.apply().then(() => {
let cssProps = {};
for (let cssProp of parseDeclarations(this.style.authoredText)) {
cssProps[cssProp.name] = cssProp;
}
for (let textProp of this.textProps) {
if (!textProp.enabled) {
continue;
}
let cssProp = cssProps[textProp.name];
if (!cssProp) {
cssProp = {
name: textProp.name,
value: "",
priority: ""
};
}
textProp.priority = cssProp.priority;
}
});
},
/**
* A helper for applyProperties that applies properties in the "as
* authored" case; that is, when the StyleRuleActor supports
* setRuleText.
*/
_applyPropertiesAuthored: function(modifications) {
return modifications.apply().then(() => {
// The rewriting may have required some other property values to
// change, e.g., to insert some needed terminators. Update the
// relevant properties here.
for (let index in modifications.changedDeclarations) {
let newValue = modifications.changedDeclarations[index];
this.textProps[index].noticeNewValue(newValue);
}
// Recompute and redisplay the computed properties.
for (let prop of this.textProps) {
if (!prop.invisible && prop.enabled) {
prop.updateComputed();
prop.updateEditor();
}
}
});
},
/**
* Reapply all the properties in this rule, and update their
* computed styles. Will re-mark overridden properties. Sets the
* |_applyingModifications| property to a promise which will resolve
* when the edit has completed.
*
* @param {Function} modifier a function that takes a RuleModificationList
* (or RuleRewriter) as an argument and that modifies it
* to apply the desired edit
* @return {Promise} a promise which will resolve when the edit
* is complete
*/
applyProperties: function(modifier) {
// If there is already a pending modification, we have to wait
// until it settles before applying the next modification.
let resultPromise =
promise.resolve(this._applyingModifications).then(() => {
let modifications = this.style.startModifyingProperties();
modifier(modifications);
if (this.style.canSetRuleText) {
return this._applyPropertiesAuthored(modifications);
}
return this._applyPropertiesNoAuthored(modifications);
}).then(() => {
this.elementStyle.markOverriddenAll();
if (resultPromise === this._applyingModifications) {
this._applyingModifications = null;
this.elementStyle._changed();
}
}).catch(promiseWarn);
this._applyingModifications = resultPromise;
return resultPromise;
},
/**
* Renames a property.
*
* @param {TextProperty} property
* The property to rename.
* @param {String} name
* The new property name (such as "background" or "border-top").
*/
setPropertyName: function(property, name) {
if (name === property.name) {
return;
}
let oldName = property.name;
property.name = name;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.renameProperty(index, oldName, name);
});
},
/**
* Sets the value and priority of a property, then reapply all properties.
*
* @param {TextProperty} property
* The property to manipulate.
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
*/
setPropertyValue: function(property, value, priority) {
if (value === property.value && priority === property.priority) {
return;
}
property.value = value;
property.priority = priority;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.setProperty(index, property.name, value, priority);
});
},
/**
* Just sets the value and priority of a property, in order to preview its
* effect on the content document.
*
* @param {TextProperty} property
* The property which value will be previewed
* @param {String} value
* The value to be used for the preview
* @param {String} priority
* The property's priority (either "important" or an empty string).
*/
previewPropertyValue: function(property, value, priority) {
let modifications = this.style.startModifyingProperties();
modifications.setProperty(this.textProps.indexOf(property),
property.name, value, priority);
modifications.apply().then(() => {
// Ensure dispatching a ruleview-changed event
// also for previews
this.elementStyle._changed();
});
},
/**
* Disables or enables given TextProperty.
*
* @param {TextProperty} property
* The property to enable/disable
* @param {Boolean} value
*/
setPropertyEnabled: function(property, value) {
if (property.enabled === !!value) {
return;
}
property.enabled = !!value;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.setPropertyEnabled(index, property.name, property.enabled);
});
},
/**
* Remove a given TextProperty from the rule and update the rule
* accordingly.
*
* @param {TextProperty} property
* The property to be removed
*/
removeProperty: function(property) {
let index = this.textProps.indexOf(property);
this.textProps.splice(index, 1);
// Need to re-apply properties in case removing this TextProperty
// exposes another one.
this.applyProperties((modifications) => {
modifications.removeProperty(index, property.name);
});
},
/**
* Get the list of TextProperties from the style. Needs
* to parse the style's authoredText.
*/
_getTextProperties: function() {
let textProps = [];
let store = this.elementStyle.store;
let props = parseDeclarations(this.style.authoredText, true);
for (let prop of props) {
let name = prop.name;
// In an inherited rule, we only show inherited properties.
// However, we must keep all properties in order for rule
// rewriting to work properly. So, compute the "invisible"
// property here.
let invisible = this.inherited && !domUtils.isInheritedProperty(name);
let value = store.userProperties.getProperty(this.style, name,
prop.value);
let textProp = new TextProperty(this, name, value, prop.priority,
!("commentOffsets" in prop),
invisible);
textProps.push(textProp);
}
return textProps;
},
/**
* Return the list of disabled properties from the store for this rule.
*/
_getDisabledProperties: function() {
let store = this.elementStyle.store;
// Include properties from the disabled property store, if any.
let disabledProps = store.disabled.get(this.style);
if (!disabledProps) {
return [];
}
let textProps = [];
for (let prop of disabledProps) {
let value = store.userProperties.getProperty(this.style, prop.name,
prop.value);
let textProp = new TextProperty(this, prop.name, value, prop.priority);
textProp.enabled = false;
textProps.push(textProp);
}
return textProps;
},
/**
* Reread the current state of the rules and rebuild text
* properties as needed.
*/
refresh: function(options) {
this.matchedSelectors = options.matchedSelectors || [];
let newTextProps = this._getTextProperties();
// Update current properties for each property present on the style.
// This will mark any touched properties with _visited so we
// can detect properties that weren't touched (because they were
// removed from the style).
// Also keep track of properties that didn't exist in the current set
// of properties.
let brandNewProps = [];
for (let newProp of newTextProps) {
if (!this._updateTextProperty(newProp)) {
brandNewProps.push(newProp);
}
}
// Refresh editors and disabled state for all the properties that
// were updated.
for (let prop of this.textProps) {
// Properties that weren't touched during the update
// process must no longer exist on the node. Mark them disabled.
if (!prop._visited) {
prop.enabled = false;
prop.updateEditor();
} else {
delete prop._visited;
}
}
// Add brand new properties.
this.textProps = this.textProps.concat(brandNewProps);
// Refresh the editor if one already exists.
if (this.editor) {
this.editor.populate();
}
},
/**
* Update the current TextProperties that match a given property
* from the authoredText. Will choose one existing TextProperty to update
* with the new property's value, and will disable all others.
*
* When choosing the best match to reuse, properties will be chosen
* by assigning a rank and choosing the highest-ranked property:
* Name, value, and priority match, enabled. (6)
* Name, value, and priority match, disabled. (5)
* Name and value match, enabled. (4)
* Name and value match, disabled. (3)
* Name matches, enabled. (2)
* Name matches, disabled. (1)
*
* If no existing properties match the property, nothing happens.
*
* @param {TextProperty} newProp
* The current version of the property, as parsed from the
* authoredText in Rule._getTextProperties().
* @return {Boolean} true if a property was updated, false if no properties
* were updated.
*/
_updateTextProperty: function(newProp) {
let match = { rank: 0, prop: null };
for (let prop of this.textProps) {
if (prop.name !== newProp.name) {
continue;
}
// Mark this property visited.
prop._visited = true;
// Start at rank 1 for matching name.
let rank = 1;
// Value and Priority matches add 2 to the rank.
// Being enabled adds 1. This ranks better matches higher,
// with priority breaking ties.
if (prop.value === newProp.value) {
rank += 2;
if (prop.priority === newProp.priority) {
rank += 2;
}
}
if (prop.enabled) {
rank += 1;
}
if (rank > match.rank) {
if (match.prop) {
// We outrank a previous match, disable it.
match.prop.enabled = false;
match.prop.updateEditor();
}
match.rank = rank;
match.prop = prop;
} else if (rank) {
// A previous match outranks us, disable ourself.
prop.enabled = false;
prop.updateEditor();
}
}
// If we found a match, update its value with the new text property
// value.
if (match.prop) {
match.prop.set(newProp);
return true;
}
return false;
},
/**
* Jump between editable properties in the UI. If the focus direction is
* forward, begin editing the next property name if available or focus the
* new property editor otherwise. If the focus direction is backward,
* begin editing the previous property value or focus the selector editor if
* this is the first element in the property list.
*
* @param {TextProperty} textProperty
* The text property that will be left to focus on a sibling.
* @param {Number} direction
* The move focus direction number.
*/
editClosestTextProperty: function(textProperty, direction) {
let index = this.textProps.indexOf(textProperty);
if (direction === Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
for (++index; index < this.textProps.length; ++index) {
if (!this.textProps[index].invisible) {
break;
}
}
if (index === this.textProps.length) {
textProperty.rule.editor.closeBrace.click();
} else {
this.textProps[index].editor.nameSpan.click();
}
} else if (direction === Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
for (--index; index >= 0; --index) {
if (!this.textProps[index].invisible) {
break;
}
}
if (index < 0) {
textProperty.editor.ruleEditor.selectorText.click();
} else {
this.textProps[index].editor.valueSpan.click();
}
}
},
/**
* Return a string representation of the rule.
*/
stringifyRule: function() {
let selectorText = this.selectorText;
let cssText = "";
let terminator = osString === "WINNT" ? "\r\n" : "\n";
for (let textProp of this.textProps) {
if (!textProp.invisible) {
cssText += "\t" + textProp.stringifyProperty() + terminator;
}
}
return selectorText + " {" + terminator + cssText + "}";
},
/**
* See whether this rule has any non-invisible properties.
* @return {Boolean} true if there is any visible property, or false
* if all properties are invisible
*/
hasAnyVisibleProperties: function() {
for (let prop of this.textProps) {
if (!prop.invisible) {
return true;
}
}
return false;
}
};
exports.Rule = Rule;

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

@ -0,0 +1,215 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci, Cu} = require("chrome");
const {escapeCSSComment} = require("devtools/client/shared/css-parsing-utils");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
/**
* TextProperty:
* Manages a single property from the authoredText attribute of the
* relevant declaration.
* Maintains a list of computed properties that come from this
* property declaration.
* Changes to the TextProperty are sent to its related Rule for
* application.
*/
/**
* A single property in a rule's authoredText.
*
* @param {Rule} rule
* The rule this TextProperty came from.
* @param {String} name
* The text property name (such as "background" or "border-top").
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
* @param {Boolean} enabled
* Whether the property is enabled.
* @param {Boolean} invisible
* Whether the property is invisible. An invisible property
* does not show up in the UI; these are needed so that the
* index of a property in Rule.textProps is the same as the index
* coming from parseDeclarations.
*/
function TextProperty(rule, name, value, priority, enabled = true,
invisible = false) {
this.rule = rule;
this.name = name;
this.value = value;
this.priority = priority;
this.enabled = !!enabled;
this.invisible = invisible;
this.updateComputed();
}
TextProperty.prototype = {
/**
* Update the editor associated with this text property,
* if any.
*/
updateEditor: function() {
if (this.editor) {
this.editor.update();
}
},
/**
* Update the list of computed properties for this text property.
*/
updateComputed: function() {
if (!this.name) {
return;
}
// This is a bit funky. To get the list of computed properties
// for this text property, we'll set the property on a dummy element
// and see what the computed style looks like.
let dummyElement = this.rule.elementStyle.dummyElement;
let dummyStyle = dummyElement.style;
dummyStyle.cssText = "";
dummyStyle.setProperty(this.name, this.value, this.priority);
this.computed = [];
try {
// Manually get all the properties that are set when setting a value on
// this.name and check the computed style on dummyElement for each one.
// If we just read dummyStyle, it would skip properties when value === "".
let subProps = domUtils.getSubpropertiesForCSSProperty(this.name);
for (let prop of subProps) {
this.computed.push({
textProp: this,
name: prop,
value: dummyStyle.getPropertyValue(prop),
priority: dummyStyle.getPropertyPriority(prop),
});
}
} catch (e) {
// This is a partial property name, probably from cutting and pasting
// text. At this point don't check for computed properties.
}
},
/**
* Set all the values from another TextProperty instance into
* this TextProperty instance.
*
* @param {TextProperty} prop
* The other TextProperty instance.
*/
set: function(prop) {
let changed = false;
for (let item of ["name", "value", "priority", "enabled"]) {
if (this[item] !== prop[item]) {
this[item] = prop[item];
changed = true;
}
}
if (changed) {
this.updateEditor();
}
},
setValue: function(value, priority, force = false) {
let store = this.rule.elementStyle.store;
if (this.editor && value !== this.editor.committed.value || force) {
store.userProperties.setProperty(this.rule.style, this.name, value);
}
this.rule.setPropertyValue(this, value, priority);
this.updateEditor();
},
/**
* Called when the property's value has been updated externally, and
* the property and editor should update.
*/
noticeNewValue: function(value) {
if (value !== this.value) {
this.value = value;
this.updateEditor();
}
},
setName: function(name) {
let store = this.rule.elementStyle.store;
if (name !== this.name) {
store.userProperties.setProperty(this.rule.style, name,
this.editor.committed.value);
}
this.rule.setPropertyName(this, name);
this.updateEditor();
},
setEnabled: function(value) {
this.rule.setPropertyEnabled(this, value);
this.updateEditor();
},
remove: function() {
this.rule.removeProperty(this);
},
/**
* Return a string representation of the rule property.
*/
stringifyProperty: function() {
// Get the displayed property value
let declaration = this.name + ": " + this.editor.valueSpan.textContent +
";";
// Comment out property declarations that are not enabled
if (!this.enabled) {
declaration = "/* " + escapeCSSComment(declaration) + " */";
}
return declaration;
},
/**
* See whether this property's name is known.
*
* @return {Boolean} true if the property name is known, false otherwise.
*/
isKnownProperty: function() {
try {
// If the property name is invalid, the cssPropertyIsShorthand
// will throw an exception. But if it is valid, no exception will
// be thrown; so we just ignore the return value.
domUtils.cssPropertyIsShorthand(this.name);
return true;
} catch (e) {
return false;
}
},
/**
* Validate this property. Does it make sense for this value to be assigned
* to this property name? This does not apply the property value
*
* @return {Boolean} true if the property value is valid, false otherwise.
*/
isValid: function() {
return domUtils.cssPropertyIsValid(this.name, this.value);
}
};
exports.TextProperty = TextProperty;

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

@ -4,6 +4,10 @@
# 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/.
DIRS += [
'models',
]
DevToolsModules(
'rules.js',
)

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

@ -17,6 +17,7 @@ const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
const {OutputParser} = require("devtools/client/shared/output-parser");
const {PrefObserver, PREF_ORIG_SOURCES} =
require("devtools/client/styleeditor/utils");
const {Rule} = require("devtools/client/inspector/rules/models/rule");
const {
createChild,
appendText,
@ -26,7 +27,6 @@ const {
throttle
} = require("devtools/client/styleinspector/utils");
const {
escapeCSSComment,
parseDeclarations,
parseSingleValue,
parsePseudoClassesAndAttributes,
@ -34,6 +34,7 @@ const {
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/shared/css-parsing-utils");
loader.lazyRequireGetter(this, "overlays",
"devtools/client/styleinspector/style-inspector-overlays");
loader.lazyRequireGetter(this, "EventEmitter",
@ -457,827 +458,6 @@ ElementStyle.prototype = {
}
};
/**
* A single style rule or declaration.
*
* @param {ElementStyle} elementStyle
* The ElementStyle to which this rule belongs.
* @param {Object} options
* The information used to construct this rule. Properties include:
* rule: A StyleRuleActor
* inherited: An element this rule was inherited from. If omitted,
* the rule applies directly to the current element.
* isSystem: Is this a user agent style?
*/
function Rule(elementStyle, options) {
this.elementStyle = elementStyle;
this.domRule = options.rule || null;
this.style = options.rule;
this.matchedSelectors = options.matchedSelectors || [];
this.pseudoElement = options.pseudoElement || "";
this.isSystem = options.isSystem;
this.inherited = options.inherited || null;
this.keyframes = options.keyframes || null;
this._modificationDepth = 0;
if (this.domRule && this.domRule.mediaText) {
this.mediaText = this.domRule.mediaText;
}
// Populate the text properties with the style's current authoredText
// value, and add in any disabled properties from the store.
this.textProps = this._getTextProperties();
this.textProps = this.textProps.concat(this._getDisabledProperties());
}
Rule.prototype = {
mediaText: "",
get title() {
let title = CssLogic.shortSource(this.sheet);
if (this.domRule.type !== ELEMENT_STYLE && this.ruleLine > 0) {
title += ":" + this.ruleLine;
}
return title + (this.mediaText ? " @media " + this.mediaText : "");
},
get inheritedSource() {
if (this._inheritedSource) {
return this._inheritedSource;
}
this._inheritedSource = "";
if (this.inherited) {
let eltText = this.inherited.tagName.toLowerCase();
if (this.inherited.id) {
eltText += "#" + this.inherited.id;
}
this._inheritedSource =
CssLogic._strings.formatStringFromName("rule.inheritedFrom",
[eltText], 1);
}
return this._inheritedSource;
},
get keyframesName() {
if (this._keyframesName) {
return this._keyframesName;
}
this._keyframesName = "";
if (this.keyframes) {
this._keyframesName =
CssLogic._strings.formatStringFromName("rule.keyframe",
[this.keyframes.name], 1);
}
return this._keyframesName;
},
get selectorText() {
return this.domRule.selectors ? this.domRule.selectors.join(", ") :
CssLogic.l10n("rule.sourceElement");
},
/**
* The rule's stylesheet.
*/
get sheet() {
return this.domRule ? this.domRule.parentStyleSheet : null;
},
/**
* The rule's line within a stylesheet
*/
get ruleLine() {
return this.domRule ? this.domRule.line : "";
},
/**
* 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 an object containing
* both the full and short version of the source string.
*/
getOriginalSourceStrings: function() {
return this.domRule.getOriginalLocation().then(({href,
line, mediaText}) => {
let mediaString = mediaText ? " @" + mediaText : "";
let linePart = line > 0 ? (":" + line) : "";
let sourceStrings = {
full: (href || CssLogic.l10n("rule.sourceInline")) + linePart +
mediaString,
short: CssLogic.shortSource({href: href}) + linePart + mediaString
};
return sourceStrings;
});
},
/**
* Returns true if the rule matches the creation options
* specified.
*
* @param {Object} options
* Creation options. See the Rule constructor for documentation.
*/
matches: function(options) {
return this.style === options.rule;
},
/**
* Create a new TextProperty to include in the rule.
*
* @param {String} name
* The text property name (such as "background" or "border-top").
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
* @param {TextProperty} siblingProp
* Optional, property next to which the new property will be added.
*/
createProperty: function(name, value, priority, siblingProp) {
let prop = new TextProperty(this, name, value, priority);
let ind;
if (siblingProp) {
ind = this.textProps.indexOf(siblingProp) + 1;
this.textProps.splice(ind, 0, prop);
} else {
ind = this.textProps.length;
this.textProps.push(prop);
}
this.applyProperties((modifications) => {
modifications.createProperty(ind, name, value, priority);
});
return prop;
},
/**
* Helper function for applyProperties that is called when the actor
* does not support as-authored styles. Store disabled properties
* in the element style's store.
*/
_applyPropertiesNoAuthored: function(modifications) {
this.elementStyle.markOverriddenAll();
let disabledProps = [];
for (let prop of this.textProps) {
if (prop.invisible) {
continue;
}
if (!prop.enabled) {
disabledProps.push({
name: prop.name,
value: prop.value,
priority: prop.priority
});
continue;
}
if (prop.value.trim() === "") {
continue;
}
modifications.setProperty(-1, prop.name, prop.value, prop.priority);
prop.updateComputed();
}
// Store disabled properties in the disabled store.
let disabled = this.elementStyle.store.disabled;
if (disabledProps.length > 0) {
disabled.set(this.style, disabledProps);
} else {
disabled.delete(this.style);
}
return modifications.apply().then(() => {
let cssProps = {};
for (let cssProp of parseDeclarations(this.style.authoredText)) {
cssProps[cssProp.name] = cssProp;
}
for (let textProp of this.textProps) {
if (!textProp.enabled) {
continue;
}
let cssProp = cssProps[textProp.name];
if (!cssProp) {
cssProp = {
name: textProp.name,
value: "",
priority: ""
};
}
textProp.priority = cssProp.priority;
}
});
},
/**
* A helper for applyProperties that applies properties in the "as
* authored" case; that is, when the StyleRuleActor supports
* setRuleText.
*/
_applyPropertiesAuthored: function(modifications) {
return modifications.apply().then(() => {
// The rewriting may have required some other property values to
// change, e.g., to insert some needed terminators. Update the
// relevant properties here.
for (let index in modifications.changedDeclarations) {
let newValue = modifications.changedDeclarations[index];
this.textProps[index].noticeNewValue(newValue);
}
// Recompute and redisplay the computed properties.
for (let prop of this.textProps) {
if (!prop.invisible && prop.enabled) {
prop.updateComputed();
prop.updateEditor();
}
}
});
},
/**
* Reapply all the properties in this rule, and update their
* computed styles. Will re-mark overridden properties. Sets the
* |_applyingModifications| property to a promise which will resolve
* when the edit has completed.
*
* @param {Function} modifier a function that takes a RuleModificationList
* (or RuleRewriter) as an argument and that modifies it
* to apply the desired edit
* @return {Promise} a promise which will resolve when the edit
* is complete
*/
applyProperties: function(modifier) {
// If there is already a pending modification, we have to wait
// until it settles before applying the next modification.
let resultPromise =
promise.resolve(this._applyingModifications).then(() => {
let modifications = this.style.startModifyingProperties();
modifier(modifications);
if (this.style.canSetRuleText) {
return this._applyPropertiesAuthored(modifications);
}
return this._applyPropertiesNoAuthored(modifications);
}).then(() => {
this.elementStyle.markOverriddenAll();
if (resultPromise === this._applyingModifications) {
this._applyingModifications = null;
this.elementStyle._changed();
}
}).catch(promiseWarn);
this._applyingModifications = resultPromise;
return resultPromise;
},
/**
* Renames a property.
*
* @param {TextProperty} property
* The property to rename.
* @param {String} name
* The new property name (such as "background" or "border-top").
*/
setPropertyName: function(property, name) {
if (name === property.name) {
return;
}
let oldName = property.name;
property.name = name;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.renameProperty(index, oldName, name);
});
},
/**
* Sets the value and priority of a property, then reapply all properties.
*
* @param {TextProperty} property
* The property to manipulate.
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
*/
setPropertyValue: function(property, value, priority) {
if (value === property.value && priority === property.priority) {
return;
}
property.value = value;
property.priority = priority;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.setProperty(index, property.name, value, priority);
});
},
/**
* Just sets the value and priority of a property, in order to preview its
* effect on the content document.
*
* @param {TextProperty} property
* The property which value will be previewed
* @param {String} value
* The value to be used for the preview
* @param {String} priority
* The property's priority (either "important" or an empty string).
*/
previewPropertyValue: function(property, value, priority) {
let modifications = this.style.startModifyingProperties();
modifications.setProperty(this.textProps.indexOf(property),
property.name, value, priority);
modifications.apply().then(() => {
// Ensure dispatching a ruleview-changed event
// also for previews
this.elementStyle._changed();
});
},
/**
* Disables or enables given TextProperty.
*
* @param {TextProperty} property
* The property to enable/disable
* @param {Boolean} value
*/
setPropertyEnabled: function(property, value) {
if (property.enabled === !!value) {
return;
}
property.enabled = !!value;
let index = this.textProps.indexOf(property);
this.applyProperties((modifications) => {
modifications.setPropertyEnabled(index, property.name, property.enabled);
});
},
/**
* Remove a given TextProperty from the rule and update the rule
* accordingly.
*
* @param {TextProperty} property
* The property to be removed
*/
removeProperty: function(property) {
let index = this.textProps.indexOf(property);
this.textProps.splice(index, 1);
// Need to re-apply properties in case removing this TextProperty
// exposes another one.
this.applyProperties((modifications) => {
modifications.removeProperty(index, property.name);
});
},
/**
* Get the list of TextProperties from the style. Needs
* to parse the style's authoredText.
*/
_getTextProperties: function() {
let textProps = [];
let store = this.elementStyle.store;
let props = parseDeclarations(this.style.authoredText, true);
for (let prop of props) {
let name = prop.name;
// In an inherited rule, we only show inherited properties.
// However, we must keep all properties in order for rule
// rewriting to work properly. So, compute the "invisible"
// property here.
let invisible = this.inherited && !domUtils.isInheritedProperty(name);
let value = store.userProperties.getProperty(this.style, name,
prop.value);
let textProp = new TextProperty(this, name, value, prop.priority,
!("commentOffsets" in prop),
invisible);
textProps.push(textProp);
}
return textProps;
},
/**
* Return the list of disabled properties from the store for this rule.
*/
_getDisabledProperties: function() {
let store = this.elementStyle.store;
// Include properties from the disabled property store, if any.
let disabledProps = store.disabled.get(this.style);
if (!disabledProps) {
return [];
}
let textProps = [];
for (let prop of disabledProps) {
let value = store.userProperties.getProperty(this.style, prop.name,
prop.value);
let textProp = new TextProperty(this, prop.name, value, prop.priority);
textProp.enabled = false;
textProps.push(textProp);
}
return textProps;
},
/**
* Reread the current state of the rules and rebuild text
* properties as needed.
*/
refresh: function(options) {
this.matchedSelectors = options.matchedSelectors || [];
let newTextProps = this._getTextProperties();
// Update current properties for each property present on the style.
// This will mark any touched properties with _visited so we
// can detect properties that weren't touched (because they were
// removed from the style).
// Also keep track of properties that didn't exist in the current set
// of properties.
let brandNewProps = [];
for (let newProp of newTextProps) {
if (!this._updateTextProperty(newProp)) {
brandNewProps.push(newProp);
}
}
// Refresh editors and disabled state for all the properties that
// were updated.
for (let prop of this.textProps) {
// Properties that weren't touched during the update
// process must no longer exist on the node. Mark them disabled.
if (!prop._visited) {
prop.enabled = false;
prop.updateEditor();
} else {
delete prop._visited;
}
}
// Add brand new properties.
this.textProps = this.textProps.concat(brandNewProps);
// Refresh the editor if one already exists.
if (this.editor) {
this.editor.populate();
}
},
/**
* Update the current TextProperties that match a given property
* from the authoredText. Will choose one existing TextProperty to update
* with the new property's value, and will disable all others.
*
* When choosing the best match to reuse, properties will be chosen
* by assigning a rank and choosing the highest-ranked property:
* Name, value, and priority match, enabled. (6)
* Name, value, and priority match, disabled. (5)
* Name and value match, enabled. (4)
* Name and value match, disabled. (3)
* Name matches, enabled. (2)
* Name matches, disabled. (1)
*
* If no existing properties match the property, nothing happens.
*
* @param {TextProperty} newProp
* The current version of the property, as parsed from the
* authoredText in Rule._getTextProperties().
* @return {Boolean} true if a property was updated, false if no properties
* were updated.
*/
_updateTextProperty: function(newProp) {
let match = { rank: 0, prop: null };
for (let prop of this.textProps) {
if (prop.name !== newProp.name) {
continue;
}
// Mark this property visited.
prop._visited = true;
// Start at rank 1 for matching name.
let rank = 1;
// Value and Priority matches add 2 to the rank.
// Being enabled adds 1. This ranks better matches higher,
// with priority breaking ties.
if (prop.value === newProp.value) {
rank += 2;
if (prop.priority === newProp.priority) {
rank += 2;
}
}
if (prop.enabled) {
rank += 1;
}
if (rank > match.rank) {
if (match.prop) {
// We outrank a previous match, disable it.
match.prop.enabled = false;
match.prop.updateEditor();
}
match.rank = rank;
match.prop = prop;
} else if (rank) {
// A previous match outranks us, disable ourself.
prop.enabled = false;
prop.updateEditor();
}
}
// If we found a match, update its value with the new text property
// value.
if (match.prop) {
match.prop.set(newProp);
return true;
}
return false;
},
/**
* Jump between editable properties in the UI. If the focus direction is
* forward, begin editing the next property name if available or focus the
* new property editor otherwise. If the focus direction is backward,
* begin editing the previous property value or focus the selector editor if
* this is the first element in the property list.
*
* @param {TextProperty} textProperty
* The text property that will be left to focus on a sibling.
* @param {Number} direction
* The move focus direction number.
*/
editClosestTextProperty: function(textProperty, direction) {
let index = this.textProps.indexOf(textProperty);
if (direction === Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
for (++index; index < this.textProps.length; ++index) {
if (!this.textProps[index].invisible) {
break;
}
}
if (index === this.textProps.length) {
textProperty.rule.editor.closeBrace.click();
} else {
this.textProps[index].editor.nameSpan.click();
}
} else if (direction === Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
for (--index; index >= 0; --index) {
if (!this.textProps[index].invisible) {
break;
}
}
if (index < 0) {
textProperty.editor.ruleEditor.selectorText.click();
} else {
this.textProps[index].editor.valueSpan.click();
}
}
},
/**
* Return a string representation of the rule.
*/
stringifyRule: function() {
let selectorText = this.selectorText;
let cssText = "";
let terminator = osString === "WINNT" ? "\r\n" : "\n";
for (let textProp of this.textProps) {
if (!textProp.invisible) {
cssText += "\t" + textProp.stringifyProperty() + terminator;
}
}
return selectorText + " {" + terminator + cssText + "}";
},
/**
* See whether this rule has any non-invisible properties.
* @return {Boolean} true if there is any visible property, or false
* if all properties are invisible
*/
hasAnyVisibleProperties: function() {
for (let prop of this.textProps) {
if (!prop.invisible) {
return true;
}
}
return false;
}
};
/**
* A single property in a rule's authoredText.
*
* @param {Rule} rule
* The rule this TextProperty came from.
* @param {String} name
* The text property name (such as "background" or "border-top").
* @param {String} value
* The property's value (not including priority).
* @param {String} priority
* The property's priority (either "important" or an empty string).
* @param {Boolean} enabled
* Whether the property is enabled.
* @param {Boolean} invisible
* Whether the property is invisible. An invisible property
* does not show up in the UI; these are needed so that the
* index of a property in Rule.textProps is the same as the index
* coming from parseDeclarations.
*/
function TextProperty(rule, name, value, priority, enabled = true,
invisible = false) {
this.rule = rule;
this.name = name;
this.value = value;
this.priority = priority;
this.enabled = !!enabled;
this.invisible = invisible;
this.updateComputed();
}
TextProperty.prototype = {
/**
* Update the editor associated with this text property,
* if any.
*/
updateEditor: function() {
if (this.editor) {
this.editor.update();
}
},
/**
* Update the list of computed properties for this text property.
*/
updateComputed: function() {
if (!this.name) {
return;
}
// This is a bit funky. To get the list of computed properties
// for this text property, we'll set the property on a dummy element
// and see what the computed style looks like.
let dummyElement = this.rule.elementStyle.dummyElement;
let dummyStyle = dummyElement.style;
dummyStyle.cssText = "";
dummyStyle.setProperty(this.name, this.value, this.priority);
this.computed = [];
try {
// Manually get all the properties that are set when setting a value on
// this.name and check the computed style on dummyElement for each one.
// If we just read dummyStyle, it would skip properties when value === "".
let subProps = domUtils.getSubpropertiesForCSSProperty(this.name);
for (let prop of subProps) {
this.computed.push({
textProp: this,
name: prop,
value: dummyStyle.getPropertyValue(prop),
priority: dummyStyle.getPropertyPriority(prop),
});
}
} catch (e) {
// This is a partial property name, probably from cutting and pasting
// text. At this point don't check for computed properties.
}
},
/**
* Set all the values from another TextProperty instance into
* this TextProperty instance.
*
* @param {TextProperty} prop
* The other TextProperty instance.
*/
set: function(prop) {
let changed = false;
for (let item of ["name", "value", "priority", "enabled"]) {
if (this[item] !== prop[item]) {
this[item] = prop[item];
changed = true;
}
}
if (changed) {
this.updateEditor();
}
},
setValue: function(value, priority, force = false) {
let store = this.rule.elementStyle.store;
if (this.editor && value !== this.editor.committed.value || force) {
store.userProperties.setProperty(this.rule.style, this.name, value);
}
this.rule.setPropertyValue(this, value, priority);
this.updateEditor();
},
/**
* Called when the property's value has been updated externally, and
* the property and editor should update.
*/
noticeNewValue: function(value) {
if (value !== this.value) {
this.value = value;
this.updateEditor();
}
},
setName: function(name) {
let store = this.rule.elementStyle.store;
if (name !== this.name) {
store.userProperties.setProperty(this.rule.style, name,
this.editor.committed.value);
}
this.rule.setPropertyName(this, name);
this.updateEditor();
},
setEnabled: function(value) {
this.rule.setPropertyEnabled(this, value);
this.updateEditor();
},
remove: function() {
this.rule.removeProperty(this);
},
/**
* Return a string representation of the rule property.
*/
stringifyProperty: function() {
// Get the displayed property value
let declaration = this.name + ": " + this.editor.valueSpan.textContent +
";";
// Comment out property declarations that are not enabled
if (!this.enabled) {
declaration = "/* " + escapeCSSComment(declaration) + " */";
}
return declaration;
},
/**
* See whether this property's name is known.
*
* @return {Boolean} true if the property name is known, false otherwise.
*/
isKnownProperty: function() {
try {
// If the property name is invalid, the cssPropertyIsShorthand
// will throw an exception. But if it is valid, no exception will
// be thrown; so we just ignore the return value.
domUtils.cssPropertyIsShorthand(this.name);
return true;
} catch (e) {
return false;
}
},
/**
* Validate this property. Does it make sense for this value to be assigned
* to this property name? This does not apply the property value
*
* @return {Boolean} true if the property value is valid, false otherwise.
*/
isValid: function() {
return domUtils.cssPropertyIsValid(this.name, this.value);
}
};
/**
* View hierarchy mostly follows the model hierarchy.
*
@ -4043,10 +3223,6 @@ XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
.getService(Ci.nsIClipboardHelper);
});
XPCOMUtils.defineLazyGetter(this, "osString", function() {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
});
XPCOMUtils.defineLazyGetter(this, "_strings", function() {
return Services.strings.createBundle(
"chrome://devtools-shared/locale/styleinspector.properties");

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

@ -54,6 +54,18 @@ add_task(function*() {
info("Entering a property value");
editor.input.value = "red";
// Setting the input value above schedules a preview to be shown in 10ms
// which triggers a ruleview-changed event. If the event arrives at just the
// right moment after pressing escape but before the new property is removed
// from the rule view (it's done after a tick), the test continues too soon
// and the assertions below fail as the new property is still there (bug
// 1209295).
//
// Thus, wait for the ruleview-changed event triggered by the preview before
// continuing to discard the new property.
info("Waiting for preview to be applied.");
yield view.once("ruleview-changed");
info("Escaping out of the field");
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);

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

@ -72,6 +72,8 @@ const TEST_DATA = [
];
add_task(function*() {
requestLongerTimeout(2);
info("Starting the test with the pref set to true before toolbox is opened");
yield setUserAgentStylesPref(true);

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

@ -85,7 +85,7 @@ var Converter = Class({
var str = {};
var bytesRead = is.readString(aCount, str);
if (!bytesRead) {
throw new Error("Stream converter failed to read the input stream!");
break;
}
aCount -= bytesRead;
this.data += str.value;

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

@ -114,3 +114,5 @@ toolbox.viewCssSourceInStyleEditor.label=Open File in Style-Editor
# corresponding URL as a js file in the Debugger tool.
# DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used.
toolbox.viewJsSourceInDebugger.label=Open File in Debugger
toolbox.resumeOrderWarning=Page did not resume after the debugger was attached. To fix this, please close and re-open the toolbox.

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

@ -217,4 +217,4 @@
<!ENTITY simulator_pixelRatio "Pixel Ratio">
<!ENTITY simulator_tv_data "TV Simulation">
<!ENTITY simulator_tv_data_open "Config Data">
<!ENTITY simulator_tv_data_open_button "Open Config Directory...">
<!ENTITY simulator_tv_data_open_button "Open Config Directory">

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

@ -10,3 +10,5 @@ support-files =
[test_tree_06.html]
[test_tree_07.html]
[test_tree_08.html]
[test_tree_09.html]
[test_tree_10.html]

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

@ -92,7 +92,7 @@ var TEST_TREE_INTERFACE = {
renderItem: (x, depth, focused, arrow) => "-".repeat(depth) + x + ":" + focused + "\n",
getRoots: () => ["A", "M"],
getKey: x => "key-" + x,
itemHeight: 1
itemHeight: 1,
};
// All tests are asynchronous.

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

@ -19,11 +19,9 @@ window.onload = Task.async(function* () {
let React = browserRequire("devtools/client/shared/vendor/react");
let Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
const tree = ReactDOM.render(Tree(TEST_TREE_INTERFACE), window.document.body);
yield setProps(tree, {
const tree = ReactDOM.render(Tree(Object.assign({}, TEST_TREE_INTERFACE, {
autoExpandDepth: 1
});
})), window.document.body);
isRenderedTree(document.body.textContent, [
"A:false",

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

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<!--
Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires.
-->
<head>
<meta charset="utf-8">
<title>Tree component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const { Simulate } = React.addons.TestUtils;
const Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
let numberOfExpands = 0;
let lastExpandedItem = null;
let numberOfCollapses = 0;
let lastCollapsedItem = null;
const tree = ReactDOM.render(Tree(Object.assign({
onExpand: item => {
lastExpandedItem = item;
numberOfExpands++;
},
onCollapse: item => {
lastCollapsedItem = item;
numberOfCollapses++;
},
}, TEST_TREE_INTERFACE)), window.document.body);
yield setState(tree, {
focused: "A"
});
is(lastExpandedItem, null);
is(lastCollapsedItem, null);
// Expand "A" via the keyboard and then let the component re-render.
Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowRight" });
yield setState(tree, {});
is(lastExpandedItem, "A", "Our onExpand callback should have been fired.");
is(numberOfExpands, 1);
// Now collapse "A" via the keyboard and then let the component re-render.
Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowLeft" });
yield setState(tree, {});
is(lastCollapsedItem, "A", "Our onCollapsed callback should have been fired.");
is(numberOfCollapses, 1);
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

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

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<!--
Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires.
-->
<head>
<meta charset="utf-8">
<title>Tree component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const { Simulate } = React.addons.TestUtils;
const Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
const tree = ReactDOM.render(Tree(Object.assign({
autoExpandDepth: 1
}, TEST_TREE_INTERFACE)), window.document.body);
yield setState(tree, {
focused: "A"
});
isRenderedTree(document.body.textContent, [
"A:true",
"-B:false",
"-C:false",
"-D:false",
"M:false",
"-N:false",
], "Should have auto-expanded one level.");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

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

@ -5,7 +5,7 @@
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { ViewHelpers } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const AUTO_EXPAND_DEPTH = 3; // depth
const AUTO_EXPAND_DEPTH = 0; // depth
/**
* An arrow that displays whether its node is expanded () or collapsed
@ -16,7 +16,7 @@ const ArrowExpander = createFactory(createClass({
shouldComponentUpdate(nextProps, nextState) {
return this.props.item !== nextProps.item
|| this.props.visible != nextProps.visible
|| this.props.visible !== nextProps.visible
|| this.props.expanded !== nextProps.expanded;
},
@ -151,19 +151,19 @@ const Tree = module.exports = createClass({
// Optional props
// A predicate function to filter out unwanted items from the tree.
filter: PropTypes.func,
// The depth to which we should automatically expand new items.
autoExpandDepth: PropTypes.number,
// A predicate that returns true if the last DFS traversal that was cached
// can be reused, false otherwise. The predicate function is passed the
// cached traversal as an array of nodes.
reuseCachedTraversal: PropTypes.func,
// Optional event handlers for when items are expanded or collapsed.
onExpand: PropTypes.func,
onCollapse: PropTypes.func,
},
getDefaultProps() {
return {
filter: item => true,
expanded: new Set(),
seen: new Set(),
focused: undefined,
@ -185,6 +185,7 @@ const Tree = module.exports = createClass({
componentDidMount() {
window.addEventListener("resize", this._updateHeight);
this._autoExpand();
this._updateHeight();
},
@ -193,16 +194,33 @@ const Tree = module.exports = createClass({
},
componentWillReceiveProps(nextProps) {
this._autoExpand();
},
_autoExpand() {
if (!this.props.autoExpandDepth) {
return;
}
// Automatically expand the first autoExpandDepth levels for new items.
for (let { item } of this._dfsFromRoots(this.props.autoExpandDepth)) {
if (!this.state.seen.has(item)) {
this.state.expanded.add(item);
this.state.seen.add(item);
// Automatically expand the first autoExpandDepth levels for new items. Do
// not use the usual DFS infrastructure because we don't want to ignore
// collapsed nodes.
const autoExpand = (item, currentDepth) => {
if (currentDepth >= this.props.autoExpandDepth ||
this.state.seen.has(item)) {
return;
}
this.state.expanded.add(item);
this.state.seen.add(item);
for (let child of this.props.getChildren(item)) {
autoExpand(item, currentDepth + 1);
}
};
for (let root of this.props.getRoots()) {
autoExpand(root, 0);
}
},
@ -280,10 +298,6 @@ const Tree = module.exports = createClass({
* Perform a pre-order depth-first search from item.
*/
_dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
if (!this.props.filter(item)) {
return traversal;
}
traversal.push({ item, depth: _depth });
if (!this.state.expanded.has(item)) {
@ -336,9 +350,17 @@ const Tree = module.exports = createClass({
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
this.state.expanded.add(item);
if (this.props.onExpand) {
this.props.onExpand(item);
}
if (expandAllChildren) {
for (let { item: child } of this._dfs(item)) {
this.state.expanded.add(child);
if (this.props.onExpand) {
this.props.onExpand(child);
}
}
}
@ -355,6 +377,11 @@ const Tree = module.exports = createClass({
*/
_onCollapse: oncePerAnimationFrame(function (item) {
this.state.expanded.delete(item);
if (this.props.onCollapse) {
this.props.onCollapse(item);
}
this.setState({
expanded: this.state.expanded,
cachedTraversal: null,

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

@ -13,7 +13,7 @@ add_task(function*() {
let hud = yield openConsole();
hud.jsterm.clearOutput(true);
hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
yield hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
hud.jsterm.execute("console.log('fooBug676722', myObj)");
let [result] = yield waitForMessages({

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

@ -14,9 +14,6 @@ function test() {
let hud = yield openConsole(tab);
let { toolbox, panel, panelWin } = yield openDebugger();
yield waitForThreadEvents(panel, "resumed");
ok(true, "Debugger resumed");
let sources = panelWin.DebuggerView.Sources;
yield panel.addBreakpoint({ actor: sources.values[0], line: 18 });
yield ensureThreadClientState(panel, "resumed");

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

@ -14,8 +14,6 @@ add_task(function*() {
yield loadTab(TEST_URI);
let hud = yield openConsole();
yield testWithoutDebuggerOpen(hud);
// Open the Debugger panel.
let debuggerPanel = yield openDebugger();
// And right after come back to the Console panel.
@ -23,13 +21,6 @@ add_task(function*() {
yield testWithDebuggerOpen(hud, debuggerPanel);
});
function* testWithoutDebuggerOpen(hud) {
let clickable = yield printFunction(hud);
let onVariablesViewOpen = hud.jsterm.once("variablesview-fetched");
synthesizeClick(clickable, hud);
return onVariablesViewOpen;
}
function* testWithDebuggerOpen(hud, debuggerPanel) {
let clickable = yield printFunction(hud);
let panelWin = debuggerPanel.panelWin;

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

@ -56,21 +56,25 @@ function addTabs(aWindow) {
}
function openConsoles() {
// open the Web Console for each of the four tabs and log a message.
let consolesOpen = 0;
for (let i = 0; i < openTabs.length; i++) {
function open(i) {
let tab = openTabs[i];
openConsole(tab).then(function(index, hud) {
ok(hud, "HUD is open for tab " + index);
openConsole(tab).then(function(hud) {
ok(hud, "HUD is open for tab " + i);
let window = hud.target.tab.linkedBrowser.contentWindow;
window.console.log("message for tab " + index);
consolesOpen++;
if (consolesOpen == 4) {
window.console.log("message for tab " + i);
if (i >= openTabs.length - 1) {
// Use executeSoon() to allow the promise to resolve.
executeSoon(closeConsoles);
}
}.bind(null, i));
else {
executeSoon(() => open(i + 1));
}
});
}
// open the Web Console for each of the four tabs and log a message.
open(0);
}
function closeConsoles() {

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

@ -19,14 +19,12 @@ function test() {
const {tab} = yield loadTab(TEST_URI);
const hud = yield openConsole(tab);
let button = content.document.querySelector("button");
// On e10s, the exception is triggered in child process
// and is ignored by test harness
if (!Services.appinfo.browserTabsRemoteAutostart) {
expectUncaughtException();
}
EventUtils.sendMouseEvent({ type: "click" }, button, content);
BrowserTestUtils.synthesizeMouseAtCenter("button", {}, gBrowser.selectedBrowser);
yield waitForMessages({
webconsole: hud,

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

@ -18,13 +18,6 @@ function test() {
gWebConsole = gJSTerm = gVariablesView = null;
});
function resumeDebugger(toolbox, panelWin, deferred) {
panelWin.gThreadClient.addOneTimeListener("resumed", () => {
ok(true, "Debugger resumed");
deferred.resolve({ toolbox: toolbox, panelWin: panelWin });
});
}
function fetchScopes(hud, toolbox, panelWin, deferred) {
panelWin.once(panelWin.EVENTS.FETCHED_SCOPES, () => {
ok(true, "Scopes were fetched");
@ -36,11 +29,6 @@ function test() {
loadTab(TEST_URI).then(() => {
openConsole().then((hud) => {
openDebugger().then(({ toolbox, panelWin }) => {
let deferred = promise.defer();
resumeDebugger(toolbox, panelWin, deferred);
return deferred.promise;
}).then(({ toolbox, panelWin }) => {
let deferred = promise.defer();
fetchScopes(hud, toolbox, panelWin, deferred);

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

@ -16,9 +16,7 @@ function test() {
const {tab} = yield loadTab(TEST_URI);
const hud = yield openConsole(tab);
let button = content.document.querySelector("#local");
ok(button, "we have the local-tests button");
EventUtils.sendMouseEvent({ type: "click" }, button, content);
BrowserTestUtils.synthesizeMouseAtCenter("#local", {}, gBrowser.selectedBrowser);
let messages = [];
[
"start",
@ -49,9 +47,7 @@ function test() {
hud.jsterm.clearOutput();
button = content.document.querySelector("#external");
ok(button, "we have the external-tests button");
EventUtils.sendMouseEvent({ type: "click" }, button, content);
BrowserTestUtils.synthesizeMouseAtCenter("#external", {}, gBrowser.selectedBrowser);
messages = [];
[
"start",

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

@ -25,7 +25,7 @@ var inputTests = [
input: "(function() { return 42; })",
output: "function ()",
printOutput: "function () { return 42; }",
inspectable: true,
suppressClick: true
},
// 2 - named function
@ -33,8 +33,7 @@ var inputTests = [
input: "window.testfn1",
output: "function testfn1()",
printOutput: "function testfn1() { return 42; }",
inspectable: true,
variablesViewLabel: "testfn1()",
suppressClick: true
},
// 3 - anonymous function, but spidermonkey gives us an inferred name.
@ -42,8 +41,7 @@ var inputTests = [
input: "testobj1.testfn2",
output: "function testobj1.testfn2()",
printOutput: "function () { return 42; }",
inspectable: true,
variablesViewLabel: "testobj1.testfn2()",
suppressClick: true
},
// 4 - named function with custom display name
@ -51,8 +49,7 @@ var inputTests = [
input: "window.testfn3",
output: "function testfn3DisplayName()",
printOutput: "function testfn3() { return 42; }",
inspectable: true,
variablesViewLabel: "testfn3DisplayName()",
suppressClick: true
},
// 5 - basic array

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

@ -1518,7 +1518,10 @@ function checkOutputForInputs(hud, inputTests) {
container.addEventListener("TabOpen", entry._onTabOpen, true);
body.scrollIntoView();
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
if (!entry.suppressClick) {
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
}
if (entry.inspectable) {
info("message body tagName '" + body.tagName + "' className '" +

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

@ -4,7 +4,7 @@
const {Cc,Ci,Cu,Cr} = require("chrome");
const ObservableObject = require("devtools/client/shared/observable-object");
const promise = require("devtools/shared/deprecated-sync-thenables");
const promise = require("promise");
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);

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

@ -4,7 +4,7 @@
"use strict";
var {Ci,Cu,CC} = require("chrome");
const promise = require("devtools/shared/deprecated-sync-thenables");
const promise = require("promise");
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
const Services = require("Services");

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

@ -18,6 +18,7 @@
window.onload = function() {
SimpleTest.waitForExplicitFinish();
const { DebuggerServer } = require("devtools/server/main");
let win;
SimpleTest.registerCleanupFunction(() => {
@ -39,8 +40,6 @@
return !win.document.querySelector("#cmd_stop").hasAttribute("disabled");
}
const { DebuggerServer } = require("devtools/server/main");
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();

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

@ -621,7 +621,7 @@ ThreadActor.prototype = {
this._debuggerSourcesSeen = new Set();
update(this._options, aRequest.options || {});
this.sources.reconfigure(this._options);
this.sources.setOptions(this._options);
this.sources.on('newSource', (name, source) => {
this.onNewSource(source);
});
@ -678,10 +678,16 @@ ThreadActor.prototype = {
if (this.state == "exited") {
return { error: "wrongState" };
}
const options = aRequest.options || {};
if ('observeAsmJS' in options) {
this.dbg.allowUnobservedAsmJS = !options.observeAsmJS;
}
update(this._options, options);
update(this._options, aRequest.options || {});
// Update the global source store
this.sources.reconfigure(this._options);
this.sources.setOptions(options);
return {};
},
@ -1921,21 +1927,26 @@ ThreadActor.prototype = {
let sourceActor = this.sources.createNonSourceMappedActor(aSource);
// Set any stored breakpoints.
let bpActors = this.breakpointActorMap.findActors();
let promises = [];
// Go ahead and establish the source actors for this script, which
// fetches sourcemaps if available and sends onNewSource
// notifications.
//
// We need to use unsafeSynchronize here because if the page is being reloaded,
// this call will replace the previous set of source actors for this source
// with a new one. If the source actors have not been replaced by the time
// we try to reset the breakpoints below, their location objects will still
// point to the old set of source actors, which point to different scripts.
this.unsafeSynchronize(this.sources.createSourceActors(aSource));
let sourceActorsCreated = this.sources.createSourceActors(aSource);
// Set any stored breakpoints.
let promises = [];
if (bpActors.length) {
// We need to use unsafeSynchronize here because if the page is being reloaded,
// this call will replace the previous set of source actors for this source
// with a new one. If the source actors have not been replaced by the time
// we try to reset the breakpoints below, their location objects will still
// point to the old set of source actors, which point to different
// scripts.
this.unsafeSynchronize(sourceActorsCreated);
}
for (let _actor of this.breakpointActorMap.findActors()) {
for (let _actor of bpActors) {
// XXX bug 1142115: We do async work in here, so we need to create a fresh
// binding because for/of does not yet do that in SpiderMonkey.
let actor = _actor;

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

@ -57,16 +57,22 @@ TabSources.prototype = {
/**
* Update preferences and clear out existing sources
*/
reconfigure: function(options) {
setOptions: function(options) {
let shouldReset = false;
if ('useSourceMaps' in options) {
shouldReset = true;
this._useSourceMaps = options.useSourceMaps;
}
if ('autoBlackBox' in options) {
shouldReset = true;
this._autoBlackBox = options.autoBlackBox;
}
this.reset();
if (shouldReset) {
this.reset();
}
},
/**
@ -392,7 +398,8 @@ TabSources.prototype = {
let result = this._fetchSourceMap(sourceMapURL, aSource.url);
// The promises in `_sourceMaps` must be the exact same instances
// as returned by `_fetchSourceMap` for `clearSourceMapCache` to work.
// as returned by `_fetchSourceMap` for `clearSourceMapCache` to
// work.
this._sourceMaps.set(aSource, result);
return result;
},

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

@ -66,6 +66,7 @@ module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebu
const dbg = new Debugger();
EventEmitter.decorate(dbg);
dbg.allowUnobservedAsmJS = true;
dbg.uncaughtExceptionHook = reportDebuggerHookException;
dbg.onNewGlobalObject = function(global) {

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

@ -298,7 +298,6 @@ DebuggerClient.requester = function (aPacketSkeleton,
histogram.add(+new Date - startTime);
}
}, "DebuggerClient.requester request callback"));
}, "DebuggerClient.requester");
};
@ -340,8 +339,8 @@ DebuggerClient.prototype = {
connect: function (aOnConnected) {
this.emit("connect");
// Also emit the event on the |DebuggerServer| object (not on
// the instance), so it's possible to track all instances.
// Also emit the event on the |DebuggerClient| object (not on the instance),
// so it's possible to track all instances.
events.emit(DebuggerClient, "connect", this);
this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
@ -1437,6 +1436,9 @@ WorkerClient.prototype = {
_onClose: function () {
this.removeListener("close", this._onClose);
if (this.thread) {
this.client.unregisterClient(this.thread);
}
this.client.unregisterClient(this);
this._isClosed = true;
},
@ -2169,12 +2171,20 @@ ThreadClient.prototype = {
*/
_onThreadState: function (aPacket) {
this._state = ThreadStateTypes[aPacket.type];
// The debugger UI may not be initialized yet so we want to keep
// the packet around so it knows what to pause state to display
// when it's initialized
this._lastPausePacket = aPacket.type === 'resumed' ? null : aPacket;
this._clearFrames();
this._clearPauseGrips();
aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
},
getLastPausePacket: function() {
return this._lastPausePacket;
},
/**
* Return an EnvironmentClient instance for the given environment actor form.
*/

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

@ -4,6 +4,7 @@
"use strict";
const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
const { immutableUpdate } = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
const DEFAULT_MAX_DEPTH = 4;
const DEFAULT_MAX_SIBLINGS = 15;
@ -213,3 +214,76 @@ DominatorTreeNode.partialTraversal = function (dominatorTree,
return dfs(dominatorTree.root, 0);
};
/**
* Insert more children into the given (partially complete) dominator tree.
*
* The tree is updated in an immutable and persistent manner: a new tree is
* returned, but all unmodified subtrees (which is most) are shared with the
* original tree. Only the modified nodes are re-allocated.
*
* @param {DominatorTreeNode} tree
* @param {Array<NodeId>} path
* @param {Array<DominatorTreeNode>} newChildren
* @param {Boolean} moreChildrenAvailable
*
* @returns {DominatorTreeNode}
*/
DominatorTreeNode.insert = function (tree, path, newChildren, moreChildrenAvailable) {
function insert(tree, i) {
if (tree.nodeId !== path[i]) {
return tree;
}
if (i == path.length - 1) {
return immutableUpdate(tree, {
children: (tree.children || []).concat(newChildren),
moreChildrenAvailable,
});
}
return tree.children
? immutableUpdate(tree, {
children: tree.children.map(c => insert(c, i + 1))
})
: tree;
}
return insert(tree, 0);
};
/**
* Get the new canonical node with the given `id` in `tree` that exists along
* `path`. If there is no such node along `path`, return null.
*
* This is useful if we have a reference to a now-outdated DominatorTreeNode due
* to a recent call to DominatorTreeNode.insert and want to get the up-to-date
* version. We don't have to walk the whole tree: if there is an updated version
* of the node then it *must* be along the path.
*
* @param {NodeId} id
* @param {DominatorTreeNode} tree
* @param {Array<NodeId>} path
*
* @returns {DominatorTreeNode|null}
*/
DominatorTreeNode.getNodeByIdAlongPath = function (id, tree, path) {
function find(node, i) {
if (!node || node.nodeId !== path[i]) {
return null;
}
if (node.nodeId === id) {
return node;
}
if (i === path.length - 1 || !node.children) {
return null;
}
const nextId = path[i + 1];
return find(node.children.find(c => c.nodeId === nextId), i + 1);
}
return find(tree, 0);
};

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

@ -214,6 +214,9 @@ HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
* - {Boolean} moreChildrenAvailable
* True iff there are more children available after the returned
* nodes.
* - {Array<NodeId>} path
* The path through the tree from the root to these node's parent, eg
* [root's id, child of root's id, child of child of root's id, ..., `nodeId`].
*/
HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
return this._worker.performTask("getImmediatelyDominated", opts);

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

@ -194,7 +194,15 @@ workerHelper.createTask(self, "getImmediatelyDominated", request => {
return node;
});
const path = [];
let id = nodeId;
do {
path.push(id);
id = dominatorTree.getImmediateDominator(id);
} while (id !== null);
path.reverse();
const moreChildrenAvailable = childIds.length > end;
return { nodes, moreChildrenAvailable };
return { nodes, moreChildrenAvailable, path };
});

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

@ -325,3 +325,53 @@ function assertLabelAndShallowSize(breakdown, givenDescription, expectedShallowS
dumpn("Actual label: " + JSON.stringify(visitor.label(), null, 4));
assertStructurallyEquivalent(visitor.label(), expectedLabel);
}
// Counter for mock DominatorTreeNode ids.
let TEST_NODE_ID_COUNTER = 0;
/**
* Create a mock DominatorTreeNode for testing, with sane defaults. Override any
* property by providing it on `opts`. Optionally pass child nodes as well.
*
* @param {Object} opts
* @param {Array<DominatorTreeNode>?} children
*
* @returns {DominatorTreeNode}
*/
function makeTestDominatorTreeNode(opts, children) {
const nodeId = TEST_NODE_ID_COUNTER++;
const node = Object.assign({
nodeId,
label: undefined,
shallowSize: 1,
retainedSize: (children || []).reduce((size, c) => size + c.retainedSize, 1),
parentId: undefined,
children,
moreChildrenAvailable: true,
}, opts);
if (children && children.length) {
children.map(c => c.parentId = node.nodeId);
}
return node;
}
/**
* Insert `newChildren` into the given dominator `tree` as specified by the
* `path` from the root to the node the `newChildren` should be inserted
* beneath. Assert that the resulting tree matches `expected`.
*/
function assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected) {
dumpn("Inserting new children into a dominator tree:");
dumpn("Dominator tree: " + JSON.stringify(tree, null, 2));
dumpn("Path: " + JSON.stringify(path, null, 2));
dumpn("New children: " + JSON.stringify(newChildren, null, 2));
dumpn("Expected resulting tree: " + JSON.stringify(expected, null, 2));
const actual = DominatorTreeNode.insert(tree, path, newChildren, moreChildrenAvailable);
dumpn("Actual resulting tree: " + JSON.stringify(actual, null, 2));
assertStructurallyEquivalent(actual, expected);
}

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

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can find the node with the given id along the specified path.
const node3000 = makeTestDominatorTreeNode({ nodeId: 3000 });
const node2000 = makeTestDominatorTreeNode({ nodeId: 2000 }, [
makeTestDominatorTreeNode({}),
node3000,
makeTestDominatorTreeNode({}),
]);
const node1000 = makeTestDominatorTreeNode({ nodeId: 1000 }, [
makeTestDominatorTreeNode({}),
node2000,
makeTestDominatorTreeNode({}),
]);
const tree = node1000;
const path = [1000, 2000, 3000];
const tests = [
{ id: 1000, expected: node1000 },
{ id: 2000, expected: node2000 },
{ id: 3000, expected: node3000 },
];
function run_test() {
for (let { id, expected } of tests) {
const actual = DominatorTreeNode.getNodeByIdAlongPath(id, tree, path);
equal(actual, expected, `We should have got the node with id = ${id}`);
}
equal(null,
DominatorTreeNode.getNodeByIdAlongPath(999999999999, tree, path),
"null is returned for nodes that are not even in the tree");
const lastNodeId = tree.children[tree.children.length - 1].nodeId;
equal(null,
DominatorTreeNode.getNodeByIdAlongPath(lastNodeId, tree, path),
"null is returned for nodes that are not along the path");
}

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

@ -0,0 +1,112 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can insert new children into an existing DominatorTreeNode tree.
const tree = makeTestDominatorTreeNode({ nodeId: 1000 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({ nodeId: 2000 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({ nodeId: 3000 }),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [1000, 2000, 3000];
const newChildren = [
makeTestDominatorTreeNode({ parentId: 3000 }),
makeTestDominatorTreeNode({ parentId: 3000 }),
];
const moreChildrenAvailable = false;
const expected = {
nodeId: 1000,
parentId: undefined,
label: undefined,
shallowSize: 1,
retainedSize: 7,
children: [
{
nodeId: 0,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 1000,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 2000,
label: undefined,
shallowSize: 1,
retainedSize: 4,
parentId: 1000,
children: [
{
nodeId: 1,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
children: [
{
nodeId: 7,
parentId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 8,
parentId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
moreChildrenAvailable: true,
children: undefined,
},
],
moreChildrenAvailable: false
},
{
nodeId: 3,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
moreChildrenAvailable: true,
children: undefined,
},
],
moreChildrenAvailable: true
},
{
nodeId: 5,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 1000,
moreChildrenAvailable: true,
children: undefined,
}
],
moreChildrenAvailable: true
};
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

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

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test attempting to insert new children into an existing DominatorTreeNode
// tree with a bad path.
const tree = makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [111111, 222222, 333333];
const newChildren = [
makeTestDominatorTreeNode({ parentId: 333333 }),
makeTestDominatorTreeNode({ parentId: 333333 }),
];
const moreChildrenAvailable = false;
const expected = tree;
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

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

@ -0,0 +1,117 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test inserting new children into an existing DominatorTreeNode at the root.
const tree = makeTestDominatorTreeNode({ nodeId: 666 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [666];
const newChildren = [
makeTestDominatorTreeNode({
nodeId: 777,
parentId: 666
}),
makeTestDominatorTreeNode({
nodeId: 888,
parentId: 666
}),
];
const moreChildrenAvailable = false;
const expected = {
nodeId: 666,
label: undefined,
parentId: undefined,
shallowSize: 1,
retainedSize: 7,
children: [
{
nodeId: 0,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 4,
label: undefined,
shallowSize: 1,
retainedSize: 4,
parentId: 666,
children: [
{
nodeId: 1,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 2,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 3,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
}
],
moreChildrenAvailable: true
},
{
nodeId: 5,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 777,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 888,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
}
],
moreChildrenAvailable: false
}
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

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

@ -41,6 +41,8 @@ add_task(function* () {
ok(Array.isArray(response.nodes));
ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
ok(response.moreChildrenAvailable);
equal(response.path.length, 1);
equal(response.path[0], partialTree.nodeId);
// Next, test getting a subset of children available.
const secondResponse = yield client.getImmediatelyDominated({
@ -54,6 +56,8 @@ add_task(function* () {
ok(Array.isArray(secondResponse.nodes));
ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
ok(!secondResponse.moreChildrenAvailable);
equal(secondResponse.path.length, 1);
equal(secondResponse.path[0], partialTree.nodeId);
client.destroy();
});

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

@ -33,6 +33,10 @@ support-files =
[test_DominatorTree_03.js]
[test_DominatorTree_04.js]
[test_DominatorTree_05.js]
[test_DominatorTreeNode_getNodeByIdAlongPath_01.js]
[test_DominatorTreeNode_insert_01.js]
[test_DominatorTreeNode_insert_02.js]
[test_DominatorTreeNode_insert_03.js]
[test_DominatorTreeNode_LabelAndShallowSize_01.js]
[test_DominatorTreeNode_LabelAndShallowSize_02.js]
[test_DominatorTreeNode_LabelAndShallowSize_03.js]

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

@ -22,8 +22,8 @@
"use strict";
const { utils: Cu } = Components;
const { Promise: promise } =
Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {});
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const promise = require("promise");
const { EventEmitter } =
Cu.import("resource://devtools/shared/event-emitter.js", {});
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});

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

@ -50,8 +50,13 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothPinCode>
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.mLength);
for (uint8_t i = 0; i < aParam.mLength; ++i) {
auto length = aParam.mLength;
if (length > MOZ_ARRAY_LENGTH(aParam.mPinCode)) {
length = MOZ_ARRAY_LENGTH(aParam.mPinCode);
}
WriteParam(aMsg, length);
for (uint8_t i = 0; i < length; ++i) {
WriteParam(aMsg, aParam.mPinCode[i]);
}
}
@ -260,11 +265,16 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
static void Write(Message* aMsg, const paramType& aParam)
{
auto length = aParam.mLength;
if (length > MOZ_ARRAY_LENGTH(aParam.mValue)) {
length = MOZ_ARRAY_LENGTH(aParam.mValue);
}
WriteParam(aMsg, aParam.mHandle);
WriteParam(aMsg, aParam.mOffset);
WriteParam(aMsg, aParam.mLength);
WriteParam(aMsg, length);
WriteParam(aMsg, aParam.mAuthReq);
for (uint16_t i = 0; i < aParam.mLength; i++) {
for (uint16_t i = 0; i < length; i++) {
WriteParam(aMsg, aParam.mValue[i]);
}
}
@ -278,6 +288,10 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
return false;
}
if (aResult->mLength > MOZ_ARRAY_LENGTH(aResult->mValue)) {
return false;
}
for (uint16_t i = 0; i < aResult->mLength; i++) {
if (!ReadParam(aMsg, aIter, &(aResult->mValue[i]))) {
return false;

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

@ -6,8 +6,6 @@
#include "ServiceWorkerManager.h"
#include "mozIApplication.h"
#include "nsIAppsService.h"
#include "nsIConsoleService.h"
#include "nsIDOMEventTarget.h"
#include "nsIDocument.h"
@ -4466,11 +4464,11 @@ ServiceWorkerManager::PropagateRemoveAll()
}
void
ServiceWorkerManager::RemoveAllRegistrations(PrincipalOriginAttributes* aParams)
ServiceWorkerManager::RemoveAllRegistrations(OriginAttributesPattern* aPattern)
{
AssertIsOnMainThread();
MOZ_ASSERT(aParams);
MOZ_ASSERT(aPattern);
for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
@ -4484,49 +4482,14 @@ ServiceWorkerManager::RemoveAllRegistrations(PrincipalOriginAttributes* aParams)
MOZ_ASSERT(reg);
MOZ_ASSERT(reg->mPrincipal);
bool equals = false;
if (aParams->mInBrowser) {
// When we do a system wide "clear cookies and stored data" on B2G we
// get the "clear-origin-data" notification with the System app appID
// and the browserOnly flag set to true. Web sites registering a
// service worker on B2G have a principal with the following
// information: web site origin + System app appId + inBrowser=1 So
// we need to check if the service worker registration info contains
// the System app appID and the enabled inBrowser flag and in that
// case remove it from the registry.
OriginAttributes attrs =
mozilla::BasePrincipal::Cast(reg->mPrincipal)->OriginAttributesRef();
equals = attrs == *aParams;
} else {
// If we get the "clear-origin-data" notification because of an app
// uninstallation, we need to check the full principal to get the
// match in the service workers registry. If we find a match, we
// unregister the worker.
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
if (NS_WARN_IF(!appsService)) {
continue;
}
nsCOMPtr<mozIApplication> app;
appsService->GetAppByLocalId(aParams->mAppId, getter_AddRefs(app));
if (NS_WARN_IF(!app)) {
continue;
}
nsCOMPtr<nsIPrincipal> principal;
app->GetPrincipal(getter_AddRefs(principal));
if (NS_WARN_IF(!principal)) {
continue;
}
reg->mPrincipal->Equals(principal, &equals);
bool matches =
aPattern->Matches(BasePrincipal::Cast(reg->mPrincipal)->OriginAttributesRef());
if (!matches) {
continue;
}
if (equals) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->ForceUnregister(data, reg);
}
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->ForceUnregister(data, reg);
}
}
}
@ -4699,10 +4662,10 @@ ServiceWorkerManager::Observe(nsISupports* aSubject,
if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) {
MOZ_ASSERT(XRE_IsParentProcess());
PrincipalOriginAttributes attrs;
MOZ_ALWAYS_TRUE(attrs.Init(nsAutoString(aData)));
OriginAttributesPattern pattern;
MOZ_ALWAYS_TRUE(pattern.Init(nsAutoString(aData)));
RemoveAllRegistrations(&attrs);
RemoveAllRegistrations(&pattern);
return NS_OK;
}
@ -5052,7 +5015,7 @@ ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal,
}
PrincipalOriginAttributes attrs =
mozilla::BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
// Then trigger an update to fire asynchronously now.
PropagateSoftUpdate(attrs, NS_ConvertUTF8toUTF16(aScope));

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

@ -640,10 +640,9 @@ private:
void
RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration);
// Removes all service worker registrations that matches the given
// mozIApplicationClearPrivateDataParams.
// Removes all service worker registrations that matches the given pattern.
void
RemoveAllRegistrations(PrincipalOriginAttributes* aParams);
RemoveAllRegistrations(OriginAttributesPattern* aPattern);
RefPtr<ServiceWorkerManagerChild> mActor;

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

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Test app for bug 1169249</title>
<script type='application/javascript;version=1.7'>
function ok(aCondition, aMessage) {
if (aCondition) {
alert('OK: ' + aMessage);
} else {
alert('KO: ' + aMessage);
}
}
function ready() {
alert('READY');
}
function registerServiceWorker() {
return new Promise((resolve, reject) => {
navigator.serviceWorker.ready.then(() => {
ready();
resolve();
});
navigator.serviceWorker.register('../empty.js', {scope: '.'})
.then(registration => {
ok(true, 'service worker registered');
})
.catch(reject);
});
}
function AddIframe() {
return new Promise((resolve, reject) => {
let iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', 'true');
iframe.setAttribute('src', "http://test/chrome/dom/workers/test/serviceworkers/serviceworker.html");
document.body.appendChild(iframe);
iframe.addEventListener("mozbrowserloadend", resolve);
});
}
function runTests() {
return Promise.resolve()
.then(AddIframe)
.then(registerServiceWorker)
.then(ready)
}
</script>
</head>
<body onload='runTests()'>
</body>
</html>

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

@ -0,0 +1,5 @@
{
"name": "App",
"launch_path": "/index.html",
"description": "Test app for bug 1169249"
}

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

@ -0,0 +1 @@
Content-Type: application/manifest+json

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

@ -3,7 +3,10 @@ skip-if = buildapp == 'b2g' || os == 'android'
support-files =
app/*
app2/*
app3/*
chrome_helpers.js
empty.js
serviceworker.html
serviceworkerinfo_iframe.html
serviceworkermanager_iframe.html
serviceworkerregistrationinfo_iframe.html
@ -12,6 +15,7 @@ support-files =
[test_aboutserviceworkers.html]
skip-if = true #bug 1193319
[test_clear_origin_data.html]
[test_app_installation.html]
[test_privateBrowsing.html]
[test_serviceworkerinfo.xul]

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

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
navigator.serviceWorker.register("worker.js");
</script>
</head>
<body>
This is a test page.
</body>
<html>

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

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id={1233644}
-->
<head>
<title>Test for Bug {1233644}</title>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/chrome-harness.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1233644}">Mozilla Bug {1233644}</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager");
SimpleTest.waitForExplicitFinish();
const gOrigin = 'http://test/chrome/dom/workers/test/serviceworkers/app3';
const appManifestURL = gOrigin + '/manifest.webapp';
let gApp;
addLoadEvent(go);
function setup() {
info('Setting up');
return new Promise((resolve, reject) => {
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.pushPrefEnv({'set': [
['dom.mozBrowserFramesEnabled', true],
['dom.serviceWorkers.exemptFromPerDomainMax', true],
['dom.serviceWorkers.enabled', true],
['dom.serviceWorkers.testing.enabled', true],
['dom.serviceWorkers.interception.enabled', true],
]}, () => {
SpecialPowers.pushPermissions([
{ 'type': 'webapps-manage', 'allow': 1, 'context': document },
{ 'type': 'browser', 'allow': 1, 'context': document },
{ 'type': 'embed-apps', 'allow': 1, 'context': document }
], () => {
SpecialPowers.autoConfirmAppInstall(() => {
SpecialPowers.autoConfirmAppUninstall(resolve);
});
});
});
});
}
function installApp() {
return new Promise((resolve, reject) => {
let req = navigator.mozApps.install(appManifestURL);
req.onsuccess = function() {
gApp = req.result;
is(req.result.manifestURL, appManifestURL, 'app installed');
if (req.result.installState == 'installed') {
is(req.result.installState, 'installed', 'app downloaded');
resolve()
} else {
req.result.ondownloadapplied = function() {
is(req.result.installState, 'installed', 'app downloaded');
resolve();
}
}
}
req.onerror = reject;
});
}
function pushPermission() {
return new Promise((resolve, reject) => {
SpecialPowers.pushPermissions([
{'type': 'browser', 'allow': 1, 'context': {manifestURL: appManifestURL}}], resolve);
});
}
function checkSwRegistration() {
return new Promise((resolve, reject) => {
let registrations = gServiceWorkerManager.getAllRegistrations();
is(registrations.length, 0, "All registrations should be removed.");
resolve();
});
}
function launchApp() {
if (!gApp) {
ok(false, 'No test application to launch');
return Promise.reject();
}
return new Promise((resolve, reject) => {
let iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', 'true');
iframe.setAttribute('mozapp', gApp.manifestURL);
iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) {
let message = e.detail.message;
if (/OK/.exec(message)) {
ok(true, "Message from app: " + message);
} else if (/READY/.exec(message)) {
ok(true, 'Message from app: ' + message);
resolve();
}
}, false);
let domParent = document.getElementById('container');
domParent.appendChild(iframe);
SpecialPowers.wrap(iframe.contentWindow).location =
gOrigin + gApp.manifest.launch_path;
});
}
function uninstallApp() {
return new Promise((resolve, reject) => {
if (!gApp) {
return reject();
}
let req = navigator.mozApps.mgmt.uninstall(gApp);
req.onsuccess = resolve;
req.onerror = reject;
});
}
function go() {
setup()
.then(installApp)
.then(pushPermission)
.then(launchApp)
.then(uninstallApp)
.then(checkSwRegistration)
.then(SimpleTest.finish)
.catch((e) => {
ok(false, 'Unexpected error ' + e);
SimpleTest.finish();
});
}
</script>
</pre>
<div id="container"></div>
</body>
</html>

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

@ -8,7 +8,7 @@ android {
defaultConfig {
targetSdkVersion 22
minSdkVersion 14
minSdkVersion 15
// Used by Robolectric based tests; see TestRunner.
buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
}

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

@ -8,7 +8,7 @@ android {
defaultConfig {
targetSdkVersion 22
minSdkVersion 14
minSdkVersion 15
applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
testApplicationId 'org.mozilla.roboexample.test'
testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'

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

@ -576,7 +576,8 @@ pref("apz.fling_curve_function_y1", "0.0");
pref("apz.fling_curve_function_x2", "0.80");
pref("apz.fling_curve_function_y2", "1.0");
pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
pref("apz.fling_friction", "0.0019");
pref("apz.fling_friction", "0.004");
pref("apz.fling_stopped_threshold", "0.1");
pref("apz.max_velocity_inches_per_ms", "0.07");
#endif

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

@ -34,13 +34,13 @@ ANDROID_NDK_VERSION_32BIT="r10c"
# Build B2GDroid
ac_add_options --enable-application=mobile/android/b2gdroid
ac_add_options --with-android-min-sdk=14
ac_add_options --with-android-min-sdk=15
ac_add_options --with-android-ndk="$topsrcdir/android-ndk-r10e"
ac_add_options --with-android-sdk="$topsrcdir/android-sdk-linux"
ac_add_options --with-android-gnu-compiler-version=4.9
ac_add_options --with-android-version=14
ac_add_options --with-android-version=9
ac_add_options --with-system-zlib
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}

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