зеркало из 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
|
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||||
# don't change CLOBBER for WebIDL changes any more.
|
# 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
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"git": {
|
"git": {
|
||||||
"git_revision": "81c021654c657f6995e5e70ef02ee067d4bfedbd",
|
"git_revision": "676237f80cf72182500356fabc49365d3471c0e6",
|
||||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||||
"branch": ""
|
"branch": ""
|
||||||
},
|
},
|
||||||
"revision": "e651f5b571034436b7bc8eb8099033dff5623a20",
|
"revision": "8e1054115b6145d46f0f2a8fa1b7ef8434b19f95",
|
||||||
"repo_path": "integration/gaia-central"
|
"repo_path": "integration/gaia-central"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<!--
|
<!--
|
||||||
B2G repositories for all targets
|
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="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
<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");
|
content: url("chrome://browser/content/abouthome/history.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
#apps::before {
|
|
||||||
content: url("chrome://browser/content/abouthome/apps.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
#addons::before {
|
#addons::before {
|
||||||
content: url("chrome://browser/content/abouthome/addons.png");
|
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");
|
content: url("chrome://browser/content/abouthome/history@2x.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
#apps::before {
|
|
||||||
content: url("chrome://browser/content/abouthome/apps@2x.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
#addons::before {
|
#addons::before {
|
||||||
content: url("chrome://browser/content/abouthome/addons@2x.png");
|
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="downloads">&abouthome.downloadsButton.label;</button>
|
||||||
<button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
|
<button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
|
||||||
<button class="launchButton" id="history">&abouthome.historyButton.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="addons">&abouthome.addonsButton.label;</button>
|
||||||
<button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
|
<button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
|
||||||
#ifdef XP_WIN
|
#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;"
|
accesskey="&addons.accesskey;"
|
||||||
key="key_openAddons"
|
key="key_openAddons"
|
||||||
command="Tools:Addons"/>
|
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 -->
|
<!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
|
||||||
<menuitem id="sync-setup"
|
<menuitem id="sync-setup"
|
||||||
|
|
|
@ -1815,3 +1815,44 @@ var BookmarkingUI = {
|
||||||
Ci.nsINavBookmarkObserver
|
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();
|
gBrowser.tabContainer.updateVisibility();
|
||||||
|
|
||||||
BookmarkingUI.init();
|
BookmarkingUI.init();
|
||||||
|
AutoShowBookmarksToolbar.init();
|
||||||
|
|
||||||
gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
|
gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
|
||||||
|
|
||||||
|
@ -1486,6 +1487,7 @@ var gBrowserInit = {
|
||||||
IndexedDBPromptHelper.uninit();
|
IndexedDBPromptHelper.uninit();
|
||||||
LightweightThemeListener.uninit();
|
LightweightThemeListener.uninit();
|
||||||
PanelUI.uninit();
|
PanelUI.uninit();
|
||||||
|
AutoShowBookmarksToolbar.uninit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final window teardown, do this last.
|
// Final window teardown, do this last.
|
||||||
|
|
|
@ -195,10 +195,6 @@ var AboutHomeListener = {
|
||||||
sendAsyncMessage("AboutHome:History");
|
sendAsyncMessage("AboutHome:History");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "apps":
|
|
||||||
sendAsyncMessage("AboutHome:Apps");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "addons":
|
case "addons":
|
||||||
sendAsyncMessage("AboutHome:Addons");
|
sendAsyncMessage("AboutHome:Addons");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3101,6 +3101,9 @@
|
||||||
// Map from tabs to STATE_* (below).
|
// Map from tabs to STATE_* (below).
|
||||||
tabState: new Map(),
|
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
|
// Keep an exact list of content processes (tabParent) in which
|
||||||
// we're actively suppressing the display port. This gives a robust
|
// we're actively suppressing the display port. This gives a robust
|
||||||
// way to make sure we don't forget to un-suppress.
|
// way to make sure we don't forget to un-suppress.
|
||||||
|
@ -3410,7 +3413,7 @@
|
||||||
let tab = this.tabbrowser.getTabForBrowser(browser);
|
let tab = this.tabbrowser.getTabForBrowser(browser);
|
||||||
this.setTabState(tab, this.STATE_LOADED);
|
this.setTabState(tab, this.STATE_LOADED);
|
||||||
|
|
||||||
this.finishTabSwitch();
|
this.maybeFinishTabSwitch();
|
||||||
|
|
||||||
if (this.loadingTab === tab) {
|
if (this.loadingTab === tab) {
|
||||||
clearTimeout(this.loadTimer);
|
clearTimeout(this.loadTimer);
|
||||||
|
@ -3424,7 +3427,7 @@
|
||||||
// around.
|
// around.
|
||||||
onPaint: function() {
|
onPaint: function() {
|
||||||
this.maybeVisibleTabs.clear();
|
this.maybeVisibleTabs.clear();
|
||||||
this.finishTabSwitch();
|
this.maybeFinishTabSwitch();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Called when we're done clearing the layers for a tab.
|
// 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.cancel("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
|
||||||
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
|
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
|
||||||
this.addMarker("AsyncTabSwitch:Start");
|
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.
|
// 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.
|
// The changes will be visible after the next refresh driver tick + composite.
|
||||||
let event = new CustomEvent("TabSwitched", {
|
let event = new CustomEvent("TabSwitched", {
|
||||||
|
@ -3540,6 +3551,7 @@
|
||||||
this.log("DEBUG: tab switch time = " + time);
|
this.log("DEBUG: tab switch time = " + time);
|
||||||
this.addMarker("AsyncTabSwitch:Finish");
|
this.addMarker("AsyncTabSwitch:Finish");
|
||||||
}
|
}
|
||||||
|
this.switchInProgress = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3556,7 +3568,7 @@
|
||||||
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
|
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
|
||||||
this.addMarker("AsyncTabSwitch:SpinnerHidden");
|
this.addMarker("AsyncTabSwitch:SpinnerHidden");
|
||||||
// we do not get a onPaint after displaying the spinner
|
// we do not get a onPaint after displaying the spinner
|
||||||
this.finishTabSwitch();
|
this.maybeFinishTabSwitch();
|
||||||
},
|
},
|
||||||
|
|
||||||
addMarker: function(marker) {
|
addMarker: function(marker) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ browser.jar:
|
||||||
content/browser/abouthome/downloads.png (content/abouthome/downloads.png)
|
content/browser/abouthome/downloads.png (content/abouthome/downloads.png)
|
||||||
content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png)
|
content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png)
|
||||||
content/browser/abouthome/history.png (content/abouthome/history.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/addons.png (content/abouthome/addons.png)
|
||||||
content/browser/abouthome/sync.png (content/abouthome/sync.png)
|
content/browser/abouthome/sync.png (content/abouthome/sync.png)
|
||||||
content/browser/abouthome/settings.png (content/abouthome/settings.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/downloads@2x.png (content/abouthome/downloads@2x.png)
|
||||||
content/browser/abouthome/bookmarks@2x.png (content/abouthome/bookmarks@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/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/addons@2x.png (content/abouthome/addons@2x.png)
|
||||||
content/browser/abouthome/sync@2x.png (content/abouthome/sync@2x.png)
|
content/browser/abouthome/sync@2x.png (content/abouthome/sync@2x.png)
|
||||||
content/browser/abouthome/settings@2x.png (content/abouthome/settings@2x.png)
|
content/browser/abouthome/settings@2x.png (content/abouthome/settings@2x.png)
|
||||||
|
|
|
@ -138,7 +138,7 @@ var gSearchPane = {
|
||||||
gSearchPane.onRestoreDefaults();
|
gSearchPane.onRestoreDefaults();
|
||||||
break;
|
break;
|
||||||
case "removeEngineButton":
|
case "removeEngineButton":
|
||||||
gSearchPane.remove();
|
Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -180,7 +180,11 @@ var gSearchPane = {
|
||||||
gEngineView.invalidate();
|
gEngineView.invalidate();
|
||||||
break;
|
break;
|
||||||
case "engine-removed":
|
case "engine-removed":
|
||||||
|
gSearchPane.remove(aEngine);
|
||||||
|
break;
|
||||||
case "engine-current":
|
case "engine-current":
|
||||||
|
gSearchPane.buildDefaultEngineDropDown();
|
||||||
|
break;
|
||||||
case "engine-default":
|
case "engine-default":
|
||||||
// Not relevant
|
// Not relevant
|
||||||
break;
|
break;
|
||||||
|
@ -228,9 +232,8 @@ var gSearchPane = {
|
||||||
document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
|
document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: function() {
|
remove: function(aEngine) {
|
||||||
gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
|
let index = gEngineView._engineStore.removeEngine(aEngine);
|
||||||
let index = gEngineView.selectedIndex;
|
|
||||||
gEngineView.rowCountChanged(index, -1);
|
gEngineView.rowCountChanged(index, -1);
|
||||||
gEngineView.invalidate();
|
gEngineView.invalidate();
|
||||||
gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
|
gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
|
||||||
|
@ -369,16 +372,18 @@ EngineStore.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
removeEngine: function ES_removeEngine(aEngine) {
|
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)
|
if (index == -1)
|
||||||
throw new Error("invalid engine?");
|
throw new Error("invalid engine?");
|
||||||
|
|
||||||
this._engines.splice(index, 1);
|
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.showRestoreDefaults(true);
|
||||||
gSearchPane.buildDefaultEngineDropDown();
|
gSearchPane.buildDefaultEngineDropDown();
|
||||||
|
return index;
|
||||||
},
|
},
|
||||||
|
|
||||||
restoreDefaultEngines: function ES_restoreDefaultEngines() {
|
restoreDefaultEngines: function ES_restoreDefaultEngines() {
|
||||||
|
|
|
@ -31,9 +31,5 @@
|
||||||
preferences/options item on about:home on Linux and OS X -->
|
preferences/options item on about:home on Linux and OS X -->
|
||||||
<!ENTITY abouthome.preferencesButtonUnix.label "Preferences">
|
<!ENTITY abouthome.preferencesButtonUnix.label "Preferences">
|
||||||
<!ENTITY abouthome.addonsButton.label "Add-ons">
|
<!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.downloadsButton.label "Downloads">
|
||||||
<!ENTITY abouthome.syncButton.label "&syncBrand.shortName.label;">
|
<!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.label "Add-ons">
|
||||||
<!ENTITY addons.accesskey "A">
|
<!ENTITY addons.accesskey "A">
|
||||||
<!ENTITY addons.commandkey "A">
|
<!ENTITY addons.commandkey "A">
|
||||||
<!ENTITY webapps.label "Apps">
|
|
||||||
<!ENTITY webapps.accesskey "p">
|
|
||||||
|
|
||||||
<!ENTITY webDeveloperMenu.label "Web Developer">
|
<!ENTITY webDeveloperMenu.label "Web Developer">
|
||||||
<!ENTITY webDeveloperMenu.accesskey "W">
|
<!ENTITY webDeveloperMenu.accesskey "W">
|
||||||
|
|
|
@ -95,7 +95,6 @@ var AboutHome = {
|
||||||
"AboutHome:Downloads",
|
"AboutHome:Downloads",
|
||||||
"AboutHome:Bookmarks",
|
"AboutHome:Bookmarks",
|
||||||
"AboutHome:History",
|
"AboutHome:History",
|
||||||
"AboutHome:Apps",
|
|
||||||
"AboutHome:Addons",
|
"AboutHome:Addons",
|
||||||
"AboutHome:Sync",
|
"AboutHome:Sync",
|
||||||
"AboutHome:Settings",
|
"AboutHome:Settings",
|
||||||
|
@ -134,10 +133,6 @@ var AboutHome = {
|
||||||
window.PlacesCommandHook.showPlacesOrganizer("History");
|
window.PlacesCommandHook.showPlacesOrganizer("History");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "AboutHome:Apps":
|
|
||||||
window.BrowserOpenApps();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "AboutHome:Addons":
|
case "AboutHome:Addons":
|
||||||
window.BrowserOpenAddonsMgr();
|
window.BrowserOpenAddonsMgr();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -20,6 +20,23 @@ function update(state = initialState, action, emitChange) {
|
||||||
emitChange('source', action.source);
|
emitChange('source', action.source);
|
||||||
return mergeIn(state, ['sources', action.source.actor], 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:
|
case constants.SELECT_SOURCE:
|
||||||
emitChange('source-selected', action.source);
|
emitChange('source-selected', action.source);
|
||||||
return state.merge({
|
return state.merge({
|
||||||
|
|
|
@ -224,7 +224,7 @@ var DebuggerController = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebuggerView.initialize();
|
yield DebuggerView.initialize();
|
||||||
this._startup = true;
|
this._startup = true;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -259,26 +259,23 @@ var DebuggerController = {
|
||||||
* A promise that is resolved when the debugger finishes connecting.
|
* A promise that is resolved when the debugger finishes connecting.
|
||||||
*/
|
*/
|
||||||
connect: Task.async(function*() {
|
connect: Task.async(function*() {
|
||||||
if (this._connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = this._target;
|
let target = this._target;
|
||||||
let { client, form: { chromeDebugger, actor } } = target;
|
|
||||||
|
let { client } = target;
|
||||||
target.on("close", this._onTabDetached);
|
target.on("close", this._onTabDetached);
|
||||||
target.on("navigate", this._onNavigate);
|
target.on("navigate", this._onNavigate);
|
||||||
target.on("will-navigate", this._onWillNavigate);
|
target.on("will-navigate", this._onWillNavigate);
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.activeThread = this._toolbox.threadClient;
|
||||||
|
|
||||||
if (target.isAddon) {
|
// Disable asm.js so that we can set breakpoints and other things
|
||||||
yield this._startAddonDebugging(actor);
|
// on asm.js scripts
|
||||||
} else if (!target.isTabActor) {
|
yield this.reconfigureThread({ observeAsmJS: true });
|
||||||
// Some actors like AddonActor or RootActor for chrome debugging
|
yield this.connectThread();
|
||||||
// do not support attach/detach and can be used directly
|
|
||||||
yield this._startChromeDebugging(chromeDebugger);
|
// We need to call this to sync the state of the resume
|
||||||
} else {
|
// button in the toolbar with the state of the thread.
|
||||||
yield this._startDebuggingTab();
|
this.ThreadState._update();
|
||||||
}
|
|
||||||
|
|
||||||
this._hideUnsupportedFeatures();
|
this._hideUnsupportedFeatures();
|
||||||
}),
|
}),
|
||||||
|
@ -307,7 +304,20 @@ var DebuggerController = {
|
||||||
// emit individual `newSource` notifications, which trigger
|
// emit individual `newSource` notifications, which trigger
|
||||||
// separate actions, so this won't do anything other than force
|
// separate actions, so this won't do anything other than force
|
||||||
// the server to traverse sources.
|
// 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
|
* Detach and reattach to the thread actor with useSourceMaps true, blow
|
||||||
* away old sources and get them again.
|
* away old sources and get them again.
|
||||||
*/
|
*/
|
||||||
reconfigureThread: function({ useSourceMaps, autoBlackBox }) {
|
reconfigureThread: function(opts) {
|
||||||
this.activeThread.reconfigure({
|
const deferred = promise.defer();
|
||||||
useSourceMaps: useSourceMaps,
|
this.activeThread.reconfigure(
|
||||||
autoBlackBox: autoBlackBox
|
opts,
|
||||||
}, aResponse => {
|
aResponse => {
|
||||||
if (aResponse.error) {
|
if (aResponse.error) {
|
||||||
let msg = "Couldn't reconfigure thread: " + aResponse.message;
|
deferred.reject(aResponse.error);
|
||||||
Cu.reportError(msg);
|
return;
|
||||||
dumpn(msg);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the view and fetch all the sources again.
|
if (('useSourceMaps' in opts) || ('autoBlackBox' in opts)) {
|
||||||
DebuggerView.handleTabNavigation();
|
// Reset the view and fetch all the sources again.
|
||||||
this.dispatch(actions.unload());
|
DebuggerView.handleTabNavigation();
|
||||||
this.dispatch(actions.loadSources());
|
this.dispatch(actions.unload());
|
||||||
|
this.dispatch(actions.loadSources());
|
||||||
|
|
||||||
// Update the stack frame list.
|
// Update the stack frame list.
|
||||||
if (this.activeThread.paused) {
|
if (this.activeThread.paused) {
|
||||||
this.activeThread._clearFrames();
|
this.activeThread._clearFrames();
|
||||||
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
|
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deferred.resolve();
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
waitForSourcesLoaded: function() {
|
waitForSourcesLoaded: function() {
|
||||||
|
@ -606,8 +536,6 @@ ThreadState.prototype = {
|
||||||
dumpn("ThreadState is connecting...");
|
dumpn("ThreadState is connecting...");
|
||||||
this.activeThread.addListener("paused", this._update);
|
this.activeThread.addListener("paused", this._update);
|
||||||
this.activeThread.addListener("resumed", 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 the UI after a thread state change.
|
||||||
*/
|
*/
|
||||||
_update: function(aEvent, aPacket) {
|
_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 (aEvent == "paused") {
|
||||||
if (aPacket.why.type == "interrupted" &&
|
if (aPacket.why.type == "interrupted" &&
|
||||||
!this.interruptedByResumeButton) {
|
this.interruptedByResumeButton) {
|
||||||
return;
|
// 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) {
|
} else if (aPacket.why.type == "breakpointConditionThrown" && aPacket.why.message) {
|
||||||
let where = aPacket.frame.where;
|
let where = aPacket.frame.where;
|
||||||
let aLocation = {
|
let aLocation = {
|
||||||
|
@ -663,10 +590,6 @@ ThreadState.prototype = {
|
||||||
this.activeThread.state,
|
this.activeThread.state,
|
||||||
aPacket ? aPacket.frame : false
|
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.
|
* A promise that is resolved when the view finishes initializing.
|
||||||
*/
|
*/
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
if (this._hasStartup) {
|
if (this._startup) {
|
||||||
return;
|
return this._startup;
|
||||||
}
|
}
|
||||||
this._hasStartup = true;
|
const deferred = promise.defer();
|
||||||
|
this._startup = deferred.promise;
|
||||||
|
|
||||||
this._initializePanes();
|
this._initializePanes();
|
||||||
this.Toolbar.initialize();
|
this.Toolbar.initialize();
|
||||||
|
@ -78,7 +79,8 @@ var DebuggerView = {
|
||||||
this.EventListeners.initialize();
|
this.EventListeners.initialize();
|
||||||
this.GlobalSearch.initialize();
|
this.GlobalSearch.initialize();
|
||||||
this._initializeVariablesView();
|
this._initializeVariablesView();
|
||||||
this._initializeEditor();
|
|
||||||
|
this._initializeEditor(deferred.resolve);
|
||||||
this._editorSource = {};
|
this._editorSource = {};
|
||||||
|
|
||||||
document.title = L10N.getStr("DebuggerWindowTitle");
|
document.title = L10N.getStr("DebuggerWindowTitle");
|
||||||
|
@ -108,6 +110,8 @@ var DebuggerView = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,7 +283,7 @@ var DebuggerView = {
|
||||||
* @param function aCallback
|
* @param function aCallback
|
||||||
* Called after the editor finishes initializing.
|
* Called after the editor finishes initializing.
|
||||||
*/
|
*/
|
||||||
_initializeEditor: function() {
|
_initializeEditor: function(callback) {
|
||||||
dumpn("Initializing the DebuggerView editor");
|
dumpn("Initializing the DebuggerView editor");
|
||||||
|
|
||||||
let extraKeys = {};
|
let extraKeys = {};
|
||||||
|
@ -311,6 +315,7 @@ var DebuggerView = {
|
||||||
this.editor.appendTo(document.getElementById("editor")).then(() => {
|
this.editor.appendTo(document.getElementById("editor")).then(() => {
|
||||||
this.editor.extend(DebuggerEditor);
|
this.editor.extend(DebuggerEditor);
|
||||||
this._loadingText = L10N.getStr("loadingText");
|
this._loadingText = L10N.getStr("loadingText");
|
||||||
|
callback();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editor.on("gutterClick", (ev, line, button) => {
|
this.editor.on("gutterClick", (ev, line, button) => {
|
||||||
|
|
|
@ -22,9 +22,6 @@ function DebuggerPanel(iframeWindow, toolbox) {
|
||||||
this._controller._toolbox = this._toolbox;
|
this._controller._toolbox = this._toolbox;
|
||||||
|
|
||||||
this.handleHostChanged = this.handleHostChanged.bind(this);
|
this.handleHostChanged = this.handleHostChanged.bind(this);
|
||||||
this.highlightWhenPaused = this.highlightWhenPaused.bind(this);
|
|
||||||
this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
|
|
||||||
|
|
||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +52,6 @@ DebuggerPanel.prototype = {
|
||||||
.then(() => this._controller.connect())
|
.then(() => this._controller.connect())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._toolbox.on("host-changed", this.handleHostChanged);
|
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
|
// Add keys from this document's keyset to the toolbox, so they
|
||||||
// can work when the split console is focused.
|
// can work when the split console is focused.
|
||||||
let keysToClone = ["resumeKey", "resumeKey2", "stepOverKey",
|
let keysToClone = ["resumeKey", "resumeKey2", "stepOverKey",
|
||||||
|
@ -87,9 +82,6 @@ DebuggerPanel.prototype = {
|
||||||
return this._destroyer;
|
return this._destroyer;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.target.off("thread-paused", this.highlightWhenPaused);
|
|
||||||
this.target.off("thread-resumed", this.unhighlightWhenResumed);
|
|
||||||
|
|
||||||
if (!this.target.isRemote) {
|
if (!this.target.isRemote) {
|
||||||
this.target.tab.removeEventListener('TabSelect', this);
|
this.target.tab.removeEventListener('TabSelect', this);
|
||||||
}
|
}
|
||||||
|
@ -125,18 +117,6 @@ DebuggerPanel.prototype = {
|
||||||
this._view.handleHostChanged(this._toolbox.hostType);
|
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
|
// nsIDOMEventListener API
|
||||||
|
|
||||||
handleEvent: function(aEvent) {
|
handleEvent: function(aEvent) {
|
||||||
|
|
|
@ -165,6 +165,7 @@ skip-if = e10s || os == "mac" || e10s # Bug 895426
|
||||||
skip-if = e10s # TODO
|
skip-if = e10s # TODO
|
||||||
[browser_dbg_break-on-dom-event-03.js]
|
[browser_dbg_break-on-dom-event-03.js]
|
||||||
skip-if = e10s # TODO
|
skip-if = e10s # TODO
|
||||||
|
[browser_dbg_break-unselected.js]
|
||||||
[browser_dbg_breakpoints-actual-location.js]
|
[browser_dbg_breakpoints-actual-location.js]
|
||||||
[browser_dbg_breakpoints-actual-location2.js]
|
[browser_dbg_breakpoints-actual-location2.js]
|
||||||
[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.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 [tab,, panel] = yield initDebugger(EXAMPLE_URL + TAB_URL);
|
||||||
let win = panel.panelWin;
|
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({
|
yield panel.addBreakpoint({
|
||||||
actor: getSourceActor(win.DebuggerView.Sources, EXAMPLE_URL + SCRIPT_URL),
|
actor: getSourceActor(win.DebuggerView.Sources, EXAMPLE_URL + SCRIPT_URL),
|
||||||
line: 6
|
line: 6
|
||||||
|
|
|
@ -93,14 +93,8 @@ function testPause() {
|
||||||
}).then(testResume);
|
}).then(testResume);
|
||||||
});
|
});
|
||||||
|
|
||||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
|
||||||
gDebugger.document.getElementById("resume"),
|
|
||||||
gDebugger);
|
|
||||||
|
|
||||||
// Evaluate a script to fully pause the debugger
|
// Evaluate a script to fully pause the debugger
|
||||||
once(gDebugger.gClient, "willInterrupt").then(() => {
|
evalInTab(gTab, "debugger;");
|
||||||
evalInTab(gTab, "1+1;");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testResume() {
|
function testResume() {
|
||||||
|
|
|
@ -24,8 +24,8 @@ function test() {
|
||||||
|
|
||||||
function testPause() {
|
function testPause() {
|
||||||
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
|
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
|
||||||
ok(gTarget.isThreadPaused,
|
ok(gDebugger.gThreadClient.paused,
|
||||||
"target.isThreadPaused has been updated to true.");
|
"threadClient.paused has been updated to true.");
|
||||||
|
|
||||||
gToolbox.once("inspector-selected").then(inspector => {
|
gToolbox.once("inspector-selected").then(inspector => {
|
||||||
inspector.once("inspector-updated").then(testNotificationIsUp1);
|
inspector.once("inspector-updated").then(testNotificationIsUp1);
|
||||||
|
@ -77,8 +77,8 @@ function testNotificationIsUp2() {
|
||||||
|
|
||||||
function testResume() {
|
function testResume() {
|
||||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||||
ok(!gTarget.isThreadPaused,
|
ok(!gDebugger.gThreadClient.paused,
|
||||||
"target.isThreadPaused has been updated to false.");
|
"threadClient.paused has been updated to false.");
|
||||||
|
|
||||||
let notificationBox = gToolbox.getNotificationBox();
|
let notificationBox = gToolbox.getNotificationBox();
|
||||||
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
|
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
|
||||||
|
|
|
@ -36,7 +36,7 @@ add_task(function* () {
|
||||||
is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
|
is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
|
||||||
"Correct set of tools supported by worker");
|
"Correct set of tools supported by worker");
|
||||||
|
|
||||||
yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
|
yield toolbox.destroy();
|
||||||
terminateWorkerInTab(tab, WORKER_URL);
|
terminateWorkerInTab(tab, WORKER_URL);
|
||||||
yield waitForWorkerClose(workerClient);
|
yield waitForWorkerClose(workerClient);
|
||||||
yield close(client);
|
yield close(client);
|
||||||
|
|
|
@ -526,13 +526,8 @@ function initDebugger(aTarget, aWindow) {
|
||||||
let debuggerPanel = aToolbox.getCurrentPanel();
|
let debuggerPanel = aToolbox.getCurrentPanel();
|
||||||
let panelWin = debuggerPanel.panelWin;
|
let panelWin = debuggerPanel.panelWin;
|
||||||
|
|
||||||
// Wait for the initial resume...
|
prepareDebugger(debuggerPanel);
|
||||||
panelWin.gClient.addOneTimeListener("resumed", () => {
|
deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
|
||||||
info("Debugger client resumed successfully.");
|
|
||||||
|
|
||||||
prepareDebugger(debuggerPanel);
|
|
||||||
deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
|
@ -595,7 +590,6 @@ AddonDebugger.prototype = {
|
||||||
this.debuggerPanel = toolbox.getCurrentPanel();
|
this.debuggerPanel = toolbox.getCurrentPanel();
|
||||||
|
|
||||||
// Wait for the initial resume...
|
// Wait for the initial resume...
|
||||||
yield waitForClientEvents(this.debuggerPanel, "resumed");
|
|
||||||
yield prepareDebugger(this.debuggerPanel);
|
yield prepareDebugger(this.debuggerPanel);
|
||||||
yield this._attachConsole();
|
yield this._attachConsole();
|
||||||
}),
|
}),
|
||||||
|
@ -1032,7 +1026,7 @@ function connect(client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(client) {
|
function close(client) {
|
||||||
info("Closing client.\n");
|
info("Waiting for client to close.\n");
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
client.close(() => {
|
client.close(() => {
|
||||||
resolve();
|
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']
|
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
|
'attach-thread.js',
|
||||||
'gDevTools.jsm',
|
'gDevTools.jsm',
|
||||||
'selection.js',
|
'selection.js',
|
||||||
'sidebar.js',
|
'sidebar.js',
|
||||||
|
|
|
@ -117,9 +117,6 @@ exports.TargetFactory = {
|
||||||
function TabTarget(tab) {
|
function TabTarget(tab) {
|
||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
this.destroy = this.destroy.bind(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;
|
this.activeTab = this.activeConsole = null;
|
||||||
// Only real tabs need initialization here. Placeholder objects for remote
|
// Only real tabs need initialization here. Placeholder objects for remote
|
||||||
// targets will be initialized after a makeRemote method call.
|
// targets will be initialized after a makeRemote method call.
|
||||||
|
@ -362,10 +359,6 @@ TabTarget.prototype = {
|
||||||
return !this.window;
|
return !this.window;
|
||||||
},
|
},
|
||||||
|
|
||||||
get isThreadPaused() {
|
|
||||||
return !!this._isThreadPaused;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds remote protocol capabilities to the target, so that it can be used
|
* Adds remote protocol capabilities to the target, so that it can be used
|
||||||
* for tools that support the Remote Debugging Protocol even for local
|
* 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.
|
* Target is not alive anymore.
|
||||||
*/
|
*/
|
||||||
|
@ -562,11 +541,6 @@ TabTarget.prototype = {
|
||||||
// Before taking any action, notify listeners that destruction is imminent.
|
// Before taking any action, notify listeners that destruction is imminent.
|
||||||
this.emit("close");
|
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) {
|
if (this._tab) {
|
||||||
this._teardownListeners();
|
this._teardownListeners();
|
||||||
}
|
}
|
||||||
|
@ -612,6 +586,7 @@ TabTarget.prototype = {
|
||||||
} else {
|
} else {
|
||||||
promiseTargets.delete(this._form);
|
promiseTargets.delete(this._form);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeTab = null;
|
this.activeTab = null;
|
||||||
this.activeConsole = null;
|
this.activeConsole = null;
|
||||||
this._client = null;
|
this._client = null;
|
||||||
|
@ -718,8 +693,6 @@ function WorkerTarget(workerClient) {
|
||||||
* requiring only minimal changes to the rest of the frontend.
|
* requiring only minimal changes to the rest of the frontend.
|
||||||
*/
|
*/
|
||||||
WorkerTarget.prototype = {
|
WorkerTarget.prototype = {
|
||||||
destroy: function () {},
|
|
||||||
|
|
||||||
get isRemote() {
|
get isRemote() {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,40 +2,25 @@
|
||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
function test()
|
function test() {
|
||||||
{
|
|
||||||
gBrowser.selectedTab = gBrowser.addTab();
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
|
|
||||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
|
const onLoad = Task.async(function *(evt) {
|
||||||
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
|
gBrowser.selectedBrowser.removeEventListener("load", onLoad);
|
||||||
gDevTools.showToolbox(target).then(testReady);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
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)
|
const toolbox2 = yield gDevTools.showToolbox(toolbox.target, toolbox.toolId);
|
||||||
{
|
|
||||||
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) {
|
|
||||||
is(toolbox2, toolbox, "same toolbox");
|
is(toolbox2, toolbox, "same toolbox");
|
||||||
cleanup(toolbox);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup(toolbox)
|
yield toolbox.destroy();
|
||||||
{
|
|
||||||
toolbox.destroy().then(function() {
|
|
||||||
gBrowser.removeCurrentTab();
|
gBrowser.removeCurrentTab();
|
||||||
finish();
|
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 Telemetry = require("devtools/client/shared/telemetry");
|
||||||
var HUDService = require("devtools/client/webconsole/hudservice");
|
var HUDService = require("devtools/client/webconsole/hudservice");
|
||||||
var viewSource = require("devtools/client/shared/view-source");
|
var viewSource = require("devtools/client/shared/view-source");
|
||||||
|
var { attachThread, detachThread } = require("./attach-thread");
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
|
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
|
||||||
|
@ -251,6 +252,10 @@ Toolbox.prototype = {
|
||||||
return this._target;
|
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
|
* Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
|
||||||
* tab. See HostType for more details.
|
* tab. See HostType for more details.
|
||||||
|
@ -348,12 +353,18 @@ Toolbox.prototype = {
|
||||||
let iframe = yield this._host.create();
|
let iframe = yield this._host.create();
|
||||||
let domReady = promise.defer();
|
let domReady = promise.defer();
|
||||||
|
|
||||||
// Load the toolbox-level actor fronts and utilities now
|
|
||||||
yield this._target.makeRemote();
|
|
||||||
iframe.setAttribute("src", this._URL);
|
iframe.setAttribute("src", this._URL);
|
||||||
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
|
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
|
||||||
let domHelper = new DOMHelpers(iframe.contentWindow);
|
let domHelper = new DOMHelpers(iframe.contentWindow);
|
||||||
domHelper.onceDOMReady(() => domReady.resolve());
|
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;
|
yield domReady.promise;
|
||||||
|
|
||||||
|
@ -1952,6 +1963,10 @@ Toolbox.prototype = {
|
||||||
// Destroy the profiler connection
|
// Destroy the profiler connection
|
||||||
outstanding.push(this.destroyPerformance());
|
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.
|
// We need to grab a reference to win before this._host is destroyed.
|
||||||
let win = this.frame.ownerGlobal;
|
let win = this.frame.ownerGlobal;
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ InspectorPanel.prototype = {
|
||||||
let notificationBox = this._toolbox.getNotificationBox();
|
let notificationBox = this._toolbox.getNotificationBox();
|
||||||
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
|
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
|
||||||
if (!notification && this._toolbox.currentToolId == "inspector" &&
|
if (!notification && this._toolbox.currentToolId == "inspector" &&
|
||||||
this.target.isThreadPaused) {
|
this._toolbox.threadClient.paused) {
|
||||||
let message = strings.GetStringFromName("debuggerPausedWarning.message");
|
let message = strings.GetStringFromName("debuggerPausedWarning.message");
|
||||||
notificationBox.appendNotification(message,
|
notificationBox.appendNotification(message,
|
||||||
"inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
|
"inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
|
||||||
|
@ -182,7 +182,7 @@ InspectorPanel.prototype = {
|
||||||
notificationBox.removeNotification(notification);
|
notificationBox.removeNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification && !this.target.isThreadPaused) {
|
if (notification && !this._toolbox.threadClient.paused) {
|
||||||
notificationBox.removeNotification(notification);
|
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
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
DIRS += [
|
||||||
|
'models',
|
||||||
|
]
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
'rules.js',
|
'rules.js',
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,6 +17,7 @@ const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||||
const {OutputParser} = require("devtools/client/shared/output-parser");
|
const {OutputParser} = require("devtools/client/shared/output-parser");
|
||||||
const {PrefObserver, PREF_ORIG_SOURCES} =
|
const {PrefObserver, PREF_ORIG_SOURCES} =
|
||||||
require("devtools/client/styleeditor/utils");
|
require("devtools/client/styleeditor/utils");
|
||||||
|
const {Rule} = require("devtools/client/inspector/rules/models/rule");
|
||||||
const {
|
const {
|
||||||
createChild,
|
createChild,
|
||||||
appendText,
|
appendText,
|
||||||
|
@ -26,7 +27,6 @@ const {
|
||||||
throttle
|
throttle
|
||||||
} = require("devtools/client/styleinspector/utils");
|
} = require("devtools/client/styleinspector/utils");
|
||||||
const {
|
const {
|
||||||
escapeCSSComment,
|
|
||||||
parseDeclarations,
|
parseDeclarations,
|
||||||
parseSingleValue,
|
parseSingleValue,
|
||||||
parsePseudoClassesAndAttributes,
|
parsePseudoClassesAndAttributes,
|
||||||
|
@ -34,6 +34,7 @@ const {
|
||||||
SELECTOR_ELEMENT,
|
SELECTOR_ELEMENT,
|
||||||
SELECTOR_PSEUDO_CLASS
|
SELECTOR_PSEUDO_CLASS
|
||||||
} = require("devtools/client/shared/css-parsing-utils");
|
} = require("devtools/client/shared/css-parsing-utils");
|
||||||
|
|
||||||
loader.lazyRequireGetter(this, "overlays",
|
loader.lazyRequireGetter(this, "overlays",
|
||||||
"devtools/client/styleinspector/style-inspector-overlays");
|
"devtools/client/styleinspector/style-inspector-overlays");
|
||||||
loader.lazyRequireGetter(this, "EventEmitter",
|
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.
|
* View hierarchy mostly follows the model hierarchy.
|
||||||
*
|
*
|
||||||
|
@ -4043,10 +3223,6 @@ XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
|
||||||
.getService(Ci.nsIClipboardHelper);
|
.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() {
|
XPCOMUtils.defineLazyGetter(this, "_strings", function() {
|
||||||
return Services.strings.createBundle(
|
return Services.strings.createBundle(
|
||||||
"chrome://devtools-shared/locale/styleinspector.properties");
|
"chrome://devtools-shared/locale/styleinspector.properties");
|
||||||
|
|
|
@ -54,6 +54,18 @@ add_task(function*() {
|
||||||
info("Entering a property value");
|
info("Entering a property value");
|
||||||
editor.input.value = "red";
|
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");
|
info("Escaping out of the field");
|
||||||
onRuleViewChanged = view.once("ruleview-changed");
|
onRuleViewChanged = view.once("ruleview-changed");
|
||||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
|
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
|
||||||
|
|
|
@ -72,6 +72,8 @@ const TEST_DATA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
add_task(function*() {
|
add_task(function*() {
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
info("Starting the test with the pref set to true before toolbox is opened");
|
info("Starting the test with the pref set to true before toolbox is opened");
|
||||||
yield setUserAgentStylesPref(true);
|
yield setUserAgentStylesPref(true);
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ var Converter = Class({
|
||||||
var str = {};
|
var str = {};
|
||||||
var bytesRead = is.readString(aCount, str);
|
var bytesRead = is.readString(aCount, str);
|
||||||
if (!bytesRead) {
|
if (!bytesRead) {
|
||||||
throw new Error("Stream converter failed to read the input stream!");
|
break;
|
||||||
}
|
}
|
||||||
aCount -= bytesRead;
|
aCount -= bytesRead;
|
||||||
this.data += str.value;
|
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.
|
# corresponding URL as a js file in the Debugger tool.
|
||||||
# DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used.
|
# DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used.
|
||||||
toolbox.viewJsSourceInDebugger.label=Open File in Debugger
|
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_pixelRatio "Pixel Ratio">
|
||||||
<!ENTITY simulator_tv_data "TV Simulation">
|
<!ENTITY simulator_tv_data "TV Simulation">
|
||||||
<!ENTITY simulator_tv_data_open "Config Data">
|
<!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_06.html]
|
||||||
[test_tree_07.html]
|
[test_tree_07.html]
|
||||||
[test_tree_08.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",
|
renderItem: (x, depth, focused, arrow) => "-".repeat(depth) + x + ":" + focused + "\n",
|
||||||
getRoots: () => ["A", "M"],
|
getRoots: () => ["A", "M"],
|
||||||
getKey: x => "key-" + x,
|
getKey: x => "key-" + x,
|
||||||
itemHeight: 1
|
itemHeight: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// All tests are asynchronous.
|
// All tests are asynchronous.
|
||||||
|
|
|
@ -19,11 +19,9 @@ window.onload = Task.async(function* () {
|
||||||
let React = browserRequire("devtools/client/shared/vendor/react");
|
let React = browserRequire("devtools/client/shared/vendor/react");
|
||||||
let Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
|
let Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
|
||||||
|
|
||||||
const tree = ReactDOM.render(Tree(TEST_TREE_INTERFACE), window.document.body);
|
const tree = ReactDOM.render(Tree(Object.assign({}, TEST_TREE_INTERFACE, {
|
||||||
|
|
||||||
yield setProps(tree, {
|
|
||||||
autoExpandDepth: 1
|
autoExpandDepth: 1
|
||||||
});
|
})), window.document.body);
|
||||||
|
|
||||||
isRenderedTree(document.body.textContent, [
|
isRenderedTree(document.body.textContent, [
|
||||||
"A:false",
|
"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 { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||||
const { ViewHelpers } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
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
|
* An arrow that displays whether its node is expanded (▼) or collapsed
|
||||||
|
@ -16,7 +16,7 @@ const ArrowExpander = createFactory(createClass({
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
return this.props.item !== nextProps.item
|
return this.props.item !== nextProps.item
|
||||||
|| this.props.visible != nextProps.visible
|
|| this.props.visible !== nextProps.visible
|
||||||
|| this.props.expanded !== nextProps.expanded;
|
|| this.props.expanded !== nextProps.expanded;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -151,19 +151,19 @@ const Tree = module.exports = createClass({
|
||||||
|
|
||||||
// Optional props
|
// 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.
|
// The depth to which we should automatically expand new items.
|
||||||
autoExpandDepth: PropTypes.number,
|
autoExpandDepth: PropTypes.number,
|
||||||
// A predicate that returns true if the last DFS traversal that was cached
|
// A predicate that returns true if the last DFS traversal that was cached
|
||||||
// can be reused, false otherwise. The predicate function is passed the
|
// can be reused, false otherwise. The predicate function is passed the
|
||||||
// cached traversal as an array of nodes.
|
// cached traversal as an array of nodes.
|
||||||
reuseCachedTraversal: PropTypes.func,
|
reuseCachedTraversal: PropTypes.func,
|
||||||
|
// Optional event handlers for when items are expanded or collapsed.
|
||||||
|
onExpand: PropTypes.func,
|
||||||
|
onCollapse: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
filter: item => true,
|
|
||||||
expanded: new Set(),
|
expanded: new Set(),
|
||||||
seen: new Set(),
|
seen: new Set(),
|
||||||
focused: undefined,
|
focused: undefined,
|
||||||
|
@ -185,6 +185,7 @@ const Tree = module.exports = createClass({
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener("resize", this._updateHeight);
|
window.addEventListener("resize", this._updateHeight);
|
||||||
|
this._autoExpand();
|
||||||
this._updateHeight();
|
this._updateHeight();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -193,16 +194,33 @@ const Tree = module.exports = createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this._autoExpand();
|
||||||
|
},
|
||||||
|
|
||||||
|
_autoExpand() {
|
||||||
if (!this.props.autoExpandDepth) {
|
if (!this.props.autoExpandDepth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically expand the first autoExpandDepth levels for new items.
|
// Automatically expand the first autoExpandDepth levels for new items. Do
|
||||||
for (let { item } of this._dfsFromRoots(this.props.autoExpandDepth)) {
|
// not use the usual DFS infrastructure because we don't want to ignore
|
||||||
if (!this.state.seen.has(item)) {
|
// collapsed nodes.
|
||||||
this.state.expanded.add(item);
|
const autoExpand = (item, currentDepth) => {
|
||||||
this.state.seen.add(item);
|
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.
|
* Perform a pre-order depth-first search from item.
|
||||||
*/
|
*/
|
||||||
_dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
|
_dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
|
||||||
if (!this.props.filter(item)) {
|
|
||||||
return traversal;
|
|
||||||
}
|
|
||||||
|
|
||||||
traversal.push({ item, depth: _depth });
|
traversal.push({ item, depth: _depth });
|
||||||
|
|
||||||
if (!this.state.expanded.has(item)) {
|
if (!this.state.expanded.has(item)) {
|
||||||
|
@ -336,9 +350,17 @@ const Tree = module.exports = createClass({
|
||||||
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
|
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
|
||||||
this.state.expanded.add(item);
|
this.state.expanded.add(item);
|
||||||
|
|
||||||
|
if (this.props.onExpand) {
|
||||||
|
this.props.onExpand(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (expandAllChildren) {
|
if (expandAllChildren) {
|
||||||
for (let { item: child } of this._dfs(item)) {
|
for (let { item: child } of this._dfs(item)) {
|
||||||
this.state.expanded.add(child);
|
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) {
|
_onCollapse: oncePerAnimationFrame(function (item) {
|
||||||
this.state.expanded.delete(item);
|
this.state.expanded.delete(item);
|
||||||
|
|
||||||
|
if (this.props.onCollapse) {
|
||||||
|
this.props.onCollapse(item);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: this.state.expanded,
|
expanded: this.state.expanded,
|
||||||
cachedTraversal: null,
|
cachedTraversal: null,
|
||||||
|
|
|
@ -13,7 +13,7 @@ add_task(function*() {
|
||||||
let hud = yield openConsole();
|
let hud = yield openConsole();
|
||||||
hud.jsterm.clearOutput(true);
|
hud.jsterm.clearOutput(true);
|
||||||
|
|
||||||
hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
|
yield hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
|
||||||
hud.jsterm.execute("console.log('fooBug676722', myObj)");
|
hud.jsterm.execute("console.log('fooBug676722', myObj)");
|
||||||
|
|
||||||
let [result] = yield waitForMessages({
|
let [result] = yield waitForMessages({
|
||||||
|
|
|
@ -14,9 +14,6 @@ function test() {
|
||||||
let hud = yield openConsole(tab);
|
let hud = yield openConsole(tab);
|
||||||
let { toolbox, panel, panelWin } = yield openDebugger();
|
let { toolbox, panel, panelWin } = yield openDebugger();
|
||||||
|
|
||||||
yield waitForThreadEvents(panel, "resumed");
|
|
||||||
ok(true, "Debugger resumed");
|
|
||||||
|
|
||||||
let sources = panelWin.DebuggerView.Sources;
|
let sources = panelWin.DebuggerView.Sources;
|
||||||
yield panel.addBreakpoint({ actor: sources.values[0], line: 18 });
|
yield panel.addBreakpoint({ actor: sources.values[0], line: 18 });
|
||||||
yield ensureThreadClientState(panel, "resumed");
|
yield ensureThreadClientState(panel, "resumed");
|
||||||
|
|
|
@ -14,8 +14,6 @@ add_task(function*() {
|
||||||
yield loadTab(TEST_URI);
|
yield loadTab(TEST_URI);
|
||||||
let hud = yield openConsole();
|
let hud = yield openConsole();
|
||||||
|
|
||||||
yield testWithoutDebuggerOpen(hud);
|
|
||||||
|
|
||||||
// Open the Debugger panel.
|
// Open the Debugger panel.
|
||||||
let debuggerPanel = yield openDebugger();
|
let debuggerPanel = yield openDebugger();
|
||||||
// And right after come back to the Console panel.
|
// And right after come back to the Console panel.
|
||||||
|
@ -23,13 +21,6 @@ add_task(function*() {
|
||||||
yield testWithDebuggerOpen(hud, debuggerPanel);
|
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) {
|
function* testWithDebuggerOpen(hud, debuggerPanel) {
|
||||||
let clickable = yield printFunction(hud);
|
let clickable = yield printFunction(hud);
|
||||||
let panelWin = debuggerPanel.panelWin;
|
let panelWin = debuggerPanel.panelWin;
|
||||||
|
|
|
@ -56,21 +56,25 @@ function addTabs(aWindow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function openConsoles() {
|
function openConsoles() {
|
||||||
// open the Web Console for each of the four tabs and log a message.
|
function open(i) {
|
||||||
let consolesOpen = 0;
|
|
||||||
for (let i = 0; i < openTabs.length; i++) {
|
|
||||||
let tab = openTabs[i];
|
let tab = openTabs[i];
|
||||||
openConsole(tab).then(function(index, hud) {
|
openConsole(tab).then(function(hud) {
|
||||||
ok(hud, "HUD is open for tab " + index);
|
ok(hud, "HUD is open for tab " + i);
|
||||||
let window = hud.target.tab.linkedBrowser.contentWindow;
|
let window = hud.target.tab.linkedBrowser.contentWindow;
|
||||||
window.console.log("message for tab " + index);
|
window.console.log("message for tab " + i);
|
||||||
consolesOpen++;
|
|
||||||
if (consolesOpen == 4) {
|
if (i >= openTabs.length - 1) {
|
||||||
// Use executeSoon() to allow the promise to resolve.
|
// Use executeSoon() to allow the promise to resolve.
|
||||||
executeSoon(closeConsoles);
|
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() {
|
function closeConsoles() {
|
||||||
|
|
|
@ -19,14 +19,12 @@ function test() {
|
||||||
const {tab} = yield loadTab(TEST_URI);
|
const {tab} = yield loadTab(TEST_URI);
|
||||||
const hud = yield openConsole(tab);
|
const hud = yield openConsole(tab);
|
||||||
|
|
||||||
let button = content.document.querySelector("button");
|
|
||||||
|
|
||||||
// On e10s, the exception is triggered in child process
|
// On e10s, the exception is triggered in child process
|
||||||
// and is ignored by test harness
|
// and is ignored by test harness
|
||||||
if (!Services.appinfo.browserTabsRemoteAutostart) {
|
if (!Services.appinfo.browserTabsRemoteAutostart) {
|
||||||
expectUncaughtException();
|
expectUncaughtException();
|
||||||
}
|
}
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, button, content);
|
BrowserTestUtils.synthesizeMouseAtCenter("button", {}, gBrowser.selectedBrowser);
|
||||||
|
|
||||||
yield waitForMessages({
|
yield waitForMessages({
|
||||||
webconsole: hud,
|
webconsole: hud,
|
||||||
|
|
|
@ -18,13 +18,6 @@ function test() {
|
||||||
gWebConsole = gJSTerm = gVariablesView = null;
|
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) {
|
function fetchScopes(hud, toolbox, panelWin, deferred) {
|
||||||
panelWin.once(panelWin.EVENTS.FETCHED_SCOPES, () => {
|
panelWin.once(panelWin.EVENTS.FETCHED_SCOPES, () => {
|
||||||
ok(true, "Scopes were fetched");
|
ok(true, "Scopes were fetched");
|
||||||
|
@ -36,11 +29,6 @@ function test() {
|
||||||
loadTab(TEST_URI).then(() => {
|
loadTab(TEST_URI).then(() => {
|
||||||
openConsole().then((hud) => {
|
openConsole().then((hud) => {
|
||||||
openDebugger().then(({ toolbox, panelWin }) => {
|
openDebugger().then(({ toolbox, panelWin }) => {
|
||||||
let deferred = promise.defer();
|
|
||||||
resumeDebugger(toolbox, panelWin, deferred);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}).then(({ toolbox, panelWin }) => {
|
|
||||||
let deferred = promise.defer();
|
let deferred = promise.defer();
|
||||||
fetchScopes(hud, toolbox, panelWin, deferred);
|
fetchScopes(hud, toolbox, panelWin, deferred);
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,7 @@ function test() {
|
||||||
const {tab} = yield loadTab(TEST_URI);
|
const {tab} = yield loadTab(TEST_URI);
|
||||||
const hud = yield openConsole(tab);
|
const hud = yield openConsole(tab);
|
||||||
|
|
||||||
let button = content.document.querySelector("#local");
|
BrowserTestUtils.synthesizeMouseAtCenter("#local", {}, gBrowser.selectedBrowser);
|
||||||
ok(button, "we have the local-tests button");
|
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, button, content);
|
|
||||||
let messages = [];
|
let messages = [];
|
||||||
[
|
[
|
||||||
"start",
|
"start",
|
||||||
|
@ -49,9 +47,7 @@ function test() {
|
||||||
|
|
||||||
hud.jsterm.clearOutput();
|
hud.jsterm.clearOutput();
|
||||||
|
|
||||||
button = content.document.querySelector("#external");
|
BrowserTestUtils.synthesizeMouseAtCenter("#external", {}, gBrowser.selectedBrowser);
|
||||||
ok(button, "we have the external-tests button");
|
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, button, content);
|
|
||||||
messages = [];
|
messages = [];
|
||||||
[
|
[
|
||||||
"start",
|
"start",
|
||||||
|
|
|
@ -25,7 +25,7 @@ var inputTests = [
|
||||||
input: "(function() { return 42; })",
|
input: "(function() { return 42; })",
|
||||||
output: "function ()",
|
output: "function ()",
|
||||||
printOutput: "function () { return 42; }",
|
printOutput: "function () { return 42; }",
|
||||||
inspectable: true,
|
suppressClick: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// 2 - named function
|
// 2 - named function
|
||||||
|
@ -33,8 +33,7 @@ var inputTests = [
|
||||||
input: "window.testfn1",
|
input: "window.testfn1",
|
||||||
output: "function testfn1()",
|
output: "function testfn1()",
|
||||||
printOutput: "function testfn1() { return 42; }",
|
printOutput: "function testfn1() { return 42; }",
|
||||||
inspectable: true,
|
suppressClick: true
|
||||||
variablesViewLabel: "testfn1()",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 3 - anonymous function, but spidermonkey gives us an inferred name.
|
// 3 - anonymous function, but spidermonkey gives us an inferred name.
|
||||||
|
@ -42,8 +41,7 @@ var inputTests = [
|
||||||
input: "testobj1.testfn2",
|
input: "testobj1.testfn2",
|
||||||
output: "function testobj1.testfn2()",
|
output: "function testobj1.testfn2()",
|
||||||
printOutput: "function () { return 42; }",
|
printOutput: "function () { return 42; }",
|
||||||
inspectable: true,
|
suppressClick: true
|
||||||
variablesViewLabel: "testobj1.testfn2()",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 4 - named function with custom display name
|
// 4 - named function with custom display name
|
||||||
|
@ -51,8 +49,7 @@ var inputTests = [
|
||||||
input: "window.testfn3",
|
input: "window.testfn3",
|
||||||
output: "function testfn3DisplayName()",
|
output: "function testfn3DisplayName()",
|
||||||
printOutput: "function testfn3() { return 42; }",
|
printOutput: "function testfn3() { return 42; }",
|
||||||
inspectable: true,
|
suppressClick: true
|
||||||
variablesViewLabel: "testfn3DisplayName()",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 5 - basic array
|
// 5 - basic array
|
||||||
|
|
|
@ -1518,7 +1518,10 @@ function checkOutputForInputs(hud, inputTests) {
|
||||||
container.addEventListener("TabOpen", entry._onTabOpen, true);
|
container.addEventListener("TabOpen", entry._onTabOpen, true);
|
||||||
|
|
||||||
body.scrollIntoView();
|
body.scrollIntoView();
|
||||||
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
|
|
||||||
|
if (!entry.suppressClick) {
|
||||||
|
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.inspectable) {
|
if (entry.inspectable) {
|
||||||
info("message body tagName '" + body.tagName + "' className '" +
|
info("message body tagName '" + body.tagName + "' className '" +
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
const {Cc,Ci,Cu,Cr} = require("chrome");
|
const {Cc,Ci,Cu,Cr} = require("chrome");
|
||||||
const ObservableObject = require("devtools/client/shared/observable-object");
|
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 {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||||
const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
|
const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var {Ci,Cu,CC} = require("chrome");
|
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 {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||||
const Services = require("Services");
|
const Services = require("Services");
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
const { DebuggerServer } = require("devtools/server/main");
|
||||||
let win;
|
let win;
|
||||||
|
|
||||||
SimpleTest.registerCleanupFunction(() => {
|
SimpleTest.registerCleanupFunction(() => {
|
||||||
|
@ -39,8 +40,6 @@
|
||||||
return !win.document.querySelector("#cmd_stop").hasAttribute("disabled");
|
return !win.document.querySelector("#cmd_stop").hasAttribute("disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { DebuggerServer } = require("devtools/server/main");
|
|
||||||
|
|
||||||
if (!DebuggerServer.initialized) {
|
if (!DebuggerServer.initialized) {
|
||||||
DebuggerServer.init();
|
DebuggerServer.init();
|
||||||
DebuggerServer.addBrowserActors();
|
DebuggerServer.addBrowserActors();
|
||||||
|
|
|
@ -621,7 +621,7 @@ ThreadActor.prototype = {
|
||||||
this._debuggerSourcesSeen = new Set();
|
this._debuggerSourcesSeen = new Set();
|
||||||
|
|
||||||
update(this._options, aRequest.options || {});
|
update(this._options, aRequest.options || {});
|
||||||
this.sources.reconfigure(this._options);
|
this.sources.setOptions(this._options);
|
||||||
this.sources.on('newSource', (name, source) => {
|
this.sources.on('newSource', (name, source) => {
|
||||||
this.onNewSource(source);
|
this.onNewSource(source);
|
||||||
});
|
});
|
||||||
|
@ -678,10 +678,16 @@ ThreadActor.prototype = {
|
||||||
if (this.state == "exited") {
|
if (this.state == "exited") {
|
||||||
return { error: "wrongState" };
|
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
|
// Update the global source store
|
||||||
this.sources.reconfigure(this._options);
|
this.sources.setOptions(options);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
@ -1921,21 +1927,26 @@ ThreadActor.prototype = {
|
||||||
|
|
||||||
let sourceActor = this.sources.createNonSourceMappedActor(aSource);
|
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
|
// Go ahead and establish the source actors for this script, which
|
||||||
// fetches sourcemaps if available and sends onNewSource
|
// fetches sourcemaps if available and sends onNewSource
|
||||||
// notifications.
|
// notifications.
|
||||||
//
|
let sourceActorsCreated = this.sources.createSourceActors(aSource);
|
||||||
// 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));
|
|
||||||
|
|
||||||
// Set any stored breakpoints.
|
if (bpActors.length) {
|
||||||
let promises = [];
|
// 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
|
// 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.
|
// binding because for/of does not yet do that in SpiderMonkey.
|
||||||
let actor = _actor;
|
let actor = _actor;
|
||||||
|
|
|
@ -57,16 +57,22 @@ TabSources.prototype = {
|
||||||
/**
|
/**
|
||||||
* Update preferences and clear out existing sources
|
* Update preferences and clear out existing sources
|
||||||
*/
|
*/
|
||||||
reconfigure: function(options) {
|
setOptions: function(options) {
|
||||||
|
let shouldReset = false;
|
||||||
|
|
||||||
if ('useSourceMaps' in options) {
|
if ('useSourceMaps' in options) {
|
||||||
|
shouldReset = true;
|
||||||
this._useSourceMaps = options.useSourceMaps;
|
this._useSourceMaps = options.useSourceMaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('autoBlackBox' in options) {
|
if ('autoBlackBox' in options) {
|
||||||
|
shouldReset = true;
|
||||||
this._autoBlackBox = options.autoBlackBox;
|
this._autoBlackBox = options.autoBlackBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reset();
|
if (shouldReset) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -392,7 +398,8 @@ TabSources.prototype = {
|
||||||
let result = this._fetchSourceMap(sourceMapURL, aSource.url);
|
let result = this._fetchSourceMap(sourceMapURL, aSource.url);
|
||||||
|
|
||||||
// The promises in `_sourceMaps` must be the exact same instances
|
// 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);
|
this._sourceMaps.set(aSource, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
|
@ -66,6 +66,7 @@ module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebu
|
||||||
const dbg = new Debugger();
|
const dbg = new Debugger();
|
||||||
EventEmitter.decorate(dbg);
|
EventEmitter.decorate(dbg);
|
||||||
|
|
||||||
|
dbg.allowUnobservedAsmJS = true;
|
||||||
dbg.uncaughtExceptionHook = reportDebuggerHookException;
|
dbg.uncaughtExceptionHook = reportDebuggerHookException;
|
||||||
|
|
||||||
dbg.onNewGlobalObject = function(global) {
|
dbg.onNewGlobalObject = function(global) {
|
||||||
|
|
|
@ -298,7 +298,6 @@ DebuggerClient.requester = function (aPacketSkeleton,
|
||||||
histogram.add(+new Date - startTime);
|
histogram.add(+new Date - startTime);
|
||||||
}
|
}
|
||||||
}, "DebuggerClient.requester request callback"));
|
}, "DebuggerClient.requester request callback"));
|
||||||
|
|
||||||
}, "DebuggerClient.requester");
|
}, "DebuggerClient.requester");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -340,8 +339,8 @@ DebuggerClient.prototype = {
|
||||||
connect: function (aOnConnected) {
|
connect: function (aOnConnected) {
|
||||||
this.emit("connect");
|
this.emit("connect");
|
||||||
|
|
||||||
// Also emit the event on the |DebuggerServer| object (not on
|
// Also emit the event on the |DebuggerClient| object (not on the instance),
|
||||||
// the instance), so it's possible to track all instances.
|
// so it's possible to track all instances.
|
||||||
events.emit(DebuggerClient, "connect", this);
|
events.emit(DebuggerClient, "connect", this);
|
||||||
|
|
||||||
this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
|
this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
|
||||||
|
@ -1437,6 +1436,9 @@ WorkerClient.prototype = {
|
||||||
_onClose: function () {
|
_onClose: function () {
|
||||||
this.removeListener("close", this._onClose);
|
this.removeListener("close", this._onClose);
|
||||||
|
|
||||||
|
if (this.thread) {
|
||||||
|
this.client.unregisterClient(this.thread);
|
||||||
|
}
|
||||||
this.client.unregisterClient(this);
|
this.client.unregisterClient(this);
|
||||||
this._isClosed = true;
|
this._isClosed = true;
|
||||||
},
|
},
|
||||||
|
@ -2169,12 +2171,20 @@ ThreadClient.prototype = {
|
||||||
*/
|
*/
|
||||||
_onThreadState: function (aPacket) {
|
_onThreadState: function (aPacket) {
|
||||||
this._state = ThreadStateTypes[aPacket.type];
|
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._clearFrames();
|
||||||
this._clearPauseGrips();
|
this._clearPauseGrips();
|
||||||
aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
|
aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
|
||||||
this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
|
this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getLastPausePacket: function() {
|
||||||
|
return this._lastPausePacket;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an EnvironmentClient instance for the given environment actor form.
|
* Return an EnvironmentClient instance for the given environment actor form.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
|
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_DEPTH = 4;
|
||||||
const DEFAULT_MAX_SIBLINGS = 15;
|
const DEFAULT_MAX_SIBLINGS = 15;
|
||||||
|
@ -213,3 +214,76 @@ DominatorTreeNode.partialTraversal = function (dominatorTree,
|
||||||
|
|
||||||
return dfs(dominatorTree.root, 0);
|
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
|
* - {Boolean} moreChildrenAvailable
|
||||||
* True iff there are more children available after the returned
|
* True iff there are more children available after the returned
|
||||||
* nodes.
|
* 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) {
|
HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
|
||||||
return this._worker.performTask("getImmediatelyDominated", opts);
|
return this._worker.performTask("getImmediatelyDominated", opts);
|
||||||
|
|
|
@ -194,7 +194,15 @@ workerHelper.createTask(self, "getImmediatelyDominated", request => {
|
||||||
return node;
|
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;
|
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));
|
dumpn("Actual label: " + JSON.stringify(visitor.label(), null, 4));
|
||||||
assertStructurallyEquivalent(visitor.label(), expectedLabel);
|
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(Array.isArray(response.nodes));
|
||||||
ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
|
ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
|
||||||
ok(response.moreChildrenAvailable);
|
ok(response.moreChildrenAvailable);
|
||||||
|
equal(response.path.length, 1);
|
||||||
|
equal(response.path[0], partialTree.nodeId);
|
||||||
|
|
||||||
// Next, test getting a subset of children available.
|
// Next, test getting a subset of children available.
|
||||||
const secondResponse = yield client.getImmediatelyDominated({
|
const secondResponse = yield client.getImmediatelyDominated({
|
||||||
|
@ -54,6 +56,8 @@ add_task(function* () {
|
||||||
ok(Array.isArray(secondResponse.nodes));
|
ok(Array.isArray(secondResponse.nodes));
|
||||||
ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
|
ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
|
||||||
ok(!secondResponse.moreChildrenAvailable);
|
ok(!secondResponse.moreChildrenAvailable);
|
||||||
|
equal(secondResponse.path.length, 1);
|
||||||
|
equal(secondResponse.path[0], partialTree.nodeId);
|
||||||
|
|
||||||
client.destroy();
|
client.destroy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,10 @@ support-files =
|
||||||
[test_DominatorTree_03.js]
|
[test_DominatorTree_03.js]
|
||||||
[test_DominatorTree_04.js]
|
[test_DominatorTree_04.js]
|
||||||
[test_DominatorTree_05.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_01.js]
|
||||||
[test_DominatorTreeNode_LabelAndShallowSize_02.js]
|
[test_DominatorTreeNode_LabelAndShallowSize_02.js]
|
||||||
[test_DominatorTreeNode_LabelAndShallowSize_03.js]
|
[test_DominatorTreeNode_LabelAndShallowSize_03.js]
|
||||||
|
|
|
@ -22,8 +22,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { utils: Cu } = Components;
|
const { utils: Cu } = Components;
|
||||||
const { Promise: promise } =
|
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||||
Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {});
|
const promise = require("promise");
|
||||||
const { EventEmitter } =
|
const { EventEmitter } =
|
||||||
Cu.import("resource://devtools/shared/event-emitter.js", {});
|
Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
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)
|
static void Write(Message* aMsg, const paramType& aParam)
|
||||||
{
|
{
|
||||||
WriteParam(aMsg, aParam.mLength);
|
auto length = aParam.mLength;
|
||||||
for (uint8_t i = 0; i < aParam.mLength; ++i) {
|
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]);
|
WriteParam(aMsg, aParam.mPinCode[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,11 +265,16 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
|
||||||
|
|
||||||
static void Write(Message* aMsg, const paramType& aParam)
|
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.mHandle);
|
||||||
WriteParam(aMsg, aParam.mOffset);
|
WriteParam(aMsg, aParam.mOffset);
|
||||||
WriteParam(aMsg, aParam.mLength);
|
WriteParam(aMsg, length);
|
||||||
WriteParam(aMsg, aParam.mAuthReq);
|
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]);
|
WriteParam(aMsg, aParam.mValue[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,6 +288,10 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aResult->mLength > MOZ_ARRAY_LENGTH(aResult->mValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint16_t i = 0; i < aResult->mLength; i++) {
|
for (uint16_t i = 0; i < aResult->mLength; i++) {
|
||||||
if (!ReadParam(aMsg, aIter, &(aResult->mValue[i]))) {
|
if (!ReadParam(aMsg, aIter, &(aResult->mValue[i]))) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
#include "ServiceWorkerManager.h"
|
#include "ServiceWorkerManager.h"
|
||||||
|
|
||||||
#include "mozIApplication.h"
|
|
||||||
#include "nsIAppsService.h"
|
|
||||||
#include "nsIConsoleService.h"
|
#include "nsIConsoleService.h"
|
||||||
#include "nsIDOMEventTarget.h"
|
#include "nsIDOMEventTarget.h"
|
||||||
#include "nsIDocument.h"
|
#include "nsIDocument.h"
|
||||||
|
@ -4466,11 +4464,11 @@ ServiceWorkerManager::PropagateRemoveAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ServiceWorkerManager::RemoveAllRegistrations(PrincipalOriginAttributes* aParams)
|
ServiceWorkerManager::RemoveAllRegistrations(OriginAttributesPattern* aPattern)
|
||||||
{
|
{
|
||||||
AssertIsOnMainThread();
|
AssertIsOnMainThread();
|
||||||
|
|
||||||
MOZ_ASSERT(aParams);
|
MOZ_ASSERT(aPattern);
|
||||||
|
|
||||||
for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
|
for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
|
||||||
ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
|
ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
|
||||||
|
@ -4484,49 +4482,14 @@ ServiceWorkerManager::RemoveAllRegistrations(PrincipalOriginAttributes* aParams)
|
||||||
MOZ_ASSERT(reg);
|
MOZ_ASSERT(reg);
|
||||||
MOZ_ASSERT(reg->mPrincipal);
|
MOZ_ASSERT(reg->mPrincipal);
|
||||||
|
|
||||||
bool equals = false;
|
bool matches =
|
||||||
|
aPattern->Matches(BasePrincipal::Cast(reg->mPrincipal)->OriginAttributesRef());
|
||||||
if (aParams->mInBrowser) {
|
if (!matches) {
|
||||||
// When we do a system wide "clear cookies and stored data" on B2G we
|
continue;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (equals) {
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
||||||
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
swm->ForceUnregister(data, reg);
|
||||||
swm->ForceUnregister(data, reg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4699,10 +4662,10 @@ ServiceWorkerManager::Observe(nsISupports* aSubject,
|
||||||
|
|
||||||
if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) {
|
if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) {
|
||||||
MOZ_ASSERT(XRE_IsParentProcess());
|
MOZ_ASSERT(XRE_IsParentProcess());
|
||||||
PrincipalOriginAttributes attrs;
|
OriginAttributesPattern pattern;
|
||||||
MOZ_ALWAYS_TRUE(attrs.Init(nsAutoString(aData)));
|
MOZ_ALWAYS_TRUE(pattern.Init(nsAutoString(aData)));
|
||||||
|
|
||||||
RemoveAllRegistrations(&attrs);
|
RemoveAllRegistrations(&pattern);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5052,7 +5015,7 @@ ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal,
|
||||||
}
|
}
|
||||||
|
|
||||||
PrincipalOriginAttributes attrs =
|
PrincipalOriginAttributes attrs =
|
||||||
mozilla::BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
|
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
|
||||||
|
|
||||||
// Then trigger an update to fire asynchronously now.
|
// Then trigger an update to fire asynchronously now.
|
||||||
PropagateSoftUpdate(attrs, NS_ConvertUTF8toUTF16(aScope));
|
PropagateSoftUpdate(attrs, NS_ConvertUTF8toUTF16(aScope));
|
||||||
|
|
|
@ -640,10 +640,9 @@ private:
|
||||||
void
|
void
|
||||||
RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration);
|
RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration);
|
||||||
|
|
||||||
// Removes all service worker registrations that matches the given
|
// Removes all service worker registrations that matches the given pattern.
|
||||||
// mozIApplicationClearPrivateDataParams.
|
|
||||||
void
|
void
|
||||||
RemoveAllRegistrations(PrincipalOriginAttributes* aParams);
|
RemoveAllRegistrations(OriginAttributesPattern* aPattern);
|
||||||
|
|
||||||
RefPtr<ServiceWorkerManagerChild> mActor;
|
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 =
|
support-files =
|
||||||
app/*
|
app/*
|
||||||
app2/*
|
app2/*
|
||||||
|
app3/*
|
||||||
chrome_helpers.js
|
chrome_helpers.js
|
||||||
|
empty.js
|
||||||
|
serviceworker.html
|
||||||
serviceworkerinfo_iframe.html
|
serviceworkerinfo_iframe.html
|
||||||
serviceworkermanager_iframe.html
|
serviceworkermanager_iframe.html
|
||||||
serviceworkerregistrationinfo_iframe.html
|
serviceworkerregistrationinfo_iframe.html
|
||||||
|
@ -12,6 +15,7 @@ support-files =
|
||||||
|
|
||||||
[test_aboutserviceworkers.html]
|
[test_aboutserviceworkers.html]
|
||||||
skip-if = true #bug 1193319
|
skip-if = true #bug 1193319
|
||||||
|
[test_clear_origin_data.html]
|
||||||
[test_app_installation.html]
|
[test_app_installation.html]
|
||||||
[test_privateBrowsing.html]
|
[test_privateBrowsing.html]
|
||||||
[test_serviceworkerinfo.xul]
|
[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 {
|
defaultConfig {
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
minSdkVersion 14
|
minSdkVersion 15
|
||||||
// Used by Robolectric based tests; see TestRunner.
|
// Used by Robolectric based tests; see TestRunner.
|
||||||
buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
|
buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
minSdkVersion 14
|
minSdkVersion 15
|
||||||
applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
|
applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
|
||||||
testApplicationId 'org.mozilla.roboexample.test'
|
testApplicationId 'org.mozilla.roboexample.test'
|
||||||
testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
|
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_x2", "0.80");
|
||||||
pref("apz.fling_curve_function_y2", "1.0");
|
pref("apz.fling_curve_function_y2", "1.0");
|
||||||
pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
|
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");
|
pref("apz.max_velocity_inches_per_ms", "0.07");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -34,13 +34,13 @@ ANDROID_NDK_VERSION_32BIT="r10c"
|
||||||
|
|
||||||
# Build B2GDroid
|
# Build B2GDroid
|
||||||
ac_add_options --enable-application=mobile/android/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-ndk="$topsrcdir/android-ndk-r10e"
|
||||||
ac_add_options --with-android-sdk="$topsrcdir/android-sdk-linux"
|
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-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 --with-system-zlib
|
||||||
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
|
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
|
||||||
|
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче