Merge m-c to inbound on a CLOSED TREE.

This commit is contained in:
Ryan VanderMeulen 2014-03-10 17:11:39 -04:00
commit e5690e4625
61 changed files with 1530 additions and 422 deletions

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,14 +15,14 @@
<project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>

View File

@ -4,6 +4,6 @@
"branch": "",
"revision": ""
},
"revision": "6064a315797a6db79e5ea926a69835b3d72e723b",
"revision": "c5bd933fe99317a7e99822f2d9345ae67a3043df",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,12 +19,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f1d4d4c6d0e6079b2f7228a7ae849d4608e2c076"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="707630df1b4270eae3dd49b7344c645f32c1b5f4"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

View File

@ -1633,7 +1633,7 @@ BrowserGlue.prototype = {
// be set to the version it has been added in, we will compare its value
// to users' smartBookmarksVersion and add new smart bookmarks without
// recreating old deleted ones.
const SMART_BOOKMARKS_VERSION = 6;
const SMART_BOOKMARKS_VERSION = 7;
const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
@ -1665,7 +1665,7 @@ BrowserGlue.prototype = {
Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
"&maxResults=" + MAX_RESULTS),
parent: PlacesUtils.toolbarFolderId,
position: toolbarIndex++,
get position() { return toolbarIndex++; },
newInVersion: 1
},
RecentlyBookmarked: {
@ -1680,7 +1680,7 @@ BrowserGlue.prototype = {
"&maxResults=" + MAX_RESULTS +
"&excludeQueries=1"),
parent: PlacesUtils.bookmarksMenuFolderId,
position: menuIndex++,
get position() { return menuIndex++; },
newInVersion: 1
},
RecentTags: {
@ -1692,25 +1692,31 @@ BrowserGlue.prototype = {
Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
"&maxResults=" + MAX_RESULTS),
parent: PlacesUtils.bookmarksMenuFolderId,
position: menuIndex++,
get position() { return menuIndex++; },
newInVersion: 1
},
};
if (Services.metro && Services.metro.supported) {
smartBookmarks.Windows8Touch = {
title: bundle.GetStringFromName("windows8TouchTitle"),
uri: NetUtil.newURI("place:folder=" +
PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0] +
"&queryType=" +
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
"&maxResults=" + MAX_RESULTS +
"&excludeQueries=1"),
title: PlacesUtils.getString("windows8TouchTitle"),
get uri() {
let metroBookmarksRoot = PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {});
if (metroBookmarksRoot.length > 0) {
return NetUtil.newURI("place:folder=" +
metroBookmarksRoot[0] +
"&queryType=" +
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
"&maxResults=" + MAX_RESULTS +
"&excludeQueries=1")
}
return null;
},
parent: PlacesUtils.bookmarksMenuFolderId,
position: menuIndex++,
newInVersion: 6
get position() { return menuIndex++; },
newInVersion: 7
};
}
@ -1722,9 +1728,13 @@ BrowserGlue.prototype = {
let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
if (queryId in smartBookmarks) {
let smartBookmark = smartBookmarks[queryId];
if (!smartBookmark.uri) {
PlacesUtils.bookmarks.removeItem(itemId);
return;
}
smartBookmark.itemId = itemId;
smartBookmark.parent = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
smartBookmark.position = PlacesUtils.bookmarks.getItemIndex(itemId);
smartBookmark.updatedPosition = PlacesUtils.bookmarks.getItemIndex(itemId);
}
else {
// We don't remove old Smart Bookmarks because user could still
@ -1742,7 +1752,7 @@ BrowserGlue.prototype = {
// bookmark if it has been removed.
if (smartBookmarksCurrentVersion > 0 &&
smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
!smartBookmark.itemId)
!smartBookmark.itemId || !smartBookmark.uri)
continue;
// Remove old version of the smart bookmark if it exists, since it
@ -1755,7 +1765,7 @@ BrowserGlue.prototype = {
smartBookmark.itemId =
PlacesUtils.bookmarks.insertBookmark(smartBookmark.parent,
smartBookmark.uri,
smartBookmark.position,
smartBookmark.updatedPosition || smartBookmark.position,
smartBookmark.title);
PlacesUtils.annotations.setItemAnnotation(smartBookmark.itemId,
SMART_BOOKMARKS_ANNO,

View File

@ -63,10 +63,9 @@ let (XULAppInfo = {
}
// Smart bookmarks constants.
let isMetroSupported = Services.metro && Services.metro.supported;
const SMART_BOOKMARKS_VERSION = 6
const SMART_BOOKMARKS_VERSION = 7;
const SMART_BOOKMARKS_ON_TOOLBAR = 1;
const SMART_BOOKMARKS_ON_MENU = isMetroSupported ? 4 : 3; // Takes in count the additional separator.
const SMART_BOOKMARKS_ON_MENU = 3; // Takes into account the additional separator.
// Default bookmarks constants.
const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;

View File

@ -0,0 +1,13 @@
var { Cc, Ci } = require("chrome");
var { once } = require("sdk/system/events");
var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
var observer = {
observe: function () {
debugger;
}
};
once("sdk:loader:destroy", () => observerService.removeObserver(observer, "debuggerAttached"));
observerService.addObserver(observer, "debuggerAttached", false);

View File

@ -0,0 +1,9 @@
{
"name": "browser_dbg_addon3",
"title": "browser_dbg_addon3",
"id": "jid1-ami3akps3baaeg",
"description": "a basic add-on",
"author": "",
"license": "MPL 2.0",
"version": "0.1"
}

Binary file not shown.

View File

@ -2,6 +2,7 @@
support-files =
addon1.xpi
addon2.xpi
addon3.xpi
code_binary_search.coffee
code_binary_search.js
code_binary_search.map
@ -80,6 +81,7 @@ support-files =
testactors.js
[browser_dbg_aaa_run_first_leaktest.js]
[browser_dbg_addonactor.js]
[browser_dbg_auto-pretty-print-01.js]
[browser_dbg_auto-pretty-print-02.js]
[browser_dbg_bfcache.js]

View File

@ -0,0 +1,99 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Make sure we can attach to addon actors.
const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
const ADDON_MODULE_URL = "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js";
var gAddon, gClient, gThreadClient;
function test() {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
gClient.connect((aType, aTraits) => {
is(aType, "browser",
"Root actor should identify itself as a browser.");
installAddon()
.then(attachAddonActorForUrl.bind(null, gClient, ADDON3_URL))
.then(attachAddonThread)
.then(testDebugger)
.then(testSources)
.then(uninstallAddon)
.then(closeConnection)
.then(finish)
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
function installAddon () {
return addAddon(ADDON3_URL).then(aAddon => {
gAddon = aAddon;
});
}
function attachAddonThread ([aGrip, aResponse]) {
info("attached addon actor for URL");
let deferred = promise.defer();
gClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
info("attached thread");
gThreadClient = aThreadClient;
gThreadClient.resume(deferred.resolve);
});
return deferred.promise;
}
function testDebugger() {
info('Entering testDebugger');
let deferred = promise.defer();
once(gClient, "paused").then(() => {
ok(true, "Should be able to attach to addon actor");
gThreadClient.resume(deferred.resolve)
});
Services.obs.notifyObservers(null, "debuggerAttached", null);
return deferred.promise;
}
function testSources() {
let deferred = promise.defer();
gThreadClient.getSources(aResponse => {
// source URLs contain launch-specific temporary directory path,
// hence the ".contains" call.
const matches = aResponse.sources.filter(s =>
s.url.contains(ADDON_MODULE_URL));
is(matches.length, 1,
"the main script of the addon is present in the source list");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
});

View File

@ -154,6 +154,7 @@ function getTabActorForUrl(aClient, aUrl) {
}
function getAddonActorForUrl(aClient, aUrl) {
info("Get addon actor for URL: " + aUrl);
let deferred = promise.defer();
aClient.listAddons(aResponse => {
@ -661,3 +662,14 @@ function filterTraces(aPanel, f) {
return Array.filter(traces, f);
}
function attachAddonActorForUrl(aClient, aUrl) {
let deferred = promise.defer();
getAddonActorForUrl(aClient, aUrl).then(aGrip => {
aClient.attachAddon(aGrip.actor, aResponse => {
deferred.resolve([aGrip, aResponse]);
});
});
return deferred.promise;
}

View File

@ -562,7 +562,11 @@ TabWebProgressListener.prototype = {
*/
destroy: function TWPL_destroy() {
if (this.target.tab) {
this.target.tab.linkedBrowser.removeProgressListener(this);
try {
this.target.tab.linkedBrowser.removeProgressListener(this);
} catch (ex) {
// This can throw when a tab crashes in e10s.
}
}
this.target._webProgressListener = null;
this.target._navRequest = null;

View File

@ -130,6 +130,99 @@ var Scratchpad = {
return obj;
},
/**
* Add the event listeners for popupshowing events.
*/
_setupPopupShowingListeners: function SP_setupPopupShowing() {
let elementIDs = ['sp-menu_editpopup', 'scratchpad-text-popup'];
for (let elementID of elementIDs) {
let elem = document.getElementById(elementID);
if (elem) {
elem.addEventListener("popupshowing", function () {
goUpdateGlobalEditMenuItems();
let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_findAgain'];
commands.forEach(goUpdateCommand);
});
}
}
},
/**
* Add the event event listeners for command events.
*/
_setupCommandListeners: function SP_setupCommands() {
let commands = {
"cmd_gotoLine": () => {
goDoCommand('cmd_gotoLine');
},
"sp-cmd-newWindow": () => {
Scratchpad.openScratchpad();
},
"sp-cmd-openFile": () => {
Scratchpad.openFile();
},
"sp-cmd-clearRecentFiles": () => {
Scratchpad.clearRecentFiles();
},
"sp-cmd-save": () => {
Scratchpad.saveFile();
},
"sp-cmd-saveas": () => {
Scratchpad.saveFileAs();
},
"sp-cmd-revert": () => {
Scratchpad.promptRevert();
},
"sp-cmd-close": () => {
Scratchpad.close();
},
"sp-cmd-run": () => {
Scratchpad.run();
},
"sp-cmd-inspect": () => {
Scratchpad.inspect();
},
"sp-cmd-display": () => {
Scratchpad.display();
},
"sp-cmd-pprint": () => {
Scratchpad.prettyPrint();
},
"sp-cmd-contentContext": () => {
Scratchpad.setContentContext();
},
"sp-cmd-browserContext": () => {
Scratchpad.setBrowserContext();
},
"sp-cmd-reloadAndRun": () => {
Scratchpad.reloadAndRun();
},
"sp-cmd-evalFunction": () => {
Scratchpad.evalTopLevelFunction();
},
"sp-cmd-errorConsole": () => {
Scratchpad.openErrorConsole();
},
"sp-cmd-webConsole": () => {
Scratchpad.openWebConsole();
},
"sp-cmd-documentationLink": () => {
Scratchpad.openDocumentationPage();
},
"sp-cmd-hideSidebar": () => {
Scratchpad.sidebar.hide();
}
}
for (let command in commands) {
let elem = document.getElementById(command);
if (elem) {
elem.addEventListener("command", commands[command]);
}
}
},
/**
* The script execution context. This tells Scratchpad in which context the
* script shall execute.
@ -1168,7 +1261,7 @@ var Scratchpad = {
menuitem.setAttribute("disabled", true);
}
menuitem.setAttribute("oncommand", "Scratchpad.openFile(" + i + ");");
menuitem.addEventListener("command", Scratchpad.openFile.bind(Scratchpad, i));
recentFilesPopup.appendChild(menuitem);
}
@ -1513,6 +1606,8 @@ var Scratchpad = {
PreferenceObserver.init();
CloseObserver.init();
}).then(null, (err) => console.log(err.message));
this._setupCommandListeners();
this._setupPopupShowingListeners();
},
/**

View File

@ -33,41 +33,32 @@
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript" src="chrome://browser/content/devtools/scratchpad.js"/>
<script type="application/javascript">
function goUpdateSourceEditorMenuItems() {
goUpdateGlobalEditMenuItems();
let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_findAgain'];
commands.forEach(goUpdateCommand);
}
</script>
<commandset id="editMenuCommands"/>
<commandset id="sourceEditorCommands">
<command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
<command id="cmd_gotoLine" oncommand=";"/>
</commandset>
<commandset id="sp-commandset">
<command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
<command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
<command id="sp-cmd-clearRecentFiles" oncommand="Scratchpad.clearRecentFiles();"/>
<command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
<command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
<command id="sp-cmd-revert" oncommand="Scratchpad.promptRevert();" disabled="true"/>
<command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
<command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
<command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
<command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
<command id="sp-cmd-pprint" oncommand="Scratchpad.prettyPrint();"/>
<command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
<command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
<command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
<command id="sp-cmd-evalFunction" oncommand="Scratchpad.evalTopLevelFunction();"/>
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
<command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
<command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
<command id="sp-cmd-newWindow" oncommand=";"/>
<command id="sp-cmd-openFile" oncommand=";"/>
<command id="sp-cmd-clearRecentFiles" oncommand=";"/>
<command id="sp-cmd-save" oncommand=";"/>
<command id="sp-cmd-saveas" oncommand=";"/>
<command id="sp-cmd-revert" oncommand=";" disabled="true"/>
<command id="sp-cmd-close" oncommand=";"/>
<command id="sp-cmd-run" oncommand=";"/>
<command id="sp-cmd-inspect" oncommand=";"/>
<command id="sp-cmd-display" oncommand=";"/>
<command id="sp-cmd-pprint" oncommand=";"/>
<command id="sp-cmd-contentContext" oncommand=";"/>
<command id="sp-cmd-browserContext" oncommand=";" disabled="true"/>
<command id="sp-cmd-reloadAndRun" oncommand=";"/>
<command id="sp-cmd-evalFunction" oncommand=";"/>
<command id="sp-cmd-errorConsole" oncommand=";" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand=";"/>
<command id="sp-cmd-documentationLink" oncommand=";"/>
<command id="sp-cmd-hideSidebar" oncommand=";"/>
</commandset>
<keyset id="editMenuKeys"/>
@ -172,8 +163,7 @@
<menu id="sp-edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;">
<menupopup id="sp-menu_editpopup"
onpopupshowing="goUpdateSourceEditorMenuItems()">
<menupopup id="sp-menu_editpopup">
<menuitem id="menu_undo"/>
<menuitem id="menu_redo"/>
<menuseparator/>
@ -305,8 +295,7 @@
<popupset id="scratchpad-popups">
<menupopup id="scratchpad-text-popup"
onpopupshowing="goUpdateSourceEditorMenuItems()">
<menupopup id="scratchpad-text-popup">
<menuitem id="cMenu_cut"/>
<menuitem id="cMenu_copy"/>
<menuitem id="cMenu_paste"/>

View File

@ -745,6 +745,40 @@ Tooltip.prototype = {
def.reject();
}
return def.promise;
},
/**
* Set the content of the tooltip to display a font family preview.
* This is based on Lea Verou's Dablet. See https://github.com/LeaVerou/dabblet
* for more info.
*
* @param {String} font
* The font family value.
*/
setFontFamilyContent: function(font) {
let def = promise.defer();
if (font) {
// Main container
let vbox = this.doc.createElement("vbox");
vbox.setAttribute("flex", "1");
// Display the font family previewer
let previewer = this.doc.createElement("description");
previewer.setAttribute("flex", "1");
previewer.style.fontFamily = font;
previewer.classList.add("devtools-tooltip-font-previewer-text");
previewer.textContent = "(ABCabc123&@%)";
vbox.appendChild(previewer);
this.content = vbox;
def.resolve();
} else {
def.reject();
}
return def.promise;
}
};

View File

@ -531,14 +531,20 @@ CssHtmlTree.prototype = {
}
}
// Test for css transform
if (target.classList.contains("property-value")) {
let propValue = target;
let propName = target.parentNode.querySelector(".property-name");
// Test for css transform
if (propName.textContent === "transform") {
return this.tooltip.setCssTransformContent(propValue.textContent,
this.pageStyle, this.viewedElement);
}
// Test for font family
if (propName.textContent === "font-family") {
return this.tooltip.setFontFamilyContent(propValue.textContent);
}
}
// If the target isn't one that should receive a tooltip, signal it by rejecting

View File

@ -1142,6 +1142,27 @@ CssRuleView.prototype = {
}
}
// Get the nodes containing the property name and property value,
// and test for font family
let propertyRoot = target.parentNode;
let propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
if (!propertyNameNode) {
propertyRoot = propertyRoot.parentNode;
propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
}
let propertyName;
if (propertyNameNode) {
propertyName = propertyNameNode.textContent;
}
if (propertyName === "font-family" &&
target.classList.contains("ruleview-propertyvalue")) {
this.previewTooltip.setFontFamilyContent(target.textContent).then(def.resolve);
hasTooltip = true;
}
if (hasTooltip) {
this.colorPicker.revert();
this.colorPicker.hide();

View File

@ -56,6 +56,8 @@ support-files = browser_ruleview_pseudoelement.html
[browser_bug765105_background_image_tooltip.js]
[browser_bug889638_rule_view_color_picker.js]
[browser_bug726427_csstransform_tooltip.js]
[browser_bug702577_fontfamily_tooltip_shorthand.js]
[browser_bug702577_fontfamily_tooltip_longhand.js]
[browser_bug940500_rule_view_pick_gradient_color.js]
[browser_ruleview_original_source_link.js]
support-files =

View File

@ -0,0 +1,131 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let contentDoc;
let inspector;
let ruleView;
let computedView;
const PAGE_CONTENT = [
'<style type="text/css">',
' #testElement {',
' font-family: cursive;',
' color: #333;',
' padding-left: 70px;',
' }',
'</style>',
'<div id="testElement">test element</div>'
].join("\n");
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
contentDoc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html;charset=utf-8,font family longhand tooltip test";
}
function createDocument() {
contentDoc.body.innerHTML = PAGE_CONTENT;
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
ruleView = aRuleView;
startTests();
});
}
function startTests() {
inspector.selection.setNode(contentDoc.querySelector("#testElement"));
inspector.once("inspector-updated", testRuleView);
}
function endTests() {
contentDoc = inspector = ruleView = computedView = null;
gBrowser.removeCurrentTab();
finish();
}
function assertTooltipShownOn(tooltip, element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true);
cb();
}, true);
tooltip._showOnHover(element);
}
function testRuleView() {
info("Testing font-family tooltips in the rule view");
let panel = ruleView.previewTooltip.panel;
// Check that the rule view has a tooltip and that a XUL panel has been created
ok(ruleView.previewTooltip, "Tooltip instance exists");
ok(panel, "XUL panel exists");
// Get the font family property inside the rule view
let {valueSpan} = getRuleViewProperty("font-family");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
ruleView.previewTooltip.hide();
testComputedView();
});
}
function testComputedView() {
info("Testing font-family tooltips in the computed view");
inspector.sidebar.select("computedview");
computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
let doc = computedView.styleDocument;
let panel = computedView.tooltip.panel;
let {valueSpan} = getComputedViewProperty("font-family");
assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
computedView.tooltip.hide();
endTests();
});
}
function getRuleViewProperty(name) {
let prop = null;
[].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
let nameSpan = property.querySelector(".ruleview-propertyname");
let valueSpan = property.querySelector(".ruleview-propertyvalue");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}
function getComputedViewProperty(name) {
let prop = null;
[].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
let nameSpan = property.querySelector(".property-name");
let valueSpan = property.querySelector(".property-value");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}

View File

@ -0,0 +1,133 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let contentDoc;
let inspector;
let ruleView;
let computedView;
const PAGE_CONTENT = [
'<style type="text/css">',
' #testElement {',
' font: italic bold .8em/1.2 Arial;',
' }',
'</style>',
'<div id="testElement">test element</div>'
].join("\n");
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
contentDoc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html;charset=utf-8,font family shorthand tooltip test";
}
function createDocument() {
contentDoc.body.innerHTML = PAGE_CONTENT;
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
ruleView = aRuleView;
startTests();
});
}
function startTests() {
inspector.selection.setNode(contentDoc.querySelector("#testElement"));
inspector.once("inspector-updated", testRuleView);
}
function endTests() {
contentDoc = inspector = ruleView = computedView = null;
gBrowser.removeCurrentTab();
finish();
}
function assertTooltipShownOn(tooltip, element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true);
cb();
}, true);
tooltip._showOnHover(element);
}
function testRuleView() {
info("Testing font-family tooltips in the rule view");
let panel = ruleView.previewTooltip.panel;
// Check that the rule view has a tooltip and that a XUL panel has been created
ok(ruleView.previewTooltip, "Tooltip instance exists");
ok(panel, "XUL panel exists");
// Get the computed font family property inside the font rule view
let propertyList = ruleView.element.querySelectorAll(".ruleview-propertylist");
let fontExpander = propertyList[1].querySelectorAll(".ruleview-expander")[0];
fontExpander.click();
let {valueSpan} = getRuleViewProperty("font-family");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
ruleView.previewTooltip.hide();
testComputedView();
});
}
function testComputedView() {
info("Testing font-family tooltips in the computed view");
inspector.sidebar.select("computedview");
computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
let doc = computedView.styleDocument;
let panel = computedView.tooltip.panel;
let {valueSpan} = getComputedViewProperty("font-family");
assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
computedView.tooltip.hide();
endTests();
});
}
function getRuleViewProperty(name) {
let prop = null;
[].forEach.call(ruleView.doc.querySelectorAll(".ruleview-computedlist"), property => {
let nameSpan = property.querySelector(".ruleview-propertyname");
let valueSpan = property.querySelector(".ruleview-propertyvalue");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}
function getComputedViewProperty(name) {
let prop = null;
[].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
let nameSpan = property.querySelector(".property-name");
let valueSpan = property.querySelector(".property-value");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}

View File

@ -70,10 +70,6 @@ detailsPane.itemsCountLabel=One item;#1 items
mostVisitedTitle=Most Visited
recentlyBookmarkedTitle=Recently Bookmarked
recentTagsTitle=Recent Tags
# LOCALIZATION NOTE (windows8TouchTitle): this is the name of the folder used
# to store bookmarks created in Metro mode and share bookmarks between Metro
# and Desktop.
windows8TouchTitle=Windows 8 Touch
OrganizerQueryHistory=History
OrganizerQueryDownloads=Downloads

View File

@ -8,6 +8,13 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
// Custom factory object to ensure that we're a singleton
const BrowserStartupServiceFactory = {
_instance: null,
@ -62,7 +69,35 @@ BrowserStartup.prototype = {
Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
BookmarkJSONUtils.importFromURL("chrome://browser/locale/bookmarks.json", false);
Task.spawn(function() {
yield BookmarkJSONUtils.importFromURL("chrome://browser/locale/bookmarks.json", false);
// Create the new smart bookmark.
const MAX_RESULTS = 10;
const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
// Place the Metro folder at the end of the smart bookmarks list.
let maxIndex = Math.max.apply(null,
PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO).map(id => {
return PlacesUtils.bookmarks.getItemIndex(id);
}));
let smartBookmarkId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
NetUtil.newURI("place:folder=" +
PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0] +
"&queryType=" +
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
"&maxResults=" + MAX_RESULTS +
"&excludeQueries=1"),
maxIndex + 1,
PlacesUtils.getString("windows8TouchTitle"));
PlacesUtils.annotations.setItemAnnotation(smartBookmarkId,
SMART_BOOKMARKS_ANNO,
"Windows8Touch", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
});
},
_startupActions: function() {

View File

@ -174,6 +174,14 @@
margin-bottom: -4px;
}
/* Tooltip: Font Family Previewer Text */
.devtools-tooltip-font-previewer-text {
max-width: 400px;
line-height: 1.5;
font-size: 150%;
text-align: center;
}
/* Tooltip: Alert Icon */
.devtools-tooltip-alert-icon {

View File

@ -312,6 +312,10 @@ div.CodeMirror span.eval-text {
border-bottom: 1px solid #434850;
}
.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
color: white;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

View File

@ -321,6 +321,10 @@ div.CodeMirror span.eval-text {
border-bottom: 1px solid #d9e1e8;
}
.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
color: black;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

View File

@ -6,7 +6,8 @@
%include downloads.css
%undef WINDOWS_AERO
@media (-moz-windows-default-theme) {
@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
(-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
richlistitem[type="download"] {
border: 1px solid transparent;
border-bottom: 1px solid hsl(213,40%,90%);

View File

@ -20,10 +20,20 @@
#downloadsHistory {
background: transparent;
color: -moz-nativehyperlinktext;
cursor: pointer;
}
%ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
#downloadsHistory {
color: -moz-nativehyperlinktext;
}
%ifdef WINDOWS_AERO
}
%endif
#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
outline: 1px -moz-dialogtext dotted;
outline-offset: -1px;
@ -34,18 +44,47 @@
margin: 1em;
}
#downloadsPanel[hasdownloads] > #downloadsFooter {
background-color: hsla(210,4%,10%,.04);
box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
transition-duration: 150ms;
transition-property: background-color;
}
#downloadsPanel[hasdownloads] > #downloadsFooter:hover {
background-color: hsla(210,4%,10%,.05);
}
#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
background-color: hsla(210,4%,10%,.1);
box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
}
%ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
@media (-moz-windows-default-theme) {
#downloadsPanel[hasdownloads] > #downloadsFooter {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
transition-duration: 0s;
}
#downloadsPanel[hasdownloads] > #downloadsFooter,
#downloadsPanel[hasdownloads] > #downloadsFooter:hover,
#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
%ifdef WINDOWS_AERO
background-color: #f1f5fb;
%else
background-color: hsla(216,45%,88%,.98);
%endif
box-shadow: 0px 1px 2px rgb(204,214,234) inset;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
}
%ifdef WINDOWS_AERO
}
%endif
/*** Downloads Summary and List items ***/
@ -166,15 +205,41 @@ richlistitem[type="download"]:first-child {
/*** Highlighted list items ***/
#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
background-color: hsla(210,4%,10%,.08);
outline: 1px solid hsla(210,4%,10%,.1);
outline-offset: -1px;
cursor: pointer;
}
#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
background-color: hsla(210,4%,10%,.15);
outline: 1px solid hsla(210,4%,10%,.15);
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
}
%ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
border-radius: 3px;
outline: 0;
border-top: 1px solid hsla(0,0%,100%,.2);
border-bottom: 1px solid hsla(0,0%,0%,.2);
background-color: Highlight;
color: HighlightText;
cursor: pointer;
}
#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
background-color: Highlight;
outline: 0;
box-shadow: none;
}
%ifdef WINDOWS_AERO
}
%endif
/*** Button icons ***/
.downloadButton.downloadCancel {

View File

@ -1,5 +1,5 @@
[DEFAULT]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # b2g-desktop(bug 979446, frequent failures)
skip-if = buildapp == 'b2g' # bug 979446, frequent failures
support-files =
serve_file.sjs

View File

@ -2746,9 +2746,16 @@ Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp)
return false;
for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
if (c->options().invisibleToDebugger())
continue;
c->zone()->scheduledForDestruction = false;
GlobalObject *global = c->maybeGlobal();
if (cx->runtime()->isSelfHostingGlobal(global))
continue;
if (global) {
/*
* We pulled |global| out of nowhere, so it's possible that it was

View File

@ -104,7 +104,7 @@ public class HomeConfigInvalidator implements GeckoEventListener {
try {
if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
handlePanelInstall(createPanelConfigFromMessage(message));
handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
} else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
final String panelId = message.getString(JSON_KEY_PANEL_ID);
@ -126,20 +126,23 @@ public class HomeConfigInvalidator implements GeckoEventListener {
/**
* Adds a new PanelConfig to the HomeConfig.
*
* This posts the invalidation of HomeConfig immediately.
*
* @param panelConfig panel to add
*/
public void installPanel(PanelConfig panelConfig) {
handlePanelInstall(panelConfig);
Log.d(LOGTAG, "installPanel: " + panelConfig.getTitle());
handlePanelInstall(panelConfig, InvalidationMode.IMMEDIATE);
}
/**
* Runs in the gecko thread.
*/
private void handlePanelInstall(PanelConfig panelConfig) {
private void handlePanelInstall(PanelConfig panelConfig, InvalidationMode mode) {
mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
scheduleInvalidation(InvalidationMode.DELAYED);
scheduleInvalidation(mode);
}
/**

View File

@ -224,6 +224,10 @@ public class HomePager extends ViewPager {
if (mDecor != null) {
mDecor.onPageSelected(item);
}
if (mHomeBanner != null) {
mHomeBanner.setActive(item == mDefaultPageIndex);
}
}
@Override
@ -300,22 +304,30 @@ public class HomePager extends ViewPager {
// in the pager.
setAdapter(adapter);
// Use the default panel as defined in the HomePager's configuration
// if the initial panel wasn't explicitly set by the load() caller,
// or if the initial panel is not found in the adapter.
final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
if (itemPosition > -1) {
setCurrentItem(itemPosition, false);
mInitialPanelId = null;
if (count == 0) {
mDefaultPageIndex = -1;
// Hide the banner if there are no enabled panels.
if (mHomeBanner != null) {
mHomeBanner.setActive(false);
}
} else {
for (int i = 0; i < count; i++) {
final PanelConfig panelConfig = enabledPanels.get(i);
if (panelConfig.isDefault()) {
if (enabledPanels.get(i).isDefault()) {
mDefaultPageIndex = i;
setCurrentItem(i, false);
break;
}
}
// Use the default panel if the initial panel wasn't explicitly set by the
// load() caller, or if the initial panel is not found in the adapter.
final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
if (itemPosition > -1) {
setCurrentItem(itemPosition, false);
mInitialPanelId = null;
} else {
setCurrentItem(mDefaultPageIndex, false);
}
}
}

View File

@ -19,6 +19,7 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
@ -135,11 +136,20 @@ public class HomePanelPicker extends FragmentActivity {
private void installNewPanelAndQuit(PanelInfo panelInfo) {
final PanelConfig newPanelConfig = panelInfo.toPanelConfig();
HomeConfigInvalidator.getInstance().installPanel(newPanelConfig);
showToastForNewPanel(newPanelConfig);
setResult(Activity.RESULT_OK);
finish();
}
private void showToastForNewPanel(PanelConfig panelConfig) {
String panelName = panelConfig.getTitle();
// Display toast.
final String successMsg = getResources().getString(R.string.home_add_panel_installed, panelName);
Toast.makeText(this, successMsg, Toast.LENGTH_SHORT).show();
}
// ViewHolder for rows of the panel picker.
private static class PanelRow {
final TextView title;

View File

@ -93,6 +93,10 @@
<!ENTITY pref_home_add_panel "Add panel">
<!ENTITY home_add_panel_title "Add new panel">
<!ENTITY home_add_panel_empty "Sorry, we couldn\'t find any panels for you to add.">
<!-- Localization note (home_add_panel_installed):
The &formatS; will be replaced with the name of the new panel the user just
selected to be added to the home page. -->
<!ENTITY home_add_panel_installed "\'&formatS;\' added to homepage">
<!ENTITY pref_category_home_content_settings "Content settings">
<!ENTITY pref_home_updates "Automatic updates">
<!ENTITY pref_home_updates_enabled "Enabled">

View File

@ -75,6 +75,7 @@ public class GeckoPreferences
private static boolean sIsCharEncodingEnabled = false;
private boolean mInitialized = false;
private int mPrefsRequestId = 0;
private PanelsPreferenceCategory mPanelsPreferenceCategory;
// These match keys in resources/xml*/preferences*.xml
private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
@ -279,9 +280,8 @@ public class GeckoPreferences
case HomePanelPicker.REQUEST_CODE_ADD_PANEL:
switch (resultCode) {
case Activity.RESULT_OK:
// XXX: Bug 976925 - UI after adding a panel.
setResult(RESULT_CODE_EXIT_SETTINGS);
finish();
// Panel installed, refresh panels list.
mPanelsPreferenceCategory.refresh();
break;
case Activity.RESULT_CANCELED:
// Dialog was cancelled, do nothing.
@ -348,6 +348,8 @@ public class GeckoPreferences
i--;
continue;
}
} else if (pref instanceof PanelsPreferenceCategory) {
mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref;
}
setupPreferences((PreferenceGroup) pref, prefs);
} else {

View File

@ -69,6 +69,21 @@ public class PanelsPreferenceCategory extends CustomListCategory {
mLoadTask.execute();
}
/**
* Reload the Home Panels list from HomeConfig.
*/
public void refresh() {
// Clear all the existing home panels, but leave the
// first item (Add panels).
int prefCount = getPreferenceCount();
while (prefCount > 1) {
removePreference(getPreference(1));
prefCount--;
}
loadHomeConfig();
}
private void displayHomeConfig(HomeConfig.State configState) {
for (PanelConfig panelConfig : configState) {
// Create and add the pref.

View File

@ -116,6 +116,7 @@
<string name="pref_home_add_panel">&pref_home_add_panel;</string>
<string name="home_add_panel_title">&home_add_panel_title;</string>
<string name="home_add_panel_empty">&home_add_panel_empty;</string>
<string name="home_add_panel_installed">&home_add_panel_installed;</string>
<string name="pref_category_home_content_settings">&pref_category_home_content_settings;</string>
<string name="pref_home_updates">&pref_home_updates;</string>
<string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>

View File

@ -7953,7 +7953,7 @@ var ExternalApps = {
Strings.browser.GetStringFromName("openInApp.ok"),
Strings.browser.GetStringFromName("openInApp.cancel")
]
}, function(result) {
}, (result) => {
if (result.button != 0) {
return;
}

View File

@ -342,6 +342,7 @@ add_task(function test_multiple_401_retry_once() {
// Request will have bad timestamp; client will retry once
try {
yield client.request("/maybe", method, credentials);
do_throw("Expected an error");
} catch (err) {
do_check_eq(err.code, 401);
}

View File

@ -67,22 +67,22 @@ AccountState.prototype = {
cert: null,
keyPair: null,
signedInUser: null,
whenVerifiedPromise: null,
whenKeysReadyPromise: null,
whenVerifiedDeferred: null,
whenKeysReadyDeferred: null,
get isCurrent() this.fxaInternal && this.fxaInternal.currentAccountState === this,
abort: function() {
if (this.whenVerifiedPromise) {
this.whenVerifiedPromise.reject(
if (this.whenVerifiedDeferred) {
this.whenVerifiedDeferred.reject(
new Error("Verification aborted; Another user signing in"));
this.whenVerifiedPromise = null;
this.whenVerifiedDeferred = null;
}
if (this.whenKeysReadyPromise) {
this.whenKeysReadyPromise.reject(
if (this.whenKeysReadyDeferred) {
this.whenKeysReadyDeferred.reject(
new Error("Verification aborted; Another user signing in"));
this.whenKeysReadyPromise = null;
this.whenKeysReadyDeferred = null;
}
this.cert = null;
this.keyPair = null;
@ -484,13 +484,13 @@ FxAccountsInternal.prototype = {
if (data.kA && data.kB) {
return data;
}
if (!currentState.whenKeysReadyPromise) {
currentState.whenKeysReadyPromise = Promise.defer();
if (!currentState.whenKeysReadyDeferred) {
currentState.whenKeysReadyDeferred = Promise.defer();
this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
currentState.whenKeysReadyPromise.resolve(data);
currentState.whenKeysReadyDeferred.resolve(data);
});
}
return currentState.whenKeysReadyPromise.promise;
return currentState.whenKeysReadyDeferred.promise;
}).then(result => currentState.resolve(result));
},
@ -607,11 +607,11 @@ FxAccountsInternal.prototype = {
log.debug("already verified");
return currentState.resolve(data);
}
if (!currentState.whenVerifiedPromise) {
if (!currentState.whenVerifiedDeferred) {
log.debug("whenVerified promise starts polling for verified email");
this.pollEmailStatus(currentState, data.sessionToken, "start");
}
return currentState.whenVerifiedPromise.promise.then(
return currentState.whenVerifiedDeferred.promise.then(
result => currentState.resolve(result)
);
},
@ -629,8 +629,8 @@ FxAccountsInternal.prototype = {
// if the user requested the verification email to be resent while we
// were already polling for receipt of an earlier email.
this.pollTimeRemaining = this.POLL_SESSION;
if (!currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise = Promise.defer();
if (!currentState.whenVerifiedDeferred) {
currentState.whenVerifiedDeferred = Promise.defer();
}
}
@ -647,9 +647,9 @@ FxAccountsInternal.prototype = {
})
.then((data) => {
// Now that the user is verified, we can proceed to fetch keys
if (currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise.resolve(data);
delete currentState.whenVerifiedPromise;
if (currentState.whenVerifiedDeferred) {
currentState.whenVerifiedDeferred.resolve(data);
delete currentState.whenVerifiedDeferred;
}
});
} else {
@ -661,11 +661,11 @@ FxAccountsInternal.prototype = {
this.pollEmailStatus(currentState, sessionToken, "timer")}, this.POLL_STEP);
log.debug("started timer " + this.currentTimer);
} else {
if (currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise.reject(
if (currentState.whenVerifiedDeferred) {
currentState.whenVerifiedDeferred.reject(
new Error("User email verification timed out.")
);
delete currentState.whenVerifiedPromise;
delete currentState.whenVerifiedDeferred;
}
}
}

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/utils.js");
@ -12,24 +14,43 @@ function run_test() {
run_next_test();
}
// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
let ACCOUNT_KEYS = {
keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+
"9091929394959697 98999a9b9c9d9e9f"),
response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+
"d83c33c9cd2c2de2 d66b222613364636"+
"c2c0f8cfbb7c6304 72c0bd88451342c6"+
"c05b14ce342c5ad4 6ad89e84464c993c"+
"3927d30230157d08 17a077eef4b20d97"+
"6f7a97363faf3f06 4c003ada7d01aa70"),
kA: h("2021222324252627 28292a2b2c2d2e2f"+
"3031323334353637 38393a3b3c3d3e3f"),
wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+
"5051525354555657 58595a5b5c5d5e5f"),
};
// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
let SESSION_KEYS = {
sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
"b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
tokenID: h("c0a29dcf46174973 da1378696e4c82ae"+
"10f723cf4f4d9f75 e39f4ae3851595ab"),
reqHMACkey: h("9d8f22998ee7f579 8b887042466b72d5"+
"3e56ab0c094388bf 65831f702d2febc0"),
};
function deferredStop(server) {
let deferred = Promise.defer();
server.stop(deferred.resolve);
return deferred.promise;
}
add_test(function test_hawk_credentials() {
let client = new FxAccountsClient();
let sessionToken = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
let result = client._deriveHawkCredentials(sessionToken, "session");
do_check_eq(result.id, "639503a218ffbb62983e9628be5cd64a0438d0ae81b2b9dadeb900a83470bc6b");
do_check_eq(CommonUtils.bytesAsHex(result.key), "3a0188943837ab228fe74e759566d0e4837cbcc7494157aac4da82025b2811b2");
run_next_test();
});
add_task(function test_authenticated_get_request() {
let message = "{\"msg\": \"Great Success!\"}";
let credentials = {
@ -94,6 +115,7 @@ add_task(function test_500_error() {
try {
yield client._request("/foo", method);
do_throw("Expected to catch an exception");
} catch (e) {
do_check_eq(500, e.code);
do_check_eq("Internal Server Error", e.message);
@ -171,12 +193,14 @@ add_task(function test_signUp() {
created = true;
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(creationMessage, creationMessage.length);
response.bodyOutputStream.write(creationMessage, creationMessage.length);
return;
}
// Error trying to create same account a second time
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -189,6 +213,7 @@ add_task(function test_signUp() {
// Try to create account again. Triggers error path.
try {
result = yield client.signUp('andré@example.org', 'pässwörd');
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(101, expectedError.errno);
}
@ -199,6 +224,7 @@ add_task(function test_signUp() {
add_task(function test_signIn() {
let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
let server = httpd_setup({
"/account/login": function(request, response) {
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
@ -207,12 +233,14 @@ add_task(function test_signIn() {
if (jsonBody.email == "mé@example.com") {
do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
return;
}
// Error trying to sign in to nonexistent account
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -223,6 +251,7 @@ add_task(function test_signIn() {
// Trigger error path
try {
result = yield client.signIn("yøü@bad.example.org", "nofear");
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -241,12 +270,14 @@ add_task(function test_signOut() {
signedOut = true;
do_check_true(request.hasHeader("Authorization"));
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
return;
}
// Error trying to sign out of nonexistent account
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -257,6 +288,7 @@ add_task(function test_signOut() {
// Trigger error path
try {
result = yield client.signOut("FakeSession");
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -274,13 +306,16 @@ add_task(function test_recoveryEmailStatus() {
do_check_true(request.hasHeader("Authorization"));
if (tries === 0) {
tries += 1;
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(emailStatus, emailStatus.length);
response.bodyOutputStream.write(emailStatus, emailStatus.length);
return;
}
// Second call gets an error trying to query a nonexistent account
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -291,6 +326,7 @@ add_task(function test_recoveryEmailStatus() {
// Trigger error path
try {
result = yield client.recoveryEmailStatus("some bogus session");
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -307,13 +343,16 @@ add_task(function test_resendVerificationEmail() {
"/recovery_email/resend_code": function(request, response) {
do_check_true(request.hasHeader("Authorization"));
if (tries === 0) {
tries += 1;
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
return;
}
// Second call gets an error trying to query a nonexistent account
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -324,6 +363,7 @@ add_task(function test_resendVerificationEmail() {
// Trigger error path
try {
result = yield client.resendVerificationEmail("some bogus session");
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -332,25 +372,11 @@ add_task(function test_resendVerificationEmail() {
});
add_task(function test_accountKeys() {
// Vectors: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
let keyFetch = h("8081828384858687 88898a8b8c8d8e8f"+
"9091929394959697 98999a9b9c9d9e9f");
let response = h("ee5c58845c7c9412 b11bbd20920c2fdd"+
"d83c33c9cd2c2de2 d66b222613364636"+
"c2c0f8cfbb7c6304 72c0bd88451342c6"+
"c05b14ce342c5ad4 6ad89e84464c993c"+
"3927d30230157d08 17a077eef4b20d97"+
"6f7a97363faf3f06 4c003ada7d01aa70");
let kA = h("2021222324252627 28292a2b2c2d2e2f"+
"3031323334353637 38393a3b3c3d3e3f");
let wrapKB = h("4041424344454647 48494a4b4c4d4e4f"+
"5051525354555657 58595a5b5c5d5e5f");
let responseMessage = JSON.stringify({bundle: response});
// Four calls to accountKeys(). The first one should work correctly, and we
// should get a valid bundle back, in exchange for our keyFetch token, from
// which we correctly derive kA and wrapKB. The subsequent three calls
// should all trigger separate error paths.
let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
let emptyMessage = "{}";
let attempt = 0;
@ -375,10 +401,12 @@ add_task(function test_accountKeys() {
case 3:
// Return gibberish to trigger client MAC error
let garbage = response;
garbage[0] = 0; // tweak a byte
// Tweak a byte
let garbageResponse = JSON.stringify({
bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
});
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(responseMessage, responseMessage.length);
response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
break;
case 4:
@ -393,27 +421,30 @@ add_task(function test_accountKeys() {
let client = new FxAccountsClient(server.baseURI);
// First try, all should be good
let result = yield client.accountKeys(keyFetch);
do_check_eq(CommonUtils.hexToBytes(kA), result.kA);
do_check_eq(CommonUtils.hexToBytes(wrapKB), result.wrapKB);
let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
// Second try, empty bundle should trigger error
try {
result = yield client.accountKeys(keyFetch);
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(expectedError.message, "failed to retrieve keys");
}
// Third try, bad bundle results in MAC error
try {
result = yield client.accountKeys(keyFetch);
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(expectedError.message, "error unbundling encryption keys");
}
// Fourth try, pretend account doesn't exist
try {
result = yield client.accountKeys(keyFetch);
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -437,12 +468,14 @@ add_task(function test_signCertificate() {
do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
do_check_eq(jsonBody.duration, 600);
response.setStatusLine(request.httpVersion, 200, "OK");
return response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
return;
}
// Second attempt, trigger error
response.setStatusLine(request.httpVersion, 400, "Bad request");
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
response.bodyOutputStream.write(errorMessage, errorMessage.length);
return;
},
});
@ -453,6 +486,7 @@ add_task(function test_signCertificate() {
// Account doesn't exist
try {
result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(102, expectedError.errno);
}
@ -502,27 +536,18 @@ add_task(function test_accountExists() {
let client = new FxAccountsClient(server.baseURI);
let result;
try {
result = yield client.accountExists("i.exist@example.com");
} catch(expectedError) {
do_check_eq(expectedError.code, 400);
do_check_eq(expectedError.errno, 103);
}
result = yield client.accountExists("i.exist@example.com");
do_check_true(result);
try {
result = yield client.accountExists("i.also.exist@example.com");
} catch(expectedError) {
do_check_eq(expectedError.errno, 103);
}
result = yield client.accountExists("i.also.exist@example.com");
do_check_true(result);
try {
result = yield client.accountExists("i.dont.exist@example.com");
} catch(expectedError) {
do_check_eq(expectedError.errno, 102);
}
result = yield client.accountExists("i.dont.exist@example.com");
do_check_false(result);
try {
result = yield client.accountExists("i.break.things@example.com");
do_throw("Expected to catch an exception");
} catch(unexpectedError) {
do_check_eq(unexpectedError.code, 500);
}
@ -583,6 +608,17 @@ add_task(function test_email_case() {
yield deferredStop(server);
});
add_task(function test__deriveHawkCredentials() {
let client = new FxAccountsClient("https://example.org");
let credentials = client._deriveHawkCredentials(
SESSION_KEYS.sessionToken, "sessionToken");
do_check_eq(credentials.algorithm, "sha256");
do_check_eq(credentials.id, SESSION_KEYS.tokenID);
do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
});
// turn formatted test vectors into normal hex strings
function h(hexStr) {
return hexStr.replace(/\s+/g, "");

View File

@ -79,14 +79,17 @@ exports.dirname = dirname;
*
* Under Unix, this will return "/tmp/foo/bar".
*/
let join = function(path /*...*/) {
let join = function(...path) {
// If there is a path that starts with a "/", eliminate everything before
let paths = [];
for each(let i in arguments) {
if (i.length != 0 && i[0] == "/") {
paths = [i];
for (let subpath of path) {
if (subpath == null) {
throw new TypeError("invalid path component");
}
if (subpath.length != 0 && subpath[0] == "/") {
paths = [subpath];
} else {
paths.push(i);
paths.push(subpath);
}
}
return paths.join("/");

View File

@ -140,7 +140,10 @@ let join = function(...path) {
let paths = [];
let root;
let absolute = false;
for each(let subpath in path) {
for (let subpath of path) {
if (subpath == null) {
throw new TypeError("invalid path component");
}
let drive = this.winGetDrive(subpath);
if (drive) {
root = drive;
@ -179,6 +182,10 @@ exports.join = join;
* includes "\\\\").
*/
let winGetDrive = function(path) {
if (path == null) {
throw new TypeError("path is invalid");
}
if (path.startsWith("\\\\")) {
// UNC path
if (path.length == 2) {

View File

@ -4901,6 +4901,20 @@
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_ADDONDETACH_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_REMOTE_ADDONDETACH_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_TABDETACH_MS": {
"expires_in_version": "never",
"kind": "exponential",

View File

@ -259,7 +259,7 @@ function reloadApp(client, webappsActor, manifestURL) {
webappsActor,
manifestURL).
then((target) => {
// Request the ContentAppActor to reload the app
// Request the ContentActor to reload the app
let request = {
to: target.form.actor,
type: "reload",

View File

@ -237,6 +237,7 @@ this.DebuggerClient = function (aTransport)
// Map actor ID to client instance for each actor type.
this._threadClients = new Map;
this._addonClients = new Map;
this._tabClients = new Map;
this._tracerClients = new Map;
this._consoleClients = new Map;
@ -413,8 +414,10 @@ DebuggerClient.prototype = {
detachClients(this._consoleClients, () => {
detachClients(this._threadClients, () => {
detachClients(this._tabClients, () => {
this._transport.close();
this._transport = null;
detachClients(this._addonClients, () => {
this._transport.close();
this._transport = null;
});
});
});
});
@ -466,6 +469,31 @@ DebuggerClient.prototype = {
});
},
/**
* Attach to an addon actor.
*
* @param string aAddonActor
* The actor ID for the addon to attach.
* @param function aOnResponse
* Called with the response packet and a AddonClient
* (which will be undefined on error).
*/
attachAddon: function DC_attachAddon(aAddonActor, aOnResponse) {
let packet = {
to: aAddonActor,
type: "attach"
};
this.request(packet, aResponse => {
let addonClient;
if (!aResponse.error) {
addonClient = new AddonClient(this, aAddonActor);
this._addonClients[aAddonActor] = addonClient;
this.activeAddon = addonClient;
}
aOnResponse(aResponse, addonClient);
});
},
/**
* Attach to a Web Console actor.
*
@ -743,7 +771,12 @@ DebuggerClient.prototype = {
if (pool.has(actorID)) return pool;
}
return null;
}
},
/**
* Currently attached addon.
*/
activeAddon: null
}
eventSource(DebuggerClient.prototype);
@ -1049,6 +1082,36 @@ TabClient.prototype = {
eventSource(TabClient.prototype);
function AddonClient(aClient, aActor) {
this._client = aClient;
this._actor = aActor;
this.request = this._client.request;
}
AddonClient.prototype = {
get actor() { return this._actor; },
get _transport() { return this._client._transport; },
/**
* Detach the client from the addon actor.
*
* @param function aOnResponse
* Called with the response packet.
*/
detach: DebuggerClient.requester({
type: "detach"
}, {
after: function(aResponse) {
if (this._client.activeAddon === this._client._addonClients[this.actor]) {
this._client.activeAddon = null
}
delete this._client._addonClients[this.actor];
return aResponse;
},
telemetry: "ADDONDETACH"
})
};
/**
* A RootClient object represents a root actor on the server. Each
* DebuggerClient keeps a RootClient instance representing the root actor

View File

@ -7,57 +7,47 @@
/**
* Tab actor for documents living in a child process.
*
* Depends on BrowserTabActor, defined in webbrowser.js actor.
* Depends on TabActor, defined in webbrowser.js.
*/
/**
* Creates a tab actor for handling requests to the single tab, like
* attaching and detaching. ContentAppActor respects the actor factories
* attaching and detaching. ContentActor respects the actor factories
* registered with DebuggerServer.addTabActor.
*
* @param connection DebuggerServerConnection
* The conection to the client.
* @param browser browser
* The browser instance that contains this tab.
* @param chromeGlobal
* The content script global holding |content| and |docShell| properties for a tab.
*/
function ContentAppActor(connection, browser)
function ContentActor(connection, chromeGlobal)
{
BrowserTabActor.call(this, connection, browser);
TabActor.call(this, connection, chromeGlobal);
this._chromeGlobal = chromeGlobal;
}
ContentAppActor.prototype = Object.create(BrowserTabActor.prototype);
ContentActor.prototype = Object.create(TabActor.prototype);
ContentAppActor.prototype.constructor = ContentAppActor;
ContentActor.prototype.constructor = ContentActor;
Object.defineProperty(ContentAppActor.prototype, "title", {
Object.defineProperty(ContentActor.prototype, "docShell", {
get: function() {
return this.browser.title;
return this._chromeGlobal.docShell;
},
enumerable: true,
configurable: false
});
Object.defineProperty(ContentAppActor.prototype, "url", {
get: function() {
return this.browser.document.documentURI;
},
enumerable: true,
configurable: false
});
Object.defineProperty(ContentAppActor.prototype, "window", {
get: function() {
return this.browser;
},
enumerable: true,
configurable: false
});
ContentActor.prototype.exit = function() {
TabActor.prototype.exit.call(this);
this._chromeGlobal = null;
};
// Override grip just to rename this._tabActorPool to this._tabActorPool2
// in order to prevent it to be cleaned on detach.
// We have to keep tab actors alive as we keep the ContentAppActor
// We have to keep tab actors alive as we keep the ContentActor
// alive after detach and reuse it for multiple debug sessions.
ContentAppActor.prototype.grip = function () {
ContentActor.prototype.grip = function () {
let response = {
'actor': this.actorID,
'title': this.title,

View File

@ -187,7 +187,7 @@ let HighlighterActor = protocol.ActorClass({
* - On a firefox desktop content page: tabActor is a BrowserTabActor from
* which the browser property will give us a target we can use to listen to
* events, even in nested iframes.
* - On B2G: tabActor is a ContentAppActor which doesn't have a browser but
* - On B2G: tabActor is a ContentActor which doesn't have a browser but
* since it overrides BrowserTabActor, it does get a browser property
* anyway, which points to its window object.
* - When using the Browser Toolbox (to inspect firefox desktop): tabActor is
@ -196,7 +196,7 @@ let HighlighterActor = protocol.ActorClass({
*/
_getPickerListenerTarget: function() {
let actor = this._tabActor;
return actor.isRootActor ? actor.window : actor.browser;
return actor.isRootActor ? actor.window : actor.chromeEventHandler;
},
_startPickerListeners: function() {

View File

@ -4562,6 +4562,96 @@ update(ChromeDebuggerActor.prototype, {
}
});
/**
* Creates an actor for handling add-on debugging. AddonThreadActor is
* a thin wrapper over ThreadActor.
*
* @param aConnection object
* The DebuggerServerConnection with which this AddonThreadActor
* is associated. (Currently unused, but required to make this
* constructor usable with addGlobalActor.)
*
* @param aHooks object
* An object with preNest and postNest methods for calling
* when entering and exiting a nested event loops.
*
* @param aAddonID string
* ID of the add-on this actor will debug. It will be used to
* filter out globals marked for debugging.
*/
function AddonThreadActor(aConnect, aHooks, aAddonID) {
this.addonID = aAddonID;
ThreadActor.call(this, aHooks);
}
AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
update(AddonThreadActor.prototype, {
constructor: AddonThreadActor,
// A constant prefix that will be used to form the actor ID by the server.
actorPrefix: "addonThread",
/**
* Override the eligibility check for scripts and sources to make
* sure every script and source with a URL is stored when debugging
* add-ons.
*/
_allowSource: (aSourceURL) => !!aSourceURL,
/**
* An object that will be used by ThreadActors to tailor their
* behaviour depending on the debugging context being required (chrome,
* addon or content). The methods that this object provides must
* be bound to the ThreadActor before use.
*/
globalManager: {
findGlobals: function ADA_findGlobals() {
for (let global of this.dbg.findAllGlobals()) {
if (this._checkGlobal(global)) {
this.dbg.addDebuggee(global);
}
}
},
/**
* A function that the engine calls when a new global object
* has been created.
*
* @param aGlobal Debugger.Object
* The new global object that was created.
*/
onNewGlobal: function ADA_onNewGlobal(aGlobal) {
if (this._checkGlobal(aGlobal)) {
this.addDebuggee(aGlobal);
// Notify the client.
this.conn.send({
from: this.actorID,
type: "newGlobal",
// TODO: after bug 801084 lands see if we need to JSONify this.
hostAnnotations: aGlobal.hostAnnotations
});
}
}
},
/**
* Checks if the provided global belongs to the debugged add-on.
*
* @param aGlobal Debugger.Object
*/
_checkGlobal: function ADA_checkGlobal(aGlobal) {
let metadata;
try {
// This will fail for non-Sandbox objects, hence the try-catch block.
metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
} catch (e) {
}
return metadata && metadata.addonID === this.addonID;
}
});
/**
* Manages the sources for a thread. Handles source maps, locations in the

View File

@ -120,7 +120,7 @@ function WebappsActor(aConnection) {
promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
// Keep reference of already created app actors.
// key: app frame message manager, value: ContentTabActor's grip() value
// key: app frame message manager, value: ContentActor's grip() value
this._appActorsMap = new Map();
this.conn = aConnection;
@ -789,73 +789,6 @@ WebappsActor.prototype = {
});
},
_connectToApp: function (aFrame) {
let deferred = Promise.defer();
let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
let childTransport, prefix;
let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
mm.removeMessageListener("debug:actor", onActorCreated);
dump("***** Got debug:actor\n");
let { actor, appId } = msg.json;
prefix = msg.json.prefix;
// Pipe Debugger message from/to parent/child via the message manager
childTransport = new ChildDebuggerTransport(mm, prefix);
childTransport.hooks = {
onPacket: this.conn.send.bind(this.conn),
onClosed: function () {}
};
childTransport.ready();
this.conn.setForwarding(prefix, childTransport);
debug("establishing forwarding for app with prefix " + prefix);
this._appActorsMap.set(mm, actor);
deferred.resolve(actor);
}).bind(this);
mm.addMessageListener("debug:actor", onActorCreated);
let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
if (subject == mm) {
Services.obs.removeObserver(onMessageManagerDisconnect, topic);
if (childTransport) {
// If we have a child transport, the actor has already
// been created. We need to stop using this message manager.
childTransport.close();
this.conn.cancelForwarding(prefix);
} else {
// Otherwise, the app has been closed before the actor
// had a chance to be created, so we are not able to create
// the actor.
deferred.resolve(null);
}
let actor = this._appActorsMap.get(mm);
if (actor) {
// The ContentAppActor within the child process doesn't necessary
// have to time to uninitialize itself when the app is closed/killed.
// So ensure telling the client that the related actor is detached.
this.conn.send({ from: actor.actor,
type: "tabDetached" });
this._appActorsMap.delete(mm);
}
}
}).bind(this);
Services.obs.addObserver(onMessageManagerDisconnect,
"message-manager-disconnect", false);
let prefixStart = this.conn.prefix + "child";
mm.sendAsyncMessage("debug:connect", { prefix: prefixStart });
return deferred.promise;
},
getAppActor: function ({ manifestURL }) {
debug("getAppActor\n");
@ -884,13 +817,21 @@ WebappsActor.prototype = {
// Only create a new actor, if we haven't already
// instanciated one for this connection.
let map = this._appActorsMap;
let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader
.messageManager;
let actor = this._appActorsMap.get(mm);
let actor = map.get(mm);
if (!actor) {
return this._connectToApp(appFrame)
.then(function (actor) ({ actor: actor }));
let onConnect = actor => {
map.set(mm, actor);
return { actor: actor };
};
let onDisconnect = mm => {
map.delete(mm);
};
return DebuggerServer.connectToChild(this.conn, mm, onDisconnect)
.then(onConnect);
}
return { actor: actor };

View File

@ -217,6 +217,8 @@ BrowserTabList.prototype.getList = function() {
// the actors. Thus, the sequence yielded is always a snapshot of the
// actors that were live when we began the iteration.
let actorPromises = [];
// Iterate over all navigator:browser XUL windows.
for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
let selectedBrowser = this._getSelectedBrowser(win);
@ -232,10 +234,20 @@ BrowserTabList.prototype.getList = function() {
// Do we have an existing actor for this browser? If not, create one.
let actor = this._actorByBrowser.get(browser);
if (actor) {
actorPromises.push(promise.resolve(actor));
foundCount++;
} else if (browser.isRemoteBrowser) {
actor = new RemoteBrowserTabActor(this._connection, browser);
this._actorByBrowser.set(browser, actor);
let promise = actor.connect().then((form) => {
actor._form = form;
return actor;
});
actorPromises.push(promise);
} else {
actor = new BrowserTabActor(this._connection, browser, win.gBrowser);
this._actorByBrowser.set(browser, actor);
actorPromises.push(promise.resolve(actor));
}
// Set the 'selected' properties on all actors correctly.
@ -249,7 +261,7 @@ BrowserTabList.prototype.getList = function() {
this._mustNotify = true;
this._checkListening();
return promise.resolve([actor for ([_, actor] of this._actorByBrowser)]);
return promise.all(actorPromises);
};
Object.defineProperty(BrowserTabList.prototype, 'onListChanged', {
@ -461,21 +473,22 @@ BrowserTabList.prototype.onCloseWindow = DevToolsUtils.makeInfallible(function(a
/**
* Creates a tab actor for handling requests to a browser tab, like
* attaching and detaching. BrowserTabActor respects the actor factories
* attaching and detaching. TabActor respects the actor factories
* registered with DebuggerServer.addTabActor.
*
* This class is subclassed by BrowserTabActor and
* ContentActor. Subclasses are expected to implement a getter
* the docShell properties.
*
* @param aConnection DebuggerServerConnection
* The conection to the client.
* @param aBrowser browser
* The browser instance that contains this tab.
* @param aTabBrowser tabbrowser
* The tabbrowser that can receive nsIWebProgressListener events.
* @param aChromeEventHandler
* An object on which listen for DOMWindowCreated and pageshow events.
*/
function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
function TabActor(aConnection, aChromeEventHandler)
{
this.conn = aConnection;
this._browser = aBrowser;
this._tabbrowser = aTabBrowser;
this._chromeEventHandler = aChromeEventHandler;
this._tabActorPool = null;
// A map of actor names to actor instances provided by extensions.
this._extraActors = {};
@ -483,13 +496,11 @@ function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
this._onWindowCreated = this.onWindowCreated.bind(this);
}
// XXX (bug 710213): BrowserTabActor attach/detach/exit/disconnect is a
// XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
// *complete* mess, needs to be rethought asap.
BrowserTabActor.prototype = {
get browser() { return this._browser; },
get exited() { return !this.browser; },
TabActor.prototype = {
get exited() { return !this._chromeEventHandler; },
get attached() { return !!this._attached; },
_tabPool: null,
@ -503,21 +514,61 @@ BrowserTabActor.prototype = {
// A constant prefix that will be used to form the actor ID by the server.
actorPrefix: "tab",
/**
* An object on which listen for DOMWindowCreated and pageshow events.
*/
get chromeEventHandler() {
return this._chromeEventHandler;
},
/**
* Getter for the tab's doc shell.
*/
get docShell() {
throw "The docShell getter should be implemented by a subclass of TabActor";
},
/**
* Getter for the tab content's DOM window.
*/
get window() {
return this.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
},
/**
* Getter for the nsIWebProgress for watching this window.
*/
get webProgress() {
return this.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
},
/**
* Getter for the nsIWebNavigation for the tab.
*/
get webNavigation() {
return this.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
},
/**
* Getter for the tab's document.
*/
get contentDocument() {
return this.webNavigation.document;
},
/**
* Getter for the tab title.
* @return string
* Tab title.
*/
get title() {
let title = this.browser.contentTitle;
// If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
// tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
// as the title.
if (!title && this._tabbrowser) {
title = this._tabbrowser
._getTabForContentWindow(this.window).label;
}
return title;
return this.contentDocument.contentTitle;
},
/**
@ -526,41 +577,14 @@ BrowserTabActor.prototype = {
* Tab URL.
*/
get url() {
if (this.browser.currentURI) {
return this.browser.currentURI.spec;
if (this.webNavigation.currentURI) {
return this.webNavigation.currentURI.spec;
}
// Abrupt closing of the browser window may leave callbacks without a
// currentURI.
return null;
},
/**
* Getter for the tab content window, will be used by child actors to target
* the right window.
* @return nsIDOMWindow
* Tab content window.
*/
get window() {
if (this.browser instanceof Ci.nsIDOMWindow) {
return this.browser;
} else if (this.browser instanceof Ci.nsIDOMElement) {
return this.browser.contentWindow;
} else {
return null;
}
},
/**
* Getter for the best nsIWebProgress for to watching this window.
*/
get webProgress() {
return this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
},
form: function BTA_form() {
dbg_assert(!this.exited,
"grip() shouldn't be called on exited browser actor.");
@ -611,8 +635,7 @@ BrowserTabActor.prototype = {
type: "tabDetached" });
}
this._browser = null;
this._tabbrowser = null;
this._chromeEventHandler = null;
},
/* Support for DebuggerServer.addTabActor. */
@ -636,11 +659,9 @@ BrowserTabActor.prototype = {
this._pushContext();
// Watch for globals being created in this tab.
this.browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
this.browser.addEventListener("pageshow", this._onWindowCreated, true);
if (this._tabbrowser) {
this._progressListener = new DebuggerProgressListener(this);
}
this.chromeEventHandler.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
this.chromeEventHandler.addEventListener("pageshow", this._onWindowCreated, true);
this._progressListener = new DebuggerProgressListener(this);
this._attached = true;
},
@ -682,12 +703,10 @@ BrowserTabActor.prototype = {
return false;
}
if (this._progressListener) {
this._progressListener.destroy();
}
this._progressListener.destroy();
this.browser.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
this.browser.removeEventListener("pageshow", this._onWindowCreated, true);
this.chromeEventHandler.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
this.chromeEventHandler.removeEventListener("pageshow", this._onWindowCreated, true);
this._popContext();
@ -736,7 +755,7 @@ BrowserTabActor.prototype = {
// subsequent navigation event packet.
Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
this.window.location.reload();
}, "BrowserTabActor.prototype.onReload's delayed body"), 0);
}, "TabActor.prototype.onReload's delayed body"), 0);
return {};
},
@ -748,7 +767,7 @@ BrowserTabActor.prototype = {
// subsequent navigation event packet.
Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
this.window.location = aRequest.url;
}, "BrowserTabActor.prototype.onNavigateTo's delayed body"), 0);
}, "TabActor.prototype.onNavigateTo's delayed body"), 0);
return {};
},
@ -798,12 +817,8 @@ BrowserTabActor.prototype = {
let enable = Ci.nsIRequest.LOAD_NORMAL;
let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
if (this.window) {
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
docShell.defaultLoadFlags = allow ? enable : disable;
if (this.docShell) {
this.docShell.defaultLoadFlags = allow ? enable : disable;
}
},
@ -811,12 +826,8 @@ BrowserTabActor.prototype = {
* Disable or enable JS via docShell.
*/
_setJavascriptEnabled: function(allow) {
if (this.window) {
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
docShell.allowJavascript = allow;
if (this.docShell) {
this.docShell.allowJavascript = allow;
}
},
@ -824,34 +835,26 @@ BrowserTabActor.prototype = {
* Return cache allowed status.
*/
_getCacheEnabled: function() {
if (!this.window) {
if (!this.docShell) {
// The tab is already closed.
return null;
}
let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
return docShell.defaultLoadFlags !== disable;
return this.docShell.defaultLoadFlags !== disable;
},
/**
* Return JS allowed status.
*/
_getJavascriptEnabled: function() {
if (!this.window) {
if (!this.docShell) {
// The tab is already closed.
return null;
}
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
return docShell.allowJavascript;
return this.docShell.allowJavascript;
},
/**
@ -900,7 +903,7 @@ BrowserTabActor.prototype = {
if (!this._attached || (evt.type == "pageshow" && !evt.persisted)) {
return;
}
if (evt.target === this.browser.contentDocument ) {
if (evt.target === this.contentDocument) {
this.threadActor.clearDebuggees();
if (this.threadActor.dbg) {
this.threadActor.dbg.enabled = true;
@ -914,7 +917,7 @@ BrowserTabActor.prototype = {
if (this.threadActor.attached) {
this.threadActor.findGlobals();
}
}, "BrowserTabActor.prototype.onWindowCreated"),
}, "TabActor.prototype.onWindowCreated"),
/**
* Tells if the window.console object is native or overwritten by script in
@ -935,12 +938,100 @@ BrowserTabActor.prototype = {
/**
* The request types this actor can handle.
*/
BrowserTabActor.prototype.requestTypes = {
"attach": BrowserTabActor.prototype.onAttach,
"detach": BrowserTabActor.prototype.onDetach,
"reload": BrowserTabActor.prototype.onReload,
"navigateTo": BrowserTabActor.prototype.onNavigateTo,
"reconfigure": BrowserTabActor.prototype.onReconfigure
TabActor.prototype.requestTypes = {
"attach": TabActor.prototype.onAttach,
"detach": TabActor.prototype.onDetach,
"reload": TabActor.prototype.onReload,
"navigateTo": TabActor.prototype.onNavigateTo,
"reconfigure": TabActor.prototype.onReconfigure
};
/**
* Creates a tab actor for handling requests to a single in-process
* <browser> tab. Most of the implementation comes from TabActor.
*
* @param aConnection DebuggerServerConnection
* The conection to the client.
* @param aBrowser browser
* The browser instance that contains this tab.
* @param aTabBrowser tabbrowser
* The tabbrowser that can receive nsIWebProgressListener events.
*/
function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
{
TabActor.call(this, aConnection, aBrowser);
this._browser = aBrowser;
this._tabbrowser = aTabBrowser;
}
BrowserTabActor.prototype = Object.create(TabActor.prototype);
BrowserTabActor.prototype.constructor = BrowserTabActor;
Object.defineProperty(BrowserTabActor.prototype, "docShell", {
get: function() {
return this._browser.docShell;
},
enumerable: true,
configurable: false
});
Object.defineProperty(BrowserTabActor.prototype, "title", {
get: function() {
let title = this.contentDocument.contentTitle;
// If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
// tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
// as the title.
if (!title && this._tabbrowser) {
title = this._tabbrowser._getTabForContentWindow(this.window).label;
}
return title;
},
enumerable: true,
configurable: false
});
Object.defineProperty(BrowserTabActor.prototype, "browser", {
get: function() {
return this._browser;
},
enumerable: true,
configurable: false
});
BrowserTabActor.prototype.exit = function() {
TabActor.prototype.exit.call(this);
this._browser = null;
this._tabbrowser = null;
};
/**
* This actor is a shim that connects to a ContentActor in a remote
* browser process. All RDP packets get forwarded using the message
* manager.
*
* @param aConnection The main RDP connection.
* @param aBrowser XUL <browser> element to connect to.
*/
function RemoteBrowserTabActor(aConnection, aBrowser)
{
this._conn = aConnection;
this._browser = aBrowser;
this._form = null;
}
RemoteBrowserTabActor.prototype = {
connect: function() {
return DebuggerServer.connectToChild(this._conn, this._browser.messageManager);
},
form: function() {
return this._form;
},
exit: function() {
this._browser = null;
},
};
function BrowserAddonList(aConnection)
@ -993,12 +1084,18 @@ BrowserAddonList.prototype.onUninstalled = function (aAddon) {
function BrowserAddonActor(aConnection, aAddon) {
this.conn = aConnection;
this._addon = aAddon;
this._contextPool = null;
this._threadActor = null;
AddonManager.addAddonListener(this);
}
BrowserAddonActor.prototype = {
actorPrefix: "addon",
get exited() {
return !this._addon;
},
get id() {
return this._addon.id;
},
@ -1007,6 +1104,10 @@ BrowserAddonActor.prototype = {
return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
},
get attached() {
return this._threadActor;
},
form: function BAA_form() {
dbg_assert(this.actorID, "addon should have an actorID.");
@ -1024,9 +1125,72 @@ BrowserAddonActor.prototype = {
onUninstalled: function BAA_onUninstalled(aAddon) {
if (aAddon != this._addon)
return;
if (this.attached) {
this.onDetach();
this.conn.send({ from: this.actorID, type: "tabDetached" });
}
this._addon = null;
AddonManager.removeAddonListener(this);
},
onAttach: function BAA_onAttach() {
if (this.exited) {
return { type: "exited" };
}
if (!this.attached) {
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this._threadActor = new AddonThreadActor(this.conn, this,
this._addon.id);
this._contextPool.addActor(this._threadActor);
}
return { type: "tabAttached", threadActor: this._threadActor.actorID };
},
onDetach: function BAA_onDetach() {
if (!this.attached) {
return { error: "wrongState" };
}
this.conn.removeActorPool(this._contextPool);
this._contextPool = null;
this._threadActor = null;
return { type: "detached" };
},
preNest: function() {
let e = Services.wm.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
}
},
postNest: function() {
let e = Services.wm.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
}
};
BrowserAddonActor.prototype.requestTypes = {
"attach": BrowserAddonActor.prototype.onAttach,
"detach": BrowserAddonActor.prototype.onDetach
};
/**
@ -1035,17 +1199,23 @@ BrowserAddonActor.prototype = {
* navigate away from a paused page, the listener makes sure that the debuggee
* is resumed before the navigation begins.
*
* @param BrowserTabActor aBrowserTabActor
* @param TabActor aTabActor
* The tab actor associated with this listener.
*/
function DebuggerProgressListener(aBrowserTabActor) {
this._tabActor = aBrowserTabActor;
this._tabActor._tabbrowser.addProgressListener(this);
function DebuggerProgressListener(aTabActor) {
this._tabActor = aTabActor;
this._tabActor.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
let EventEmitter = devtools.require("devtools/toolkit/event-emitter");
EventEmitter.decorate(this);
}
DebuggerProgressListener.prototype = {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsISupports,
]),
onStateChange:
DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
@ -1103,13 +1273,12 @@ DebuggerProgressListener.prototype = {
* Destroy the progress listener instance.
*/
destroy: function DPL_destroy() {
if (this._tabActor._tabbrowser.removeProgressListener) {
try {
this._tabActor._tabbrowser.removeProgressListener(this);
} catch (ex) {
// This can throw during browser shutdown.
}
try {
this._tabActor.webProgress.removeProgressListener(this);
} catch (ex) {
// This can throw during browser shutdown.
}
this._tabActor._progressListener = null;
this._tabActor = null;
}

View File

@ -4,6 +4,8 @@
"use strict";
let chromeGlobal = this;
// Encapsulate in its own scope to allows loading this frame script
// more than once.
(function () {
@ -26,18 +28,14 @@
let mm = msg.target;
let prefix = msg.data.prefix + docShell.appId;
let conn = DebuggerServer.connectToParent(msg.data.prefix, mm);
let conn = DebuggerServer.connectToParent(prefix, mm);
let actor = new DebuggerServer.ContentAppActor(conn, content);
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
let actorPool = new ActorPool(conn);
actorPool.addActor(actor);
conn.addActorPool(actorPool);
sendAsyncMessage("debug:actor", {actor: actor.grip(),
appId: docShell.appId,
prefix: prefix});
sendAsyncMessage("debug:actor", {actor: actor.grip()});
});
addMessageListener("debug:connect", onConnect);

View File

@ -38,6 +38,7 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
@ -375,7 +376,7 @@ var DebuggerServer = {
if (!("BrowserTabActor" in this)) {
this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
}
if (!("ContentAppActor" in this)) {
if (!("ContentActor" in this)) {
this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
}
},
@ -524,6 +525,71 @@ var DebuggerServer = {
return this._onConnection(transport, aPrefix, true);
},
connectToChild: function(aConnection, aMessageManager, aOnDisconnect) {
let deferred = Promise.defer();
let mm = aMessageManager;
mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
let actor, childTransport;
let prefix = aConnection.allocID("child");
let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
mm.removeMessageListener("debug:actor", onActorCreated);
// Pipe Debugger message from/to parent/child via the message manager
childTransport = new ChildDebuggerTransport(mm, prefix);
childTransport.hooks = {
onPacket: aConnection.send.bind(aConnection),
onClosed: function () {}
};
childTransport.ready();
aConnection.setForwarding(prefix, childTransport);
dumpn("establishing forwarding for app with prefix " + prefix);
actor = msg.json.actor;
deferred.resolve(actor);
}).bind(this);
mm.addMessageListener("debug:actor", onActorCreated);
let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
if (subject == mm) {
Services.obs.removeObserver(onMessageManagerDisconnect, topic);
if (childTransport) {
// If we have a child transport, the actor has already
// been created. We need to stop using this message manager.
childTransport.close();
aConnection.cancelForwarding(prefix);
} else {
// Otherwise, the app has been closed before the actor
// had a chance to be created, so we are not able to create
// the actor.
deferred.resolve(null);
}
if (actor) {
// The ContentActor within the child process doesn't necessary
// have to time to uninitialize itself when the app is closed/killed.
// So ensure telling the client that the related actor is detached.
aConnection.send({ from: actor.actor, type: "tabDetached" });
actor = null;
}
if (aOnDisconnect) {
aOnDisconnect(mm);
}
}
}).bind(this);
Services.obs.addObserver(onMessageManagerDisconnect,
"message-manager-disconnect", false);
mm.sendAsyncMessage("debug:connect", { prefix: prefix });
return deferred.promise;
},
// nsIServerSocketListener implementation
onSocketAccepted:

View File

@ -30,3 +30,7 @@ localhost=(local files)
# %2$S is the file size unit
backupFileSizeText=%1$S %2$S
# LOCALIZATION NOTE (windows8TouchTitle): this is the name of the folder used
# to store bookmarks created in Metro mode and share bookmarks between Metro
# and Desktop.
windows8TouchTitle=Windows 8 Touch