зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
99af1f8707
2
CLOBBER
2
CLOBBER
|
@ -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
Двоичные данные
browser/base/content/abouthome/apps.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 961 B |
Двоичные данные
browser/base/content/abouthome/apps@2x.png
Двоичные данные
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}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче