import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1169044 - Patch 1 - Refactor setting referrer and referrer policy between fetch and XHR. r=khuey (3912ebaef)
- Bug 1150771 - Let ArrayBuffer through object Xrays. r=gabor (bed760277)
- Bug 1151385 - Fail early for cross-origin sandboxPrototype. r=gabor (3b65b1561)
- Bug 1131707 - Transparently forward the construct bit for function forwarders. r=gabor (1f5792775)
- Bug 1170311 - Stop asserting non-null argument to nsIPrincipal::{subsumes,equals}{,ConsideringDomain}. r=gabor (7e36d6683)
- Bug 1171175 - Improve BasePrincipal::IsCodebasePrincipal. r=baku (0d278e8f4)
- Bug 1174731 - patch 1 - Make searchParams attribute readonly, r=smaug (11f5d6dcf)
- Bug 1174731 - patch 2 - Make searchParams attribute readonly, r=annevk (4aa7ea1e4)
- Bug 1170097 - Part 1: Move OriginAttributeDictionary. r=bholley (63a1139dd)
- Bug 1084525 - Part 1: Create initial PromisesActor skeleton r=fitzgen (2ef0ad37d)
- Bug 1131643 - Implement a Location object;r=jlong (710fb9b79)
- Bug 1129834 - Store BreakpointActors by original location;r=jlong (67d16d37a)
- Bug 1129837 - Remove generatedLocation from BreakpointActor;r=jlongster (018a60746)
- Bug 1082837 - test cases for image redirects loaded from the imagelib cache. r=smaug, ckerschb (49d216725)
- Bug 1073352, part 2 - Enable some devtools tests. r=ejpbruel (0de7cfdc0)
- Bug 1131646 - Clean up the breakpoint code;r=jlongster (7fa9c6a76)
- Bug 1136146 - Merge the latest version of the source-map library with fx-team;r=fitzgen (983f2c2e9)
- Bug 1042976 follow up - Remove getInnerId from script actors; r=Mossop (43f935298)
- Bug 837630 - Stop hiding __proto__ from O.getOwnPropertyNames. r=Waldo,peterv,past (0f321614d)
- Bug 1138975 - Refactor breakpoint sliding for non-source mapped sources;r=jlong (9fd4be4e4)
- Fix breaking on the "load" event in the debugger (bug 1054159). r=ochameau (6b6b40e78)
- Bug 983469 - Pause on breakpoint condition exception. r=fitzgen (fb6dfab57)
- Bug 1135435 - Add UI for breakpoint condition throws. r=fitzgen (b2f49cb03)
- Bug 1137384 - Rename ThreadSources as TabSources and move it up into the TabActor. r=ejpbruel (fdf1db5d9)
This commit is contained in:
2021-04-15 10:14:56 +08:00
parent 3d36fa43e7
commit 2a40ef2903
83 changed files with 5946 additions and 2721 deletions
@@ -0,0 +1,314 @@
/*
* Description of the Tests for
* - Bug 418354 - Call Mixed content blocking on redirects
*
* Single redirect script tests
* 1. Load a script over https inside an https page
* - the server responds with a 302 redirect to a >> HTTP << script
* - the doorhanger should appear!
*
* 2. Load a script over https inside an http page
* - the server responds with a 302 redirect to a >> HTTP << script
* - the doorhanger should not appear!
*
* Single redirect image tests
* 3. Load an image over https inside an https page
* - the server responds with a 302 redirect to a >> HTTP << image
* - the image should not load
*
* 4. Load an image over https inside an http page
* - the server responds with a 302 redirect to a >> HTTP << image
* - the image should load and get cached
*
* Single redirect cached image tests
* 5. Using offline mode to ensure we hit the cache, load a cached image over
* https inside an http page
* - the server would have responded with a 302 redirect to a >> HTTP <<
* image, but instead we try to use the cached image.
* - the image should load
*
* 6. Using offline mode to ensure we hit the cache, load a cached image over
* https inside an https page
* - the server would have responded with a 302 redirect to a >> HTTP <<
* image, but instead we try to use the cached image.
* - the image should not load
*
* Double redirect image test
* 7. Load an image over https inside an http page
* - the server responds with a 302 redirect to a >> HTTP << server
* - the HTTP server responds with a 302 redirect to a >> HTTPS << image
* - the image should load and get cached
*
* Double redirect cached image tests
* 8. Using offline mode to ensure we hit the cache, load a cached image over
* https inside an http page
* - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
* but instead we try to use the cached image.
* - the image should load
*
* 9. Using offline mode to ensure we hit the cache, load a cached image over
* https inside an https page
* - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
* but instead we try to use the cached image.
* - the image should not load
*/
const PREF_ACTIVE = "security.mixed_content.block_active_content";
const PREF_DISPLAY = "security.mixed_content.block_display_content";
const gHttpsTestRoot = "https://example.com/browser/browser/base/content/test/general/";
const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
let origBlockActive;
let origBlockDisplay;
var gTestBrowser = null;
//------------------------ Helper Functions ---------------------
registerCleanupFunction(function() {
// Set preferences back to their original values
Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay);
// Make sure we are online again
Services.io.offline = false;
});
function cleanUpAfterTests() {
gBrowser.removeCurrentTab();
window.focus();
finish();
}
function waitForCondition(condition, nextTest, errorMsg, okMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
ok(true, okMsg)
moveOn();
}
tries++;
}, 100);
var moveOn = function() {
clearInterval(interval); nextTest();
};
}
//------------------------ Test 1 ------------------------------
function test1() {
gTestBrowser.addEventListener("load", checkPopUpNotificationsForTest1, true);
var url = gHttpsTestRoot + "test_mcb_redirect.html"
gTestBrowser.contentWindow.location = url;
}
function checkPopUpNotificationsForTest1() {
gTestBrowser.removeEventListener("load", checkPopUpNotificationsForTest1, true);
var notification = PopupNotifications.getNotification("bad-content", gTestBrowser.selectedBrowser);
ok(notification, "OK: Mixed Content Doorhanger appeared in Test1!");
var expected = "script blocked";
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test2, "Error: Waited too long for status in Test 1!",
"OK: Expected result in innerHTML for Test1!");
}
//------------------------ Test 2 ------------------------------
function test2() {
gTestBrowser.addEventListener("load", checkPopUpNotificationsForTest2, true);
var url = gHttpTestRoot + "test_mcb_redirect.html"
gTestBrowser.contentWindow.location = url;
}
function checkPopUpNotificationsForTest2() {
gTestBrowser.removeEventListener("load", checkPopUpNotificationsForTest2, true);
var notification = PopupNotifications.getNotification("bad-content", gTestBrowser.selectedBrowser);
ok(!notification, "OK: Mixed Content Doorhanger did not appear in 2!");
var expected = "script executed";
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test3, "Error: Waited too long for status in Test 2!",
"OK: Expected result in innerHTML for Test2!");
}
//------------------------ Test 3 ------------------------------
// HTTPS page loading insecure image
function test3() {
gTestBrowser.addEventListener("load", checkLoadEventForTest3, true);
var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest3() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest3, true);
var expected = "image blocked"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test4, "Error: Waited too long for status in Test 3!",
"OK: Expected result in innerHTML for Test3!");
}
//------------------------ Test 4 ------------------------------
// HTTP page loading insecure image
function test4() {
gTestBrowser.addEventListener("load", checkLoadEventForTest4, true);
var url = gHttpTestRoot + "test_mcb_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest4() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest4, true);
var expected = "image loaded"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test5, "Error: Waited too long for status in Test 4!",
"OK: Expected result in innerHTML for Test4!");
}
//------------------------ Test 5 ------------------------------
// HTTP page laoding insecure cached image
// Assuming test 4 succeeded, the image has already been loaded once
// and hence should be cached per the sjs cache-control header
// Going into offline mode to ensure we are loading from the cache.
function test5() {
gTestBrowser.addEventListener("load", checkLoadEventForTest5, true);
// Go into offline mode
Services.io.offline = true;
var url = gHttpTestRoot + "test_mcb_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest5() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest5, true);
var expected = "image loaded"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test6, "Error: Waited too long for status in Test 5!",
"OK: Expected result in innerHTML for Test5!");
// Go back online
Services.io.offline = false;
}
//------------------------ Test 6 ------------------------------
// HTTPS page loading insecure cached image
// Assuming test 4 succeeded, the image has already been loaded once
// and hence should be cached per the sjs cache-control header
// Going into offline mode to ensure we are loading from the cache.
function test6() {
gTestBrowser.addEventListener("load", checkLoadEventForTest6, true);
// Go into offline mode
Services.io.offline = true;
var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest6() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest6, true);
var expected = "image blocked"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test7, "Error: Waited too long for status in Test 6!",
"OK: Expected result in innerHTML for Test6!");
// Go back online
Services.io.offline = false;
}
//------------------------ Test 7 ------------------------------
// HTTP page loading insecure image that went through a double redirect
function test7() {
gTestBrowser.addEventListener("load", checkLoadEventForTest7, true);
var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest7() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest7, true);
var expected = "image loaded"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test8, "Error: Waited too long for status in Test 7!",
"OK: Expected result in innerHTML for Test7!");
}
//------------------------ Test 8 ------------------------------
// HTTP page loading insecure cached image that went through a double redirect
// Assuming test 7 succeeded, the image has already been loaded once
// and hence should be cached per the sjs cache-control header
// Going into offline mode to ensure we are loading from the cache.
function test8() {
gTestBrowser.addEventListener("load", checkLoadEventForTest8, true);
// Go into offline mode
Services.io.offline = true;
var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest8() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest8, true);
var expected = "image loaded"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
test9, "Error: Waited too long for status in Test 8!",
"OK: Expected result in innerHTML for Test8!");
// Go back online
Services.io.offline = false;
}
//------------------------ Test 9 ------------------------------
// HTTPS page loading insecure cached image that went through a double redirect
// Assuming test 7 succeeded, the image has already been loaded once
// and hence should be cached per the sjs cache-control header
// Going into offline mode to ensure we are loading from the cache.
function test9() {
gTestBrowser.addEventListener("load", checkLoadEventForTest9, true);
// Go into offline mode
Services.io.offline = true;
var url = gHttpsTestRoot + "test_mcb_double_redirect_image.html"
gTestBrowser.contentWindow.location = url;
}
function checkLoadEventForTest9() {
gTestBrowser.removeEventListener("load", checkLoadEventForTest9, true);
var expected = "image blocked"
waitForCondition(
function() content.document.getElementById('mctestdiv').innerHTML == expected,
cleanUpAfterTests, "Error: Waited too long for status in Test 9!",
"OK: Expected result in innerHTML for Test9!");
// Go back online
Services.io.offline = false;
}
//------------------------ SETUP ------------------------------
function test() {
// Performing async calls, e.g. 'onload', we have to wait till all of them finished
waitForExplicitFinish();
// Store original preferences so we can restore settings after testing
origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY);
Services.prefs.setBoolPref(PREF_ACTIVE, true);
Services.prefs.setBoolPref(PREF_DISPLAY, true);
var newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
gTestBrowser = gBrowser.selectedBrowser;
newTab.linkedBrowser.stop();
executeSoon(test1);
}
@@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<!--
Test 7-9 for Bug 1082837 - See file browser_mcb_redirect.js for description.
https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
-->
<head>
<meta charset="utf-8">
<title>Bug 1082837</title>
<script>
function image_loaded() {
document.getElementById("mctestdiv").innerHTML = "image loaded";
}
function image_blocked() {
document.getElementById("mctestdiv").innerHTML = "image blocked";
}
</script>
</head>
<body>
<div id="mctestdiv"></div>
<img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_http_sjs" onload="image_loaded()" onerror="image_blocked()" ></image>
</body>
</html>
@@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<!--
Test 1 for Bug 418354 - See file browser_mcb_redirect.js for description.
https://bugzilla.mozilla.org/show_bug.cgi?id=418354
-->
<head>
<meta charset="utf-8">
<title>Bug 418354</title>
</head>
<body>
<div id="mctestdiv">script blocked</div>
<script src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?script" ></script>
</body>
</html>
@@ -0,0 +1,22 @@
function handleRequest(request, response) {
var page = "<!DOCTYPE html><html><body>bug 418354 and bug 1082837</body></html>";
if (request.queryString === "script") {
var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.js";
response.setHeader("Cache-Control", "no-cache", false);
} else if (request.queryString === "image_http") {
var redirect = "http://example.com/tests/image/test/mochitest/blue.png";
response.setHeader("Cache-Control", "max-age=3600", false);
} else if (request.queryString === "image_redirect_http_sjs") {
var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_https";
response.setHeader("Cache-Control", "max-age=3600", false);
} else if (request.queryString === "image_redirect_https") {
var redirect = "https://example.com/tests/image/test/mochitest/blue.png";
response.setHeader("Cache-Control", "max-age=3600", false);
}
response.setHeader("Content-Type", "text/html", false);
response.setStatusLine(request.httpVersion, "302", "Found");
response.setHeader("Location", redirect, false);
response.write(page);
}
@@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<!--
Test 3-6 for Bug 1082837 - See file browser_mcb_redirect.js for description.
https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
-->
<head>
<meta charset="utf-8">
<title>Bug 1082837</title>
<script>
function image_loaded() {
document.getElementById("mctestdiv").innerHTML = "image loaded";
}
function image_blocked() {
document.getElementById("mctestdiv").innerHTML = "image blocked";
}
</script>
</head>
<body>
<div id="mctestdiv"></div>
<img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_http" onload="image_loaded()" onerror="image_blocked()" ></image>
</body>
</html>
+8 -30
View File
@@ -28,7 +28,7 @@ OriginAttributes::CreateSuffix(nsACString& aStr) const
{
MOZ_RELEASE_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
nsRefPtr<URLSearchParams> usp = new URLSearchParams();
nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
nsAutoString value;
if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
@@ -108,8 +108,8 @@ OriginAttributes::PopulateFromSuffix(const nsACString& aStr)
return false;
}
nsRefPtr<URLSearchParams> usp = new URLSearchParams();
usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1), nullptr);
nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1));
PopulateFromSuffixIterator iterator(this);
return usp->ForEach(iterator);
@@ -130,14 +130,14 @@ BasePrincipal::GetOrigin(nsACString& aOrigin)
bool
BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration)
{
MOZ_RELEASE_ASSERT(aOther, "The caller is performing a nonsensical security check!");
MOZ_ASSERT(aOther);
return SubsumesInternal(aOther, aConsideration);
}
NS_IMETHODIMP
BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult)
{
NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
*aResult = Subsumes(aOther, DontConsiderDocumentDomain) &&
Cast(aOther)->Subsumes(this, DontConsiderDocumentDomain);
return NS_OK;
@@ -146,6 +146,7 @@ BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult)
NS_IMETHODIMP
BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult)
{
NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
*aResult = Subsumes(aOther, ConsiderDocumentDomain) &&
Cast(aOther)->Subsumes(this, ConsiderDocumentDomain);
return NS_OK;
@@ -154,6 +155,7 @@ BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult)
NS_IMETHODIMP
BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult)
{
NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
*aResult = Subsumes(aOther, DontConsiderDocumentDomain);
return NS_OK;
}
@@ -161,6 +163,7 @@ BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult)
NS_IMETHODIMP
BasePrincipal::SubsumesConsideringDomain(nsIPrincipal *aOther, bool *aResult)
{
NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
*aResult = Subsumes(aOther, ConsiderDocumentDomain);
return NS_OK;
}
@@ -310,29 +313,4 @@ BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs)
return codebase.forget();
}
/* static */ bool
BasePrincipal::IsCodebasePrincipal(nsIPrincipal* aPrincipal)
{
MOZ_ASSERT(aPrincipal);
bool isNullPrincipal = true;
nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (isNullPrincipal || nsContentUtils::IsSystemPrincipal(aPrincipal)) {
return false;
}
// No expanded principals.
nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal =
do_QueryInterface(aPrincipal);
if (expandedPrincipal) {
return false;
}
return true;
}
} // namespace mozilla
+2 -2
View File
@@ -11,7 +11,7 @@
#include "nsIScriptSecurityManager.h"
#include "nsJSPrincipals.h"
#include "mozilla/dom/SystemDictionariesBinding.h"
#include "mozilla/dom/ChromeUtilsBinding.h"
class nsIContentSecurityPolicy;
class nsIObjectOutputStream;
@@ -81,7 +81,7 @@ public:
virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
static bool IsCodebasePrincipal(nsIPrincipal* aPrincipal);
virtual bool IsCodebasePrincipal() const { return false; };
static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }
static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs);
+1
View File
@@ -29,6 +29,7 @@ public:
NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
bool IsCodebasePrincipal() const override { return true; }
nsresult GetOriginInternal(nsACString& aOrigin) override;
nsPrincipal();
+2 -19
View File
@@ -578,21 +578,6 @@ Link::SearchParams()
return mSearchParams;
}
void
Link::SetSearchParams(URLSearchParams& aSearchParams)
{
if (mSearchParams) {
mSearchParams->RemoveObserver(this);
}
mSearchParams = &aSearchParams;
mSearchParams->AddObserver(this);
nsAutoString search;
mSearchParams->Serialize(search);
SetSearchInternal(search);
}
void
Link::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
{
@@ -621,15 +606,14 @@ Link::UpdateURLSearchParams()
}
}
mSearchParams->ParseInput(search, this);
mSearchParams->ParseInput(search);
}
void
Link::CreateSearchParamsIfNeeded()
{
if (!mSearchParams) {
mSearchParams = new URLSearchParams();
mSearchParams->AddObserver(this);
mSearchParams = new URLSearchParams(this);
UpdateURLSearchParams();
}
}
@@ -638,7 +622,6 @@ void
Link::Unlink()
{
if (mSearchParams) {
mSearchParams->RemoveObserver(this);
mSearchParams = nullptr;
}
}
-1
View File
@@ -64,7 +64,6 @@ public:
void SetHostname(const nsAString &aHostname, ErrorResult& aError);
void SetPathname(const nsAString &aPathname, ErrorResult& aError);
void SetSearch(const nsAString &aSearch, ErrorResult& aError);
void SetSearchParams(mozilla::dom::URLSearchParams& aSearchParams);
void SetPort(const nsAString &aPort, ErrorResult& aError);
void SetHash(const nsAString &aHash, ErrorResult& aError);
void GetOrigin(nsAString &aOrigin, ErrorResult& aError);
+2 -20
View File
@@ -27,7 +27,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(URL)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(URL)
if (tmp->mSearchParams) {
tmp->mSearchParams->RemoveObserver(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSearchParams)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -358,7 +357,7 @@ URL::UpdateURLSearchParams()
}
}
mSearchParams->ParseInput(search, this);
mSearchParams->ParseInput(search);
}
void
@@ -484,22 +483,6 @@ URL::SearchParams()
return mSearchParams;
}
void
URL::SetSearchParams(URLSearchParams& aSearchParams)
{
if (mSearchParams) {
mSearchParams->RemoveObserver(this);
}
// the observer will be cleared using the cycle collector.
mSearchParams = &aSearchParams;
mSearchParams->AddObserver(this);
nsAutoString search;
mSearchParams->Serialize(search);
SetSearchInternal(search);
}
void
URL::GetHash(nsAString& aHash, ErrorResult& aRv) const
{
@@ -534,8 +517,7 @@ void
URL::CreateSearchParamsIfNeeded()
{
if (!mSearchParams) {
mSearchParams = new URLSearchParams();
mSearchParams->AddObserver(this);
mSearchParams = new URLSearchParams(this);
UpdateURLSearchParams();
}
}
-2
View File
@@ -119,8 +119,6 @@ public:
URLSearchParams* SearchParams();
void SetSearchParams(URLSearchParams& aSearchParams);
void GetHash(nsAString& aRetval, ErrorResult& aRv) const;
void SetHash(const nsAString& aArg, ErrorResult& aRv);
+13 -38
View File
@@ -12,7 +12,7 @@
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObservers)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObserver)
NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
@@ -21,7 +21,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
URLSearchParams::URLSearchParams()
URLSearchParams::URLSearchParams(URLSearchParamsObserver* aObserver)
: mObserver(aObserver)
{
}
@@ -41,8 +42,8 @@ URLSearchParams::Constructor(const GlobalObject& aGlobal,
const nsAString& aInit,
ErrorResult& aRv)
{
nsRefPtr<URLSearchParams> sp = new URLSearchParams();
sp->ParseInput(NS_ConvertUTF16toUTF8(aInit), nullptr);
nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
sp->ParseInput(NS_ConvertUTF16toUTF8(aInit));
return sp.forget();
}
@@ -51,14 +52,13 @@ URLSearchParams::Constructor(const GlobalObject& aGlobal,
URLSearchParams& aInit,
ErrorResult& aRv)
{
nsRefPtr<URLSearchParams> sp = new URLSearchParams();
nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
sp->mSearchParams = aInit.mSearchParams;
return sp.forget();
}
void
URLSearchParams::ParseInput(const nsACString& aInput,
URLSearchParamsObserver* aObserver)
URLSearchParams::ParseInput(const nsACString& aInput)
{
// Remove all the existing data before parsing a new input.
DeleteAll();
@@ -108,8 +108,6 @@ URLSearchParams::ParseInput(const nsACString& aInput,
AppendInternal(decodedName, decodedValue);
}
NotifyObservers(aObserver);
}
void
@@ -208,27 +206,6 @@ URLSearchParams::ConvertString(const nsACString& aInput, nsAString& aOutput)
}
}
void
URLSearchParams::AddObserver(URLSearchParamsObserver* aObserver)
{
MOZ_ASSERT(aObserver);
MOZ_ASSERT(!mObservers.Contains(aObserver));
mObservers.AppendElement(aObserver);
}
void
URLSearchParams::RemoveObserver(URLSearchParamsObserver* aObserver)
{
MOZ_ASSERT(aObserver);
mObservers.RemoveElement(aObserver);
}
void
URLSearchParams::RemoveObservers()
{
mObservers.Clear();
}
void
URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
{
@@ -280,14 +257,14 @@ URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
param->mValue = aValue;
NotifyObservers(nullptr);
NotifyObserver();
}
void
URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
{
AppendInternal(aName, aValue);
NotifyObservers(nullptr);
NotifyObserver();
}
void
@@ -324,7 +301,7 @@ URLSearchParams::Delete(const nsAString& aName)
}
if (found) {
NotifyObservers(nullptr);
NotifyObserver();
}
}
@@ -380,12 +357,10 @@ URLSearchParams::Serialize(nsAString& aValue) const
}
void
URLSearchParams::NotifyObservers(URLSearchParamsObserver* aExceptObserver)
URLSearchParams::NotifyObserver()
{
for (uint32_t i = 0; i < mObservers.Length(); ++i) {
if (mObservers[i] != aExceptObserver) {
mObservers[i]->URLSearchParamsUpdated(this);
}
if (mObserver) {
mObserver->URLSearchParamsUpdated(this);
}
}
+4 -9
View File
@@ -40,7 +40,7 @@ public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(URLSearchParams)
URLSearchParams();
explicit URLSearchParams(URLSearchParamsObserver* aObserver);
// WebIDL methods
nsISupports* GetParentObject() const
@@ -59,12 +59,7 @@ public:
Constructor(const GlobalObject& aGlobal, URLSearchParams& aInit,
ErrorResult& aRv);
void ParseInput(const nsACString& aInput,
URLSearchParamsObserver* aObserver);
void AddObserver(URLSearchParamsObserver* aObserver);
void RemoveObserver(URLSearchParamsObserver* aObserver);
void RemoveObservers();
void ParseInput(const nsACString& aInput);
void Serialize(nsAString& aValue) const;
@@ -113,7 +108,7 @@ private:
void DecodeString(const nsACString& aInput, nsAString& aOutput);
void ConvertString(const nsACString& aInput, nsAString& aOutput);
void NotifyObservers(URLSearchParamsObserver* aExceptObserver);
void NotifyObserver();
struct Param
{
@@ -123,7 +118,7 @@ private:
nsTArray<Param> mSearchParams;
nsTArray<nsRefPtr<URLSearchParamsObserver>> mObservers;
nsRefPtr<URLSearchParamsObserver> mObserver;
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
};
+51
View File
@@ -8008,3 +8008,54 @@ nsContentUtils::GetWindowRoot(nsIDocument* aDoc)
}
return nullptr;
}
nsresult
nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
nsIDocument* aDoc,
nsIHttpChannel* aChannel)
{
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_ARG_POINTER(aChannel);
nsCOMPtr<nsIURI> principalURI;
if (IsSystemPrincipal(aPrincipal)) {
return NS_OK;
}
aPrincipal->GetURI(getter_AddRefs(principalURI));
if (!aDoc) {
return aChannel->SetReferrerWithPolicy(principalURI, net::RP_Default);
}
// If it weren't for history.push/replaceState, we could just use the
// principal's URI here. But since we want changes to the URI effected
// by push/replaceState to be reflected in the XHR referrer, we have to
// be more clever.
//
// If the document's original URI (before any push/replaceStates) matches
// our principal, then we use the document's current URI (after
// push/replaceStates). Otherwise (if the document is, say, a data:
// URI), we just use the principal's URI.
nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
nsCOMPtr<nsIURI> referrerURI;
if (principalURI && docCurURI && docOrigURI) {
bool equal = false;
principalURI->Equals(docOrigURI, &equal);
if (equal) {
referrerURI = docCurURI;
}
}
if (!referrerURI) {
referrerURI = principalURI;
}
net::ReferrerPolicy referrerPolicy = aDoc->GetReferrerPolicy();
return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
}
+19
View File
@@ -2489,6 +2489,25 @@ public:
static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
/*
* Implements step 3.1 and 3.3 of the Determine request's Referrer algorithm
* from the Referrer Policy specification.
*
* The referrer policy of the document is applied by Necko when using
* channels.
*
* For documents representing an iframe srcdoc attribute, the document sets
* its own URI correctly, so this method simply uses the document's original
* or current URI as appropriate.
*
* aDoc may be null.
*
* https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
*/
static nsresult SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
nsIDocument* aDoc,
nsIHttpChannel* aChannel);
private:
static bool InitializeEventTable();
+4 -44
View File
@@ -2716,50 +2716,10 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
if (!IsSystemXHR()) {
// Get the referrer for the request.
//
// If it weren't for history.push/replaceState, we could just use the
// principal's URI here. But since we want changes to the URI effected
// by push/replaceState to be reflected in the XHR referrer, we have to
// be more clever.
//
// If the document's original URI (before any push/replaceStates) matches
// our principal, then we use the document's current URI (after
// push/replaceStates). Otherwise (if the document is, say, a data:
// URI), we just use the principal's URI.
nsCOMPtr<nsIURI> principalURI;
mPrincipal->GetURI(getter_AddRefs(principalURI));
nsIScriptContext* sc = GetContextForEventHandlers(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocument> doc =
nsContentUtils::GetDocumentFromScriptContext(sc);
nsCOMPtr<nsIURI> docCurURI;
nsCOMPtr<nsIURI> docOrigURI;
net::ReferrerPolicy referrerPolicy = net::RP_Default;
if (doc) {
docCurURI = doc->GetDocumentURI();
docOrigURI = doc->GetOriginalURI();
referrerPolicy = doc->GetReferrerPolicy();
}
nsCOMPtr<nsIURI> referrerURI;
if (principalURI && docCurURI && docOrigURI) {
bool equal = false;
principalURI->Equals(docOrigURI, &equal);
if (equal) {
referrerURI = docCurURI;
}
}
if (!referrerURI)
referrerURI = principalURI;
httpChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc,
httpChannel);
}
// Some extensions override the http protocol handler and provide their own
+1 -91
View File
@@ -124,28 +124,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
url.searchParams.set('e', 'f');
ok(url.href.indexOf('e=f') != 1, 'URL right');
var u = new URLSearchParams();
u.append('foo', 'bar');
url.searchParams = u;
is(url.searchParams, u, "URL.searchParams is the same object");
is(url.searchParams.get('foo'), 'bar', "URL.searchParams.get('foo')");
is(url.href, 'http://www.example.net/?foo=bar', 'URL right');
try {
url.searchParams = null;
ok(false, "URLSearchParams is not nullable");
} catch(e) {
ok(true, "URLSearchParams is not nullable");
}
var url2 = new URL('http://www.example.net?e=f');
url.searchParams = url2.searchParams;
is(url.searchParams, url2.searchParams, "URL.searchParams is not the same object");
is(url.searchParams.get('e'), 'f', "URL.searchParams.get('e')");
url.href = "http://www.example.net?bar=foo";
is(url.searchParams.get('bar'), 'foo', "URL.searchParams.get('bar')");
runTest();
}
@@ -160,31 +138,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
e.searchParams.set('e', 'f');
ok(e.href.indexOf('e=f') != 1, 'e is right');
var u = new URLSearchParams();
u.append('foo', 'bar');
e.searchParams = u;
is(e.searchParams, u, "e.searchParams is the same object");
is(e.searchParams.get('foo'), 'bar', "e.searchParams.get('foo')");
is(e.href, 'http://www.example.net/?foo=bar', 'e is right');
try {
e.searchParams = null;
ok(false, "URLSearchParams is not nullable");
} catch(e) {
ok(true, "URLSearchParams is not nullable");
}
var url2 = new URL('http://www.example.net?e=f');
e.searchParams = url2.searchParams;
is(e.searchParams, url2.searchParams, "e.searchParams is not the same object");
is(e.searchParams.get('e'), 'f', "e.searchParams.get('e')");
e.href = "http://www.example.net?bar=foo";
is(e.searchParams.get('bar'), 'foo', "e.searchParams.get('bar')");
e.setAttribute('href', "http://www.example.net?bar2=foo2");
is(e.searchParams.get('bar2'), 'foo2', "e.searchParams.get('bar2')");
runTest();
}
@@ -195,11 +148,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
[ '\u0541', '%D5%81'] ];
for (var i = 0; i < encoding.length; ++i) {
var a = new URLSearchParams();
a.set('a', encoding[i][0]);
var url = new URL('http://www.example.net');
url.searchParams = a;
url.searchParams.set('a', encoding[i][0]);
is(url.href, 'http://www.example.net/?a=' + encoding[i][1]);
var url2 = new URL(url.href);
@@ -209,45 +159,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
runTest();
}
function testMultiURL() {
var a = new URL('http://www.example.net?a=b&c=d');
var b = new URL('http://www.example.net?e=f');
var c = document.createElement('a');
var d = document.createElement('area');
ok(a.searchParams.has('a'), "a.searchParams.has('a')");
ok(a.searchParams.has('c'), "a.searchParams.has('c')");
ok(b.searchParams.has('e'), "b.searchParams.has('e')");
ok(c.searchParams, "c.searchParams");
ok(d.searchParams, "d.searchParams");
var u = new URLSearchParams();
a.searchParams = b.searchParams = c.searchParams = d.searchParams = u;
is(a.searchParams, u, "a.searchParams === u");
is(b.searchParams, u, "b.searchParams === u");
is(c.searchParams, u, "c.searchParams === u");
is(d.searchParams, u, "d.searchParams === u");
ok(!a.searchParams.has('a'), "!a.searchParams.has('a')");
ok(!a.searchParams.has('c'), "!a.searchParams.has('c')");
ok(!b.searchParams.has('e'), "!b.searchParams.has('e')");
u.append('foo', 'bar');
is(a.searchParams.get('foo'), 'bar', "a has foo=bar");
is(b.searchParams.get('foo'), 'bar', "b has foo=bar");
is(c.searchParams.get('foo'), 'bar', "c has foo=bar");
is(d.searchParams.get('foo'), 'bar', "d has foo=bar");
is(a + "", b + "", "stringify a == b");
is(c.searchParams + "", b.searchParams + "", "stringify c.searchParams == b.searchParams");
is(d.searchParams + "", b.searchParams + "", "stringify d.searchParams == b.searchParams");
a.search = "?bar=foo";
is(a.searchParams.get('bar'), 'foo', "a has bar=foo");
is(b.searchParams.get('bar'), 'foo', "b has bar=foo");
is(c.searchParams.get('bar'), 'foo', "c has bar=foo");
is(d.searchParams.get('bar'), 'foo', "d has bar=foo");
runTest();
}
function testOrdering() {
var a = new URLSearchParams("a=1&a=2&b=3&c=4&c=5&a=6");
is(a.toString(), "a=1&a=2&b=3&c=4&c=5&a=6", "Order is correct");
@@ -334,7 +245,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
function() { testElement(document.getElementById('anchor')) },
function() { testElement(document.getElementById('area')) },
testEncoding,
testMultiURL,
testOrdering,
testDelete,
testGetNULL,
@@ -7,9 +7,6 @@
<script>
test(function() {
var props = Object.getOwnPropertyNames(Object.prototype);
// getOwnPropertyNames intentionally filters out the non-standard
// "__proto__" property.
props.push("__proto__");
// If you change this list, make sure it continues to match the list in
// Codegen.py's CGDictionary.getMemberDefinition method.
var expected = [
+2 -59
View File
@@ -217,11 +217,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
nsRefPtr<InternalRequest> r = request->GetInternalRequest();
aRv = UpdateRequestReferrer(aGlobal, r);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
nsCOMPtr<nsIDocument> doc;
@@ -398,58 +393,6 @@ WorkerFetchResolver::OnResponseEnd()
}
}
// This method sets the request's referrerURL, as specified by the "determine
// request's referrer" steps from Referrer Policy [1].
// The actual referrer policy and stripping is dealt with by HttpBaseChannel,
// this always sets the full API referrer URL of the relevant global if it is
// not already a url or no-referrer.
// [1]: https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
nsresult
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest)
{
nsAutoString originalReferrer;
aRequest->GetReferrer(originalReferrer);
// If it is no-referrer ("") or a URL, don't modify.
if (!originalReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
if (window) {
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (doc) {
nsAutoString referrer;
doc->GetReferrer(referrer);
aRequest->SetReferrer(referrer);
}
} else if (NS_IsMainThread()) {
// Pull the principal from the global for non-worker scripts.
nsIPrincipal *principal = aGlobal->PrincipalOrNull();
bool isNull;
// Only set the referrer if the principal is present,
// and the principal is not null or the system principal.
if (principal &&
NS_SUCCEEDED(principal->GetIsNullPrincipal(&isNull)) && !isNull &&
!nsContentUtils::IsSystemPrincipal(principal)) {
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(principal->GetURI(getter_AddRefs(uri))) && uri) {
nsAutoCString referrer;
if (NS_SUCCEEDED(uri->GetSpec(referrer))) {
aRequest->SetReferrer(NS_ConvertUTF8toUTF16(referrer));
}
}
}
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
WorkerPrivate::LocationInfo& info = worker->GetLocationInfo();
aRequest->SetReferrer(NS_ConvertUTF8toUTF16(info.mHref));
}
return NS_OK;
}
namespace {
nsresult
ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
@@ -1600,8 +1543,8 @@ FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength
}
if (isValidUrlEncodedMimeType) {
nsRefPtr<URLSearchParams> params = new URLSearchParams();
params->ParseInput(data, /* aObserver */ nullptr);
nsRefPtr<URLSearchParams> params = new URLSearchParams(nullptr);
params->ParseInput(data);
nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
FillFormIterator iterator(fd);
+27 -11
View File
@@ -416,21 +416,37 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
// Step 2. Set the referrer.
nsAutoString referrer;
mRequest->GetReferrer(referrer);
// The referrer should have already been resolved to a URL by the caller.
MOZ_ASSERT(!referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR));
if (!referrer.IsEmpty()) {
nsCOMPtr<nsIURI> refURI;
rv = NS_NewURI(getter_AddRefs(refURI), referrer, nullptr, nullptr);
if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
mDocument,
httpChan);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
} else if (referrer.IsEmpty()) {
rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
} else {
// From "Determine request's Referrer" step 3
// "If request's referrer is a URL, let referrerSource be request's
// referrer."
//
// This allows ServiceWorkers to function transparently when the referrer
// of the intercepted request is already set.
nsCOMPtr<nsIURI> referrerURI;
rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
net::ReferrerPolicy referrerPolicy = net::RP_Default;
if (mDocument) {
referrerPolicy = mDocument->GetReferrerPolicy();
}
rv = httpChan->SetReferrerWithPolicy(refURI, referrerPolicy);
// FIXME(nsm): Can we assert that this case can only happen in
// ServiceWorkers and assume null mDocument?
rv =
httpChan->SetReferrerWithPolicy(nullptr,
mDocument ? mDocument->GetReferrerPolicy() :
net::RP_Default);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
-1
View File
@@ -158,7 +158,6 @@ public:
using Link::SetHash;
// The Link::GetSearchParams is OK for us
// The Link::SetSearchParams is OK for us
bool NoHref() const
{
@@ -1250,6 +1250,25 @@ function testRedirects() {
return Promise.all(fetches);
}
function testReferrer() {
var referrer;
if (self && self.location) {
referrer = self.location.href;
} else {
referrer = document.documentURI;
}
var dict = {
'Referer': referrer
};
return fetch(corsServerPath + "headers=" + dict.toSource()).then(function(res) {
is(res.status, 200, "expected correct referrer header to be sent");
dump(res.statusText);
}, function(e) {
ok(false, "expected correct referrer header to be sent");
});
}
function runTest() {
testNoCorsCtor();
@@ -1260,5 +1279,6 @@ function runTest() {
.then(testSameOriginCredentials)
.then(testCrossOriginCredentials)
.then(testRedirects)
.then(testReferrer)
// Put more promise based tests here.
}
+14
View File
@@ -61,3 +61,17 @@ dictionary HeapSnapshotBoundaries {
object debugger;
boolean runtime;
};
/**
* Used by principals and the script security manager to represent origin
* attributes.
*
* IMPORTANT: If you add any members here, you need to update the
* methods on mozilla::OriginAttributes, and bump the CIDs of all
* the principal implementations that use OriginAttributes in their
* nsISerializable implementations.
*/
dictionary OriginAttributesDictionary {
unsigned long appId = 0;
boolean inBrowser = false;
};
-22
View File
@@ -1,22 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
/*
* Used by principals and the script security manager to represent origin
* attributes.
*
* IMPORTANT: If you add any members here, you need to update the
* methods on mozilla::OriginAttributes, and bump the CIDs of all
* the principal implementations that use OriginAttributes in their
* nsISerializable implementations.
*/
dictionary OriginAttributesDictionary {
unsigned long appId = 0;
boolean inBrowser = false;
};
+1 -1
View File
@@ -51,5 +51,5 @@ interface URLUtils {
[NoInterfaceObject,
Exposed=(Window, Worker)]
interface URLUtilsSearchParams {
attribute URLSearchParams searchParams;
readonly attribute URLSearchParams searchParams;
};
-1
View File
@@ -510,7 +510,6 @@ WEBIDL_FILES = [
'SVGViewElement.webidl',
'SVGZoomAndPan.webidl',
'SVGZoomEvent.webidl',
'SystemDictionaries.webidl',
'SystemUpdate.webidl',
'Telephony.webidl',
'TelephonyCall.webidl',
+3 -3
View File
@@ -238,7 +238,7 @@ PopulateRegistrationData(nsIPrincipal* aPrincipal,
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aRegistration);
if (NS_WARN_IF(!BasePrincipal::IsCodebasePrincipal(aPrincipal))) {
if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsCodebasePrincipal())) {
return NS_ERROR_FAILURE;
}
@@ -1765,7 +1765,7 @@ public:
nsTArray<nsRefPtr<ServiceWorkerRegistrationMainThread>> array;
if (NS_WARN_IF(!BasePrincipal::IsCodebasePrincipal(principal))) {
if (NS_WARN_IF(!BasePrincipal::Cast(principal)->IsCodebasePrincipal())) {
return NS_OK;
}
@@ -2796,7 +2796,7 @@ ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal* aPrincipal,
{
MOZ_ASSERT(aPrincipal);
if (NS_WARN_IF(!BasePrincipal::IsCodebasePrincipal(aPrincipal))) {
if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsCodebasePrincipal())) {
return NS_ERROR_FAILURE;
}
+2 -18
View File
@@ -838,21 +838,6 @@ URL::SearchParams()
return mSearchParams;
}
void
URL::SetSearchParams(URLSearchParams& aSearchParams)
{
if (mSearchParams) {
mSearchParams->RemoveObserver(this);
}
mSearchParams = &aSearchParams;
mSearchParams->AddObserver(this);
nsAutoString search;
mSearchParams->Serialize(search);
SetSearchInternal(search);
}
void
URL::GetHash(nsAString& aHash, ErrorResult& aRv) const
{
@@ -959,7 +944,7 @@ URL::UpdateURLSearchParams()
nsAutoString search;
ErrorResult rv;
GetSearch(search, rv);
mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(Substring(search, 1)), this);
mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(Substring(search, 1)));
}
}
@@ -967,8 +952,7 @@ void
URL::CreateSearchParamsIfNeeded()
{
if (!mSearchParams) {
mSearchParams = new URLSearchParams();
mSearchParams->AddObserver(this);
mSearchParams = new URLSearchParams(this);
UpdateURLSearchParams();
}
}
-2
View File
@@ -108,8 +108,6 @@ public:
URLSearchParams* SearchParams();
void SetSearchParams(URLSearchParams& aSearchParams);
void GetHash(nsAString& aHost, ErrorResult& aRv) const;
void SetHash(const nsAString& aHash, ErrorResult& aRv);
+1 -53
View File
@@ -116,28 +116,6 @@ onmessage = function() {
url.searchParams.set('e', 'f');
ok(url.href.indexOf('e=f') != 1, 'URL right');
var u = new URLSearchParams();
u.append('foo', 'bar');
url.searchParams = u;
is(url.searchParams, u, "URL.searchParams is the same object");
is(url.searchParams.get('foo'), 'bar', "URL.searchParams.get('foo')");
is(url.href, 'http://www.example.net/?foo=bar', 'URL right');
try {
url.searchParams = null;
ok(false, "URLSearchParams is not nullable");
} catch(e) {
ok(true, "URLSearchParams is not nullable");
}
var url2 = new URL('http://www.example.net?e=f');
url.searchParams = url2.searchParams;
is(url.searchParams, url2.searchParams, "URL.searchParams is not the same object");
is(url.searchParams.get('e'), 'f', "URL.searchParams.get('e')");
url.href = "http://www.example.net?bar=foo";
is(url.searchParams.get('bar'), 'foo', "URL.searchParams.get('bar')");
runTest();
}
@@ -148,11 +126,8 @@ onmessage = function() {
[ '\u0541', '%D5%81'] ];
for (var i = 0; i < encoding.length; ++i) {
var a = new URLSearchParams();
a.set('a', encoding[i][0]);
var url = new URL('http://www.example.net');
url.searchParams = a;
url.searchParams.set('a', encoding[i][0]);
is(url.href, 'http://www.example.net/?a=' + encoding[i][1]);
var url2 = new URL(url.href);
@@ -162,39 +137,12 @@ onmessage = function() {
runTest();
}
function testMultiURL() {
var a = new URL('http://www.example.net?a=b&c=d');
var b = new URL('http://www.example.net?e=f');
ok(a.searchParams.has('a'), "a.searchParams.has('a')");
ok(a.searchParams.has('c'), "a.searchParams.has('c')");
ok(b.searchParams.has('e'), "b.searchParams.has('e')");
var u = new URLSearchParams();
a.searchParams = b.searchParams = u;
is(a.searchParams, u, "a.searchParams === u");
is(b.searchParams, u, "b.searchParams === u");
ok(!a.searchParams.has('a'), "!a.searchParams.has('a')");
ok(!a.searchParams.has('c'), "!a.searchParams.has('c')");
ok(!b.searchParams.has('e'), "!b.searchParams.has('e')");
u.append('foo', 'bar');
is(a.searchParams.get('foo'), 'bar', "a has foo=bar");
is(b.searchParams.get('foo'), 'bar', "b has foo=bar");
is(a + "", b + "", "stringify a == b");
a.search = "?bar=foo";
is(a.searchParams.get('bar'), 'foo', "a has bar=foo");
is(b.searchParams.get('bar'), 'foo', "b has bar=foo");
runTest();
}
var tests = [
testSimpleURLSearchParams,
testCopyURLSearchParams,
testParserURLSearchParams,
testURL,
testEncoding,
testMultiURL
];
function runTest() {
-9
View File
@@ -99,15 +99,6 @@ static inline bool
Enumerate(JSContext* cx, HandleObject pobj, jsid id,
bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
{
// We implement __proto__ using a property on |Object.prototype|, but
// because __proto__ is highly deserving of removal, we don't want it to
// show up in property enumeration, even if only for |Object.prototype|
// (think introspection by Prototype-like frameworks that add methods to
// the built-in prototypes). So exclude __proto__ if the object where the
// property was found has no [[Prototype]] and might be |Object.prototype|.
if (MOZ_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto)))
return true;
if (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() || pobj->getOps()->enumerate) {
if (!ht) {
ht.emplace(cx);
@@ -4,10 +4,8 @@
*/
//-----------------------------------------------------------------------------
var BUGNUMBER = 690031;
var summary =
'Exclude __proto__ from showing up when enumerating properties of ' +
'Object.prototype again';
var BUGNUMBER = 837630;
var summary ='__proto__ should show up with O.getOwnPropertyNames(O.prototype)';
print(BUGNUMBER + ": " + summary);
@@ -16,8 +14,8 @@ print(BUGNUMBER + ": " + summary);
**************/
var keys = Object.getOwnPropertyNames(Object.prototype);
assertEq(keys.indexOf("__proto__"), -1,
"shouldn't have gotten __proto__ as a property of Object.prototype " +
assertEq(keys.indexOf("__proto__") >= 0, true,
"should have gotten __proto__ as a property of Object.prototype " +
"(got these properties: " + keys + ")");
/******************************************************************************/
+14 -10
View File
@@ -324,18 +324,14 @@ FunctionForwarder(JSContext* cx, unsigned argc, Value* vp)
RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
RootedObject unwrappedFun(cx, js::UncheckedUnwrap(&v.toObject()));
RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp));
if (!thisObj) {
return false;
}
RootedObject thisObj(cx, args.isConstructing() ? nullptr : JS_THIS_OBJECT(cx, vp));
{
// We manually implement the contents of CrossCompartmentWrapper::call
// here, because certain function wrappers (notably content->nsEP) are
// not callable.
JSAutoCompartment ac(cx, unwrappedFun);
RootedValue thisVal(cx, ObjectValue(*thisObj));
RootedValue thisVal(cx, ObjectOrNullValue(thisObj));
if (!CheckSameOriginArg(cx, options, thisVal) || !JS_WrapObject(cx, &thisObj))
return false;
@@ -345,8 +341,13 @@ FunctionForwarder(JSContext* cx, unsigned argc, Value* vp)
}
RootedValue fval(cx, ObjectValue(*unwrappedFun));
if (!JS_CallFunctionValue(cx, thisObj, fval, args, args.rval()))
return false;
if (args.isConstructing()) {
if (!JS::Construct(cx, fval, args, args.rval()))
return false;
} else {
if (!JS_CallFunctionValue(cx, thisObj, fval, args, args.rval()))
return false;
}
}
// Rewrap the return value into our compartment.
@@ -361,8 +362,11 @@ NewFunctionForwarder(JSContext* cx, HandleId idArg, HandleObject callable,
if (id == JSID_VOIDHANDLE)
id = GetRTIdByIndex(cx, XPCJSRuntime::IDX_EMPTYSTRING);
JSFunction *fun = js::NewFunctionByIdWithReserved(cx, FunctionForwarder,
0, 0, id);
// We have no way of knowing whether the underlying function wants to be a
// constructor or not, so we just mark all forwarders as constructors, and
// let the underlying function throw for construct calls if it wants.
JSFunction* fun = js::NewFunctionByIdWithReserved(cx, FunctionForwarder,
0, JSFUN_CONSTRUCTOR, id);
if (!fun)
return false;
+5 -1
View File
@@ -1053,7 +1053,11 @@ xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prin
return NS_ERROR_XPC_UNEXPECTED;
// Now check what sort of thing we've got in |proto|
JSObject* unwrappedProto = js::UncheckedUnwrap(options.proto, false);
JSObject* unwrappedProto = js::CheckedUnwrap(options.proto, false);
if (!unwrappedProto) {
JS_ReportError(cx, "Sandbox must subsume sandboxPrototype");
return NS_ERROR_INVALID_ARG;
}
const js::Class* unwrappedClass = js::GetObjectClass(unwrappedProto);
if (IS_WN_CLASS(unwrappedClass) ||
mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) {
+3 -3
View File
@@ -160,11 +160,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
"toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString",
"toISOString", "toJSON", "toSource", "toString", "valueOf", "constructor",
"toGMTString"];
gPrototypeProperties['Object'] = /* __proto__ is intentionally excluded here, because
the JS engine filters it out of getOwnPropertyNames */
gPrototypeProperties['Object'] =
["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch",
"unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
"__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__"];
"__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__",
"__proto__"];
gPrototypeProperties['Array'] =
["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
"pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
@@ -0,0 +1,22 @@
const Cu = Components.utils;
function testStrict(sb) {
"use strict";
do_check_eq(sb.eval("typeof wrappedCtor()"), "string");
do_check_eq(sb.eval("typeof new wrappedCtor()"), "object");
}
function run_test() {
var sb = new Cu.Sandbox(null);
var dateCtor = sb.Date;
sb.wrappedCtor = Cu.exportFunction(function wrapper(val) {
"use strict";
var constructing = this.constructor == wrapper;
return constructing ? new dateCtor(val) : dateCtor(val);
}, sb);
do_check_eq(typeof Date(), "string");
do_check_eq(typeof new Date(), "object");
do_check_eq(sb.eval("typeof wrappedCtor()"), "string");
do_check_eq(sb.eval("typeof new wrappedCtor()"), "object");
testStrict(sb);
}
@@ -0,0 +1,13 @@
function run_test() {
let Cu = Components.utils;
let sandbox1 = new Cu.Sandbox(null);
let sandbox2 = new Cu.Sandbox(null);
let arg = Cu.evalInSandbox('({ buf: ArrayBuffer(2) })', sandbox1);
let clonedArg = Cu.cloneInto(Cu.waiveXrays(arg), sandbox2);
do_check_eq(typeof Cu.waiveXrays(clonedArg).buf, "object");
clonedArg = Cu.cloneInto(arg, sandbox2);
do_check_eq(typeof Cu.waiveXrays(clonedArg).buf, "object");
do_check_eq(Cu.waiveXrays(clonedArg).buf.constructor.name, "ArrayBuffer");
}
@@ -0,0 +1,9 @@
function run_test()
{
try {
var sandbox = new Components.utils.Sandbox(null, {"sandboxPrototype" : {}});
do_check_true(false);
} catch (e) {
do_check_true(/must subsume sandboxPrototype/.test(e));
}
}
@@ -0,0 +1,5 @@
const Cu = Components.utils;
function run_test() {
do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).equals(null), "NS_ERROR_ILLEGAL_VALUE");
do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).subsumes(null), "NS_ERROR_ILLEGAL_VALUE");
}
+4
View File
@@ -54,7 +54,11 @@ support-files =
[test_bug1082450.js]
[test_bug1081990.js]
[test_bug1110546.js]
[test_bug1131707.js]
[test_bug1150106.js]
[test_bug1150771.js]
[test_bug1151385.js]
[test_bug1170311.js]
[test_bug_442086.js]
[test_callFunctionWithAsyncStack.js]
[test_file.js]
+21 -3
View File
@@ -301,11 +301,29 @@ bool JSXrayTraits::getOwnPropertyFromTargetIfSafe(JSContext* cx,
return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not same-origin with target");
}
// Disallow non-Xrayable objects.
// Disallow (most) non-Xrayable objects.
XrayType xrayType = GetXrayType(propObj);
if (xrayType == NotXray || xrayType == XrayForOpaqueObject) {
JSAutoCompartment ac(cx, wrapper);
return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not Xrayable");
if (IdentifyStandardInstance(propObj) == JSProto_ArrayBuffer) {
// Given that non-Xrayable objects are now opaque by default,
// this restriction is somewhat more draconian than it needs to
// be. It's true that script can't do much with an opaque
// object, so in general it doesn't make much of a difference.
// But one place it _does_ make a difference is in the
// structured clone algorithm. When traversing an object to
// clone it, the algorithm dutifully traverses inspects the
// security wrapper without unwrapping it, so it won't see
// properties we restrict here. But there are some object types
// that the structured clone algorithm can handle safely even
// without Xrays (i.e. ArrayBuffer, where it just clones the
// underlying byte array).
//
// So we make some special cases here for such situations. Pass
// them through.
} else {
JSAutoCompartment ac(cx, wrapper);
return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not Xrayable");
}
}
// Disallow callables.
@@ -30,7 +30,7 @@ interface URLUtils {
attribute ScalarValueString port;
attribute ScalarValueString pathname;
attribute ScalarValueString search;
attribute URLSearchParams searchParams;
readonly attribute URLSearchParams searchParams;
attribute ScalarValueString hash;
};
+3 -2
View File
@@ -361,7 +361,8 @@ function _setupDebuggerServer(breakpointFiles, callback) {
prefs.setBoolPref("devtools.debugger.log.verbose", true);
}
let {DebuggerServer} = Components.utils.import('resource://gre/modules/devtools/dbg-server.jsm', {});
let {DebuggerServer, OriginalLocation} =
Components.utils.import('resource://gre/modules/devtools/dbg-server.jsm', {});
DebuggerServer.init();
DebuggerServer.addBrowserActors();
DebuggerServer.addActors("resource://testing-common/dbg-actors.js");
@@ -383,7 +384,7 @@ function _setupDebuggerServer(breakpointFiles, callback) {
let threadActor = subject.wrappedJSObject;
for (let file of breakpointFiles) {
let sourceActor = threadActor.sources.source({originalUrl: file});
sourceActor.setBreakpoint(1);
sourceActor._setBreakpoint(new OriginalLocation(sourceActor, 1));
}
} catch (ex) {
do_print("Failed to initialize breakpoints: " + ex + "\n" + ex.stack);
@@ -145,7 +145,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
NS_INTERFACE_MAP_END
URLSearchParams::URLSearchParams()
URLSearchParams::URLSearchParams(URLSearchParamsObserver* aObserver)
{
}
@@ -160,24 +160,11 @@ URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
}
void
URLSearchParams::ParseInput(const nsACString& aInput,
URLSearchParamsObserver* aObserver)
URLSearchParams::ParseInput(const nsACString& aInput)
{
NS_NOTREACHED("Unexpected call to URLSearchParams::ParseInput");
}
void
URLSearchParams::AddObserver(URLSearchParamsObserver* aObserver)
{
NS_NOTREACHED("Unexpected call to URLSearchParams::SetObserver");
}
void
URLSearchParams::RemoveObserver(URLSearchParamsObserver* aObserver)
{
NS_NOTREACHED("Unexpected call to URLSearchParams::SetObserver");
}
void
URLSearchParams::Serialize(nsAString& aValue) const
{
@@ -234,9 +221,9 @@ URLSearchParams::DeleteAll()
}
void
URLSearchParams::NotifyObservers(URLSearchParamsObserver* aExceptObserver)
URLSearchParams::NotifyObserver()
{
NS_NOTREACHED("Unexpected call to URLSearchParams::NotifyObservers");
NS_NOTREACHED("Unexpected call to URLSearchParams::NotifyObserver");
}
} // namespace dom
@@ -496,10 +496,22 @@ ThreadState.prototype = {
// Ignore "interrupted" events, to avoid UI flicker. These are generated
// by the slow script dialog and internal events such as setting
// breakpoints. Pressing the resume button does need to be shown, though.
if (aEvent == "paused" &&
aPacket.why.type == "interrupted" &&
!this.interruptedByResumeButton) {
return;
if (aEvent == "paused") {
if (aPacket.why.type == "interrupted" &&
!this.interruptedByResumeButton) {
return;
} else if (aPacket.why.type == "breakpointConditionThrown" && aPacket.why.message) {
let where = aPacket.frame.where;
let aLocation = {
line: where.line,
column: where.column,
actor: where.source ? where.source.actor : null
};
DebuggerView.Sources.showBreakpointConditionThrownMessage(
aLocation,
aPacket.why.message
);
}
}
this.interruptedByResumeButton = false;
@@ -590,6 +602,10 @@ StackFrames.prototype = {
case "breakpoint":
this._currentBreakpointLocation = aPacket.frame.where;
break;
case "breakpointConditionThrown":
this._currentBreakpointLocation = aPacket.frame.where;
this._conditionThrowMessage = aPacket.why.message;
break;
// If paused by a client evaluation, store the evaluated value.
case "clientEvaluated":
this._currentEvaluation = aPacket.why.frameFinished;
+40 -6
View File
@@ -468,6 +468,19 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._unselectBreakpoint();
},
/**
* Display the message thrown on breakpoint condition
*/
showBreakpointConditionThrownMessage: function(aLocation, aMessage = "") {
let breakpointItem = this.getBreakpoint(aLocation);
if (!breakpointItem) {
return;
}
let attachment = breakpointItem.attachment;
attachment.view.container.classList.add("dbg-breakpoint-condition-thrown");
attachment.view.message.setAttribute("value", aMessage);
},
/**
* Update the checked/unchecked and enabled/disabled states of the buttons in
* the sources toolbar based on the currently selected source's state.
@@ -707,12 +720,13 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* - location: the breakpoint's source location and line number
* - disabled: the breakpoint's disabled state, boolean
* - text: the breakpoint's line text to be displayed
* - message: thrown string when the breakpoint condition throws,
* @return object
* An object containing the breakpoint container, checkbox,
* line number and line text nodes.
*/
_createBreakpointView: function(aOptions) {
let { location, disabled, text } = aOptions;
let { location, disabled, text, message } = aOptions;
let identifier = DebuggerController.Breakpoints.getIdentifier(location);
let checkbox = document.createElement("checkbox");
@@ -729,9 +743,29 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
lineTextNode.setAttribute("crop", "end");
lineTextNode.setAttribute("flex", "1");
let tooltip = text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH);
let tooltip = text ? text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH) : "";
lineTextNode.setAttribute("tooltiptext", tooltip);
let thrownNode = document.createElement("label");
thrownNode.className = "plain dbg-breakpoint-condition-thrown-message dbg-breakpoint-text";
thrownNode.setAttribute("value", message);
thrownNode.setAttribute("crop", "end");
thrownNode.setAttribute("flex", "1");
let bpLineContainer = document.createElement("hbox");
bpLineContainer.className = "plain dbg-breakpoint-line-container";
bpLineContainer.setAttribute("flex", "1");
bpLineContainer.appendChild(lineNumberNode);
bpLineContainer.appendChild(lineTextNode);
let bpDetailContainer = document.createElement("vbox");
bpDetailContainer.className = "plain dbg-breakpoint-detail-container";
bpDetailContainer.setAttribute("flex", "1");
bpDetailContainer.appendChild(bpLineContainer);
bpDetailContainer.appendChild(thrownNode);
let container = document.createElement("hbox");
container.id = "breakpoint-" + identifier;
container.className = "dbg-breakpoint side-menu-widget-item-other";
@@ -743,14 +777,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
checkbox.addEventListener("click", this._onBreakpointCheckboxClick, false);
container.appendChild(checkbox);
container.appendChild(lineNumberNode);
container.appendChild(lineTextNode);
container.appendChild(bpDetailContainer);
return {
container: container,
checkbox: checkbox,
lineNumber: lineNumberNode,
lineText: lineTextNode
lineText: lineTextNode,
message: thrownNode
};
},
@@ -1010,7 +1044,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
let attachment = breakpointItem.attachment;
// Check if this is an enabled conditional breakpoint, and if so,
// save the current conditional epression.
// save the current conditional expression.
let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
if (breakpointPromise) {
let { location } = yield breakpointPromise;
+8 -25
View File
@@ -59,6 +59,7 @@ support-files =
doc_event-listeners-01.html
doc_event-listeners-02.html
doc_event-listeners-03.html
doc_event-listeners-04.html
doc_frame-parameters.html
doc_function-display-name.html
doc_function-search.html
@@ -103,71 +104,49 @@ support-files =
[browser_dbg_aaa_run_first_leaktest.js]
skip-if = e10s && debug
[browser_dbg_addonactor.js]
skip-if = e10s && debug
[browser_dbg_addon-sources.js]
skip-if = e10s && debug
[browser_dbg_addon-modules.js]
skip-if = e10s # TODO
[browser_dbg_addon-modules-unpacked.js]
skip-if = e10s # TODO
[browser_dbg_addon-panels.js]
skip-if = e10s && debug
[browser_dbg_addon-console.js]
skip-if = e10s && debug || os == 'win' # bug 1005274
[browser_dbg_auto-pretty-print-01.js]
skip-if = e10s && debug
[browser_dbg_auto-pretty-print-02.js]
skip-if = e10s && debug
[browser_dbg_bfcache.js]
skip-if = e10s || true # bug 1113935
[browser_dbg_blackboxing-01.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-02.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-03.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-04.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-05.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-06.js]
skip-if = e10s && debug
[browser_dbg_breadcrumbs-access.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-01.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-02.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-03.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-04.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-05.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-06.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-07.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-08.js]
skip-if = e10s && debug
[browser_dbg_break-on-dom-event-01.js]
skip-if = e10s || os == "mac" || e10s # Bug 895426
[browser_dbg_break-on-dom-event-02.js]
skip-if = e10s # TODO
[browser_dbg_break-on-dom-event-03.js]
skip-if = e10s # TODO
[browser_dbg_breakpoints-actual-location.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-actual-location2.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
skip-if = e10s # Bug 1093535
[browser_dbg_breakpoints-button-01.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-button-02.js]
[browser_dbg_breakpoints-condition-thrown-message.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-contextmenu-add.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-contextmenu.js]
skip-if = e10s && debug
[browser_dbg_breakpoints-disabled-reload.js]
skip-if = e10s # Bug 1093535
[browser_dbg_breakpoints-editor.js]
@@ -208,6 +187,8 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_conditional-breakpoints-04.js]
skip-if = e10s && debug
[browser_dbg_conditional-breakpoints-05.js]
skip-if = e10s && debug
[browser_dbg_server-conditional-bp-01.js]
skip-if = e10s && debug
[browser_dbg_server-conditional-bp-02.js]
@@ -216,6 +197,8 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_server-conditional-bp-04.js]
skip-if = e10s && debug
[browser_dbg_server-conditional-bp-05.js]
skip-if = e10s && debug
[browser_dbg_controller-evaluate-01.js]
skip-if = e10s && debug
[browser_dbg_controller-evaluate-02.js]
@@ -0,0 +1,101 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the break-on-dom-events request works for load event listeners.
*/
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
let gClient, gThreadClient;
function test() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
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.");
addTab(TAB_URL)
.then(() => attachThreadActorForUrl(gClient, TAB_URL))
.then(aThreadClient => gThreadClient = aThreadClient)
.then(pauseDebuggee)
.then(testBreakOnLoad)
.then(closeConnection)
.then(finish)
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
function pauseDebuggee() {
let deferred = promise.defer();
gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
is(aPacket.type, "paused",
"We should now be paused.");
is(aPacket.why.type, "debuggerStatement",
"The debugger statement was hit.");
gThreadClient.resume(deferred.resolve);
});
// Spin the event loop before causing the debuggee to pause, to allow
// this function to return first.
executeSoon(() => triggerButtonClick());
return deferred.promise;
}
// Test pause on a load event.
function testBreakOnLoad() {
let deferred = promise.defer();
// Test calling pauseOnDOMEvents from a running state.
gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
is(aPacket.error, undefined,
"The pause-on-load request completed successfully.");
let handlers = ["loadHandler"];
gClient.addListener("paused", function tester(aEvent, aPacket) {
is(aPacket.why.type, "pauseOnDOMEvents",
"A hidden breakpoint was hit.");
is(aPacket.frame.where.line, 15, "Found the load event listener.");
gClient.removeListener("paused", tester);
deferred.resolve();
gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
});
getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
aTabClient.reload();
});
});
});
return deferred.promise;
}
function triggerButtonClick() {
let button = content.document.querySelector("button");
EventUtils.sendMouseEvent({ type: "click" }, button);
}
function closeConnection() {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gThreadClient = null;
});
@@ -0,0 +1,106 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the message which breakpoint condition throws
* could be displayed on UI correctly
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
function test() {
let gTab, gPanel, gDebugger, gEditor;
let gSources, gLocation;
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
.then(addBreakpoints)
.then(() => resumeAndTestThrownMessage(18))
.then(() => resumeAndTestNoThrownMessage(19))
.then(() => resumeAndTestThrownMessage(22))
.then(() => resumeAndFinishTest())
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
callInTab(gTab, "ermahgerd");
});
function resumeAndTestThrownMessage(aLine) {
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
let finished = waitForCaretUpdated(gPanel, aLine).then(() => {
//test that the thrown message is correctly shown
let attachment = gSources.getBreakpoint({ actor: gSources.values[0], line: aLine}).attachment;
ok(attachment.view.container.classList.contains('dbg-breakpoint-condition-thrown'),
"Message on line " + aLine + " should be shown when condition throws.");
});
return finished;
}
function resumeAndTestNoThrownMessage(aLine) {
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
let finished = waitForCaretUpdated(gPanel, aLine).then(() => {
//test that the thrown message is correctly shown
let attachment = gSources.getBreakpoint({ actor: gSources.values[0], line: aLine}).attachment;
ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
"Message on line " + aLine + " should be hidden if condition doesn't throw.");
});
return finished;
}
function resumeAndFinishTest() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED)
gDebugger.gThreadClient.resume();
return finished;
}
function addBreakpoints() {
return promise.resolve(null)
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 18,
condition: " 1a"}))
.then(() => initialCheck(18))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 19,
condition: "true"}))
.then(() => initialCheck(19))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 20,
condition: "false"}))
.then(() => initialCheck(20))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 22,
condition: "randomVar"}))
.then(() => initialCheck(22));
}
function initialCheck(aCaretLine) {
let bp = gSources.getBreakpoint({ actor: gSources.values[0], line: aCaretLine})
let attachment = bp.attachment;
ok(attachment,
"There should be an item for line " + aCaretLine + " in the sources pane.");
let thrownNode = attachment.view.container.querySelector(".dbg-breakpoint-condition-thrown-message");
ok(thrownNode,
"The breakpoint item should contain a thrown message node.")
ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
"The thrown message on line " + aCaretLine + " should be hidden when condition has not been evaluated.")
}
}
@@ -0,0 +1,141 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that conditional breakpoints with an exception-throwing expression
* could pause on hit
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
function test() {
let gTab, gPanel, gDebugger, gEditor;
let gSources, gBreakpoints, gLocation;
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
// This test forces conditional breakpoints to be evaluated on the
// client-side
var client = gPanel.target.client;
client.mainRoot.traits.conditionalBreakpoints = false;
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
.then(addBreakpoints)
.then(() => resumeAndTestBreakpoint(18))
.then(() => resumeAndTestBreakpoint(19))
.then(() => resumeAndTestBreakpoint(20))
.then(() => resumeAndTestBreakpoint(23))
.then(() => resumeAndTestNoBreakpoint())
.then(() => {
// Reset traits back to default value
client.mainRoot.traits.conditionalBreakpoints = true;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
callInTab(gTab, "ermahgerd");
});
function resumeAndTestBreakpoint(aLine) {
let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
return finished;
}
function resumeAndTestNoBreakpoint() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
is(gSources.itemCount, 1,
"Found the expected number of sources.");
is(gEditor.getText().indexOf("ermahgerd"), 253,
"The correct source was loaded initially.");
is(gSources.selectedValue, gSources.values[0],
"The correct source is selected.");
ok(gSources.selectedItem,
"There should be a selected source in the sources pane.");
ok(!gSources._selectedBreakpointItem,
"There should be no selected breakpoint in the sources pane.");
is(gSources._conditionalPopupVisible, false,
"The breakpoint conditional expression popup should not be shown.");
is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
"There should be no visible stackframes.");
is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6,
"There should be thirteen visible breakpoints.");
});
gDebugger.gThreadClient.resume();
return finished;
}
function addBreakpoints() {
return promise.resolve(null)
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 }))
.then(aClient => aClient.conditionalExpression = " 1a")
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 19 }))
.then(aClient => aClient.conditionalExpression = "new Error()")
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 20 }))
.then(aClient => aClient.conditionalExpression = "true")
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 21 }))
.then(aClient => aClient.conditionalExpression = "false")
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 22 }))
.then(aClient => aClient.conditionalExpression = "0")
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 23 }))
.then(aClient => aClient.conditionalExpression = "randomVar");
}
function testBreakpoint(aLine, aHighlightBreakpoint) {
// Highlight the breakpoint only if required.
if (aHighlightBreakpoint) {
let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
return finished;
}
let selectedActor = gSources.selectedValue;
let selectedBreakpoint = gSources._selectedBreakpointItem;
ok(selectedActor,
"There should be a selected item in the sources pane.");
ok(selectedBreakpoint,
"There should be a selected breakpoint in the sources pane.");
let source = gSources.selectedItem.attachment.source;
is(selectedBreakpoint.attachment.actor, source.actor,
"The breakpoint on line " + aLine + " wasn't added on the correct source.");
is(selectedBreakpoint.attachment.line, aLine,
"The breakpoint on line " + aLine + " wasn't found.");
is(!!selectedBreakpoint.attachment.disabled, false,
"The breakpoint on line " + aLine + " should be enabled.");
is(!!selectedBreakpoint.attachment.openPopup, false,
"The breakpoint on line " + aLine + " should not have opened a popup.");
is(gSources._conditionalPopupVisible, false,
"The breakpoint conditional expression popup should not have been shown.");
return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
is(aBreakpointClient.location.url, source.url,
"The breakpoint's client url is correct");
is(aBreakpointClient.location.line, aLine,
"The breakpoint's client line is correct");
isnot(aBreakpointClient.conditionalExpression, undefined,
"The breakpoint on line " + aLine + " should have a conditional expression.");
ok(isCaretPos(gPanel, aLine),
"The editor caret position is not properly set.");
});
}
}
@@ -36,6 +36,7 @@ function test() {
.then(() => resumeAndTestBreakpoint(27))
.then(() => resumeAndTestBreakpoint(28))
.then(() => resumeAndTestBreakpoint(29))
.then(() => resumeAndTestBreakpoint(30))
.then(() => resumeAndTestNoBreakpoint())
.then(() => {
return promise.all([
@@ -0,0 +1,171 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test conditional breakpoints throwing exceptions
* with server support
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
function test() {
// Linux debug test slaves are a bit slow at this test sometimes.
requestLongerTimeout(2);
let gTab, gPanel, gDebugger;
let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gBreakpointsAdded = gBreakpoints._added;
gBreakpointsRemoving = gBreakpoints._removing;
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
.then(() => addBreakpoints())
.then(() => initialChecks())
.then(() => resumeAndTestBreakpoint(18))
.then(() => resumeAndTestBreakpoint(19))
.then(() => resumeAndTestBreakpoint(20))
.then(() => resumeAndTestBreakpoint(23))
.then(() => resumeAndTestNoBreakpoint())
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
callInTab(gTab, "ermahgerd");
});
function addBreakpoints() {
return promise.resolve(null)
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 18,
condition: "1a"
}))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 19,
condition: "new Error()"
}))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 20,
condition: "true"
}))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 21,
condition: "false"
}))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 22,
condition: "undefined"
}))
.then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
line: 23,
condition: "randomVar"
}));
}
function initialChecks() {
is(gDebugger.gThreadClient.state, "paused",
"Should only be getting stack frames while paused.");
is(gSources.itemCount, 1,
"Found the expected number of sources.");
is(gEditor.getText().indexOf("ermahgerd"), 253,
"The correct source was loaded initially.");
is(gSources.selectedValue, gSources.values[0],
"The correct source is selected.");
is(gBreakpointsAdded.size, 6,
"6 breakpoints currently added.");
is(gBreakpointsRemoving.size, 0,
"No breakpoints currently being removed.");
is(gEditor.getBreakpoints().length, 6,
"6 breakpoints currently shown in the editor.");
ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
"_getAdded('foo', 3) returns falsey.");
ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
"_getRemoving('bar', 3) returns falsey.");
}
function resumeAndTestBreakpoint(aLine) {
let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
return finished;
}
function resumeAndTestNoBreakpoint() {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
is(gSources.itemCount, 1,
"Found the expected number of sources.");
is(gEditor.getText().indexOf("ermahgerd"), 253,
"The correct source was loaded initially.");
is(gSources.selectedValue, gSources.values[0],
"The correct source is selected.");
ok(gSources.selectedItem,
"There should be a selected source in the sources pane.")
ok(!gSources._selectedBreakpointItem,
"There should be no selected breakpoint in the sources pane.")
is(gSources._conditionalPopupVisible, false,
"The breakpoint conditional expression popup should not be shown.");
is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
"There should be no visible stackframes.");
is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6,
"There should be thirteen visible breakpoints.");
});
gDebugger.gThreadClient.resume();
return finished;
}
function testBreakpoint(aLine, aHighlightBreakpoint) {
// Highlight the breakpoint only if required.
if (aHighlightBreakpoint) {
let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
return finished;
}
let selectedActor = gSources.selectedValue;
let selectedBreakpoint = gSources._selectedBreakpointItem;
ok(selectedActor,
"There should be a selected item in the sources pane.");
ok(selectedBreakpoint,
"There should be a selected brekapoint in the sources pane.");
is(selectedBreakpoint.attachment.actor, selectedActor,
"The breakpoint on line " + aLine + " wasn't added on the correct source.");
is(selectedBreakpoint.attachment.line, aLine,
"The breakpoint on line " + aLine + " wasn't found.");
is(!!selectedBreakpoint.attachment.disabled, false,
"The breakpoint on line " + aLine + " should be enabled.");
is(!!selectedBreakpoint.attachment.openPopup, false,
"The breakpoint on line " + aLine + " should not have opened a popup.");
is(gSources._conditionalPopupVisible, false,
"The breakpoint conditional expression popup should not have been shown.");
return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
is(aBreakpointClient.location.actor, selectedActor,
"The breakpoint's client url is correct");
is(aBreakpointClient.location.line, aLine,
"The breakpoint's client line is correct");
isnot(aBreakpointClient.condition, undefined,
"The breakpoint on line " + aLine + " should have a conditional expression.");
ok(isCaretPos(gPanel, aLine),
"The editor caret position is not properly set.");
});
}
}
@@ -79,15 +79,15 @@ function testSetBreakpointBlankLine() {
let sourceForm = getSourceForm(gSources, COFFEE_URL);
let source = gDebugger.gThreadClient.source(sourceForm);
source.setBreakpoint({ line: 3 }, aResponse => {
source.setBreakpoint({ line: 7 }, aResponse => {
ok(!aResponse.error,
"Should be able to set a breakpoint in a coffee source file on a blank line.");
ok(aResponse.actualLocation,
"Because 3 is empty, we should have an actualLocation.");
is(aResponse.actualLocation.source.url, COFFEE_URL,
"actualLocation.actor should be source mapped to the coffee file.");
is(aResponse.actualLocation.line, 2,
"actualLocation.line should be source mapped back to 2.");
is(aResponse.actualLocation.line, 8,
"actualLocation.line should be source mapped back to 8.");
deferred.resolve();
});
@@ -147,7 +147,7 @@ function testStepping() {
is(aPacket.frame.environment.bindings.variables.start.value, 0,
"'start' is 0.");
is(aPacket.frame.environment.bindings.variables.stop.value, 5,
"'stop' hasn't been assigned to yet.");
"'stop' is 5.");
is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined",
"'pivot' hasn't been assigned to yet.");
@@ -45,11 +45,11 @@ function testSetBreakpoint() {
let sourceForm = getSourceForm(gSources, JS_URL);
let source = gDebugger.gThreadClient.source(sourceForm);
source.setBreakpoint({ line: 30, column: 21 }, aResponse => {
source.setBreakpoint({ line: 30 }, aResponse => {
ok(!aResponse.error,
"Should be able to set a breakpoint in a js file.");
ok(!aResponse.actualLocation,
"Should be able to set a breakpoint on line 30 and column 10.");
"Should be able to set a breakpoint on line 30.");
gDebugger.gClient.addOneTimeListener("resumed", () => {
waitForCaretAndScopes(gPanel, 30).then(() => {
@@ -89,11 +89,11 @@ function testSetBreakpoint() {
let sourceForm = getSourceForm(gSources, JS_URL);
let source = gDebugger.gThreadClient.source(sourceForm);
source.setBreakpoint({ line: 3, column: 61 }, aResponse => {
source.setBreakpoint({ line: 3, column: 18 }, aResponse => {
ok(!aResponse.error,
"Should be able to set a breakpoint in a js file.");
ok(!aResponse.actualLocation,
"Should be able to set a breakpoint on line 3 and column 61.");
"Should be able to set a breakpoint on line 3 and column 18.");
deferred.resolve();
});
@@ -0,0 +1,23 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger test page</title>
</head>
<body>
<button>Click me!</button>
<script type="text/javascript">
window.addEventListener("load", function onload() {
var button = document.querySelector("button");
button.onclick = function () {
debugger;
};
});
</script>
</body>
</html>
+140
View File
@@ -295,6 +295,146 @@ ActorPool.prototype = {
exports.ActorPool = ActorPool;
/**
* An OriginalLocation represents a location in an original source.
*
* @param SourceActor actor
* A SourceActor representing an original source.
* @param Number line
* A line within the given source.
* @param Number column
* A column within the given line.
* @param String name
* The name of the symbol corresponding to this OriginalLocation.
*/
function OriginalLocation(actor, line, column, name) {
this._connection = actor ? actor.conn : null;
this._actorID = actor ? actor.actorID : undefined;
this._line = line;
this._column = column;
this._name = name;
}
OriginalLocation.fromGeneratedLocation = function (generatedLocation) {
return new OriginalLocation(
generatedLocation.generatedSourceActor,
generatedLocation.generatedLine,
generatedLocation.generatedColumn
);
};
OriginalLocation.prototype = {
get originalSourceActor() {
return this._connection ? this._connection.getActor(this._actorID) : null;
},
get originalUrl() {
let actor = this.originalSourceActor;
let source = actor.source;
return source ? source.url : actor._originalUrl;
},
get originalLine() {
return this._line;
},
get originalColumn() {
return this._column;
},
get originalName() {
return this._name;
},
get generatedSourceActor() {
throw new Error("Shouldn't access generatedSourceActor from an OriginalLocation");
},
get generatedLine() {
throw new Error("Shouldn't access generatedLine from an OriginalLocation");
},
get generatedColumn() {
throw new Error("Shouldn't access generatedColumn from an Originallocation");
},
equals: function (other) {
return this.originalSourceActor.url == other.originalSourceActor.url &&
this.originalLine === other.originalLine;
},
toJSON: function () {
return {
source: this.originalSourceActor.form(),
line: this.originalLine,
column: this.originalColumn
};
}
};
exports.OriginalLocation = OriginalLocation;
/**
* A GeneratedLocation represents a location in an original source.
*
* @param SourceActor actor
* A SourceActor representing a generated source.
* @param Number line
* A line within the given source.
* @param Number column
* A column within the given line.
*/
function GeneratedLocation(actor, line, column) {
this._connection = actor ? actor.conn : null;
this._actorID = actor ? actor.actorID : undefined;
this._line = line;
this._column = column;
}
GeneratedLocation.fromOriginalLocation = function (originalLocation) {
return new GeneratedLocation(
originalLocation.originalSourceActor,
originalLocation.originalLine,
originalLocation.originalColumn
);
};
GeneratedLocation.prototype = {
get originalSourceActor() {
throw new Error();
},
get originalUrl() {
throw new Error("Shouldn't access originalUrl from a GeneratedLocation");
},
get originalLine() {
throw new Error("Shouldn't access originalLine from a GeneratedLocation");
},
get originalColumn() {
throw new Error("Shouldn't access originalColumn from a GeneratedLocation");
},
get originalName() {
throw new Error("Shouldn't access originalName from a GeneratedLocation");
},
get generatedSourceActor() {
return this._connection ? this._connection.getActor(this._actorID) : null;
},
get generatedLine() {
return this._line;
},
get generatedColumn() {
return this._column;
}
};
exports.GeneratedLocation = GeneratedLocation;
// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
// implemented.
exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
@@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const protocol = require("devtools/server/protocol");
const { method, RetVal, Arg, types } = protocol;
/**
* The Promises Actor provides support for getting the list of live promises and
* observing changes to their settlement state.
*/
let PromisesActor = protocol.ActorClass({
typeName: "promises",
/**
* @param conn DebuggerServerConnection.
* @param parent TabActor|RootActor
*/
initialize: function(conn, parent) {
protocol.Actor.prototype.initialize.call(this, conn);
},
destroy: function() {
protocol.Actor.prototype.destroy.call(this, conn);
},
});
exports.PromisesActor = PromisesActor;
exports.PromisesFront = protocol.FrontClass(PromisesActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
this.actorID = form.promisesActor;
this.manage(this);
}
});
File diff suppressed because it is too large Load Diff
+12 -13
View File
@@ -8,7 +8,7 @@ const { Cu } = require("chrome");
const { DebuggerServer } = require("devtools/server/main");
const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
const Debugger = require("Debugger");
const { getOffsetColumn } = require("devtools/server/actors/common");
const { GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
const promise = require("promise");
Cu.import("resource://gre/modules/Task.jsm");
@@ -339,25 +339,25 @@ TracerActor.prototype = {
if (aFrame.script) {
let sources = this._parent.threadActor.sources;
sourceMappedLocation = yield sources.getOriginalLocation({
sourceActor: sources.createNonSourceMappedActor(aFrame.script.source),
url: aFrame.script.source.url,
line: aFrame.script.startLine,
sourceMappedLocation = yield sources.getOriginalLocation(new GeneratedLocation(
sources.createNonSourceMappedActor(aFrame.script.source),
aFrame.script.startLine,
// We should return the location of the start of the script, but
// Debugger.Script does not provide complete start locations (bug
// 901138). Instead, return the current offset (the location of the
// first statement in the function).
column: getOffsetColumn(aFrame.offset, aFrame.script)
});
getOffsetColumn(aFrame.offset, aFrame.script)
));
}
}
if (this._requestsForTraceType.name) {
if (sourceMappedLocation && sourceMappedLocation.name) {
packet.name = sourceMappedLocation.name;
if (sourceMappedLocation && sourceMappedLocation.originalName) {
packet.name = sourceMappedLocation.originalName;
} else {
packet.name = name;
}
packet.name = name;
}
if (this._requestsForTraceType.location) {
@@ -365,10 +365,9 @@ TracerActor.prototype = {
// Don't copy sourceMappedLocation directly because it
// contains a reference to the source actor
packet.location = {
url: sourceMappedLocation.url,
line: sourceMappedLocation.line,
column: sourceMappedLocation.column,
name: sourceMappedLocation.name
url: sourceMappedLocation.originalUrl,
line: sourceMappedLocation.originalLine,
column: sourceMappedLocation.originalColumn
};
}
}
@@ -79,6 +79,18 @@ ScriptStore.prototype = {
return this._scripts.items;
},
getScriptsBySourceActor(sourceActor) {
return sourceActor.source ?
this.getScriptsBySource(sourceActor.source) :
this.getScriptsByURL(sourceActor._originalUrl);
},
getScriptsBySourceActorAndLine(sourceActor, line) {
return sourceActor.source ?
this.getScriptsBySourceAndLine(sourceActor.source, line) :
this.getScriptsByURLAndLine(sourceActor._originalUrl, line);
},
/**
* Get all scripts produced from the given source.
*
@@ -0,0 +1,751 @@
const Services = require("Services");
const { Ci, Cu } = require("chrome");
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const EventEmitter = require("devtools/toolkit/event-emitter");
const { dbg_assert, fetch } = require("devtools/toolkit/DevToolsUtils");
const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
const { resolve } = require("promise");
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
/**
* Manages the sources for a thread. Handles source maps, locations in the
* sources, etc for ThreadActors.
*/
function TabSources(threadActor, allowSourceFn=() => true) {
EventEmitter.decorate(this);
this._thread = threadActor;
this._useSourceMaps = true;
this._autoBlackBox = true;
this._anonSourceMapId = 1;
this.allowSource = source => {
return !isHiddenSource(source) && allowSourceFn(source);
}
this.blackBoxedSources = new Set();
this.prettyPrintedSources = new Map();
// generated Debugger.Source -> promise of SourceMapConsumer
this._sourceMaps = new Map();
// sourceMapURL -> promise of SourceMapConsumer
this._sourceMapCache = Object.create(null);
// Debugger.Source -> SourceActor
this._sourceActors = new Map();
// url -> SourceActor
this._sourceMappedSourceActors = Object.create(null);
}
/**
* Matches strings of the form "foo.min.js" or "foo-min.js", etc. If the regular
* expression matches, we can be fairly sure that the source is minified, and
* treat it as such.
*/
const MINIFIED_SOURCE_REGEXP = /\bmin\.js$/;
TabSources.prototype = {
/**
* Update preferences and clear out existing sources
*/
reconfigure: function(options) {
if('useSourceMaps' in options) {
this._useSourceMaps = options.useSourceMaps;
}
if('autoBlackBox' in options) {
this._autoBlackBox = options.autoBlackBox;
}
this.reset();
},
/**
* Clear existing sources so they are recreated on the next access.
*
* @param Object opts
* Specify { sourceMaps: true } if you also want to clear
* the source map cache (usually done on reload).
*/
reset: function(opts={}) {
this._sourceActors = new Map();
this._sourceMaps = new Map();
this._sourceMappedSourceActors = Object.create(null);
if(opts.sourceMaps) {
this._sourceMapCache = Object.create(null);
}
},
/**
* Return the source actor representing the `source` (or
* `originalUrl`), creating one if none exists already. May return
* null if the source is disallowed.
*
* @param Debugger.Source source
* The source to make an actor for
* @param String originalUrl
* The original source URL of a sourcemapped source
* @param optional Debguger.Source generatedSource
* The generated source that introduced this source via source map,
* if any.
* @param optional String contentType
* The content type of the source, if immediately available.
* @returns a SourceActor representing the source or null.
*/
source: function ({ source, originalUrl, generatedSource,
isInlineSource, contentType }) {
dbg_assert(source || (originalUrl && generatedSource),
"TabSources.prototype.source needs an originalUrl or a source");
if (source) {
// If a source is passed, we are creating an actor for a real
// source, which may or may not be sourcemapped.
if (!this.allowSource(source)) {
return null;
}
// It's a hack, but inline HTML scripts each have real sources,
// but we want to represent all of them as one source as the
// HTML page. The actor representing this fake HTML source is
// stored in this array, which always has a URL, so check it
// first.
if (source.url in this._sourceMappedSourceActors) {
return this._sourceMappedSourceActors[source.url];
}
if (isInlineSource) {
// If it's an inline source, the fake HTML source hasn't been
// created yet (would have returned above), so flip this source
// into a sourcemapped state by giving it an `originalUrl` which
// is the HTML url.
originalUrl = source.url;
source = null;
}
else if (this._sourceActors.has(source)) {
return this._sourceActors.get(source);
}
}
else if (originalUrl) {
// Not all "original" scripts are distinctly separate from the
// generated script. Pretty-printed sources have a sourcemap for
// themselves, so we need to make sure there a real source
// doesn't already exist with this URL.
for (let [source, actor] of this._sourceActors) {
if (source.url === originalUrl) {
return actor;
}
}
if (originalUrl in this._sourceMappedSourceActors) {
return this._sourceMappedSourceActors[originalUrl];
}
}
let actor = new SourceActor({
thread: this._thread,
source: source,
originalUrl: originalUrl,
generatedSource: generatedSource,
contentType: contentType
});
let sourceActorStore = this._thread.sourceActorStore;
var id = sourceActorStore.getReusableActorId(source, originalUrl);
if (id) {
actor.actorID = id;
}
this._thread.threadLifetimePool.addActor(actor);
sourceActorStore.setReusableActorId(source, originalUrl, actor.actorID);
if (this._autoBlackBox && this._isMinifiedURL(actor.url)) {
this.blackBox(actor.url);
}
if (source) {
this._sourceActors.set(source, actor);
}
else {
this._sourceMappedSourceActors[originalUrl] = actor;
}
this._emitNewSource(actor);
return actor;
},
_emitNewSource: function(actor) {
if(!actor.source) {
// Always notify if we don't have a source because that means
// it's something that has been sourcemapped, or it represents
// the HTML file that contains inline sources.
this.emit('newSource', actor);
}
else {
// If sourcemapping is enabled and a source has sourcemaps, we
// create `SourceActor` instances for both the original and
// generated sources. The source actors for the generated
// sources are only for internal use, however; breakpoints are
// managed by these internal actors. We only want to notify the
// user of the original sources though, so if the actor has a
// `Debugger.Source` instance and a valid source map (meaning
// it's a generated source), don't send the notification.
this.fetchSourceMap(actor.source).then(map => {
if(!map) {
this.emit('newSource', actor);
}
});
}
},
getSourceActor: function(source) {
if (source.url in this._sourceMappedSourceActors) {
return this._sourceMappedSourceActors[source.url];
}
if (this._sourceActors.has(source)) {
return this._sourceActors.get(source);
}
throw new Error('getSource: could not find source actor for ' +
(source.url || 'source'));
},
getSourceActorByURL: function(url) {
if (url) {
for (let [source, actor] of this._sourceActors) {
if (source.url === url) {
return actor;
}
}
if (url in this._sourceMappedSourceActors) {
return this._sourceMappedSourceActors[url];
}
}
throw new Error('getSourceByURL: could not find source for ' + url);
},
/**
* Returns true if the URL likely points to a minified resource, false
* otherwise.
*
* @param String aURL
* The URL to test.
* @returns Boolean
*/
_isMinifiedURL: function (aURL) {
try {
let url = Services.io.newURI(aURL, null, null)
.QueryInterface(Ci.nsIURL);
return MINIFIED_SOURCE_REGEXP.test(url.fileName);
} catch (e) {
// Not a valid URL so don't try to parse out the filename, just test the
// whole thing with the minified source regexp.
return MINIFIED_SOURCE_REGEXP.test(aURL);
}
},
/**
* Create a source actor representing this source. This ignores
* source mapping and always returns an actor representing this real
* source. Use `createSourceActors` if you want to respect source maps.
*
* @param Debugger.Source aSource
* The source instance to create an actor for.
* @returns SourceActor
*/
createNonSourceMappedActor: function (aSource) {
// Don't use getSourceURL because we don't want to consider the
// displayURL property if it's an eval source. We only want to
// consider real URLs, otherwise if there is a URL but it's
// invalid the code below will not set the content type, and we
// will later try to fetch the contents of the URL to figure out
// the content type, but it's a made up URL for eval sources.
let url = isEvalSource(aSource) ? null : aSource.url;
let spec = { source: aSource };
// XXX bug 915433: We can't rely on Debugger.Source.prototype.text
// if the source is an HTML-embedded <script> tag. Since we don't
// have an API implemented to detect whether this is the case, we
// need to be conservative and only treat valid js files as real
// sources. Otherwise, use the `originalUrl` property to treat it
// as an HTML source that manages multiple inline sources.
if (url) {
try {
let urlInfo = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
if (urlInfo.fileExtension === "html") {
spec.isInlineSource = true;
}
else if (urlInfo.fileExtension === "js") {
spec.contentType = "text/javascript";
}
} catch(ex) {
// Not a valid URI.
// bug 1124536: fix getSourceText on scripts associated "javascript:SOURCE" urls
// (e.g. 'evaluate(sandbox, sourcecode, "javascript:"+sourcecode)' )
if (url.indexOf("javascript:") === 0) {
spec.contentType = "text/javascript";
}
}
}
else {
// Assume the content is javascript if there's no URL
spec.contentType = "text/javascript";
}
return this.source(spec);
},
/**
* This is an internal function that returns a promise of an array
* of source actors representing all the source mapped sources of
* `aSource`, or `null` if the source is not sourcemapped or
* sourcemapping is disabled. Users should call `createSourceActors`
* instead of this.
*
* @param Debugger.Source aSource
* The source instance to create actors for.
* @return Promise of an array of source actors
*/
_createSourceMappedActors: function (aSource) {
if (!this._useSourceMaps || !aSource.sourceMapURL) {
return resolve(null);
}
return this.fetchSourceMap(aSource)
.then(map => {
if (map) {
return [
this.source({ originalUrl: s, generatedSource: aSource })
for (s of map.sources)
].filter(isNotNull);
}
return null;
});
},
/**
* Creates the source actors representing the appropriate sources
* of `aSource`. If sourcemapped, returns actors for all of the original
* sources, otherwise returns a 1-element array with the actor for
* `aSource`.
*
* @param Debugger.Source aSource
* The source instance to create actors for.
* @param Promise of an array of source actors
*/
createSourceActors: function(aSource) {
return this._createSourceMappedActors(aSource).then(actors => {
let actor = this.createNonSourceMappedActor(aSource);
return (actors || [actor]).filter(isNotNull);
});
},
/**
* Return a promise of a SourceMapConsumer for the source map for
* `aSource`; if we already have such a promise extant, return that.
* This will fetch the source map if we don't have a cached object
* and source maps are enabled (see `_fetchSourceMap`).
*
* @param Debugger.Source aSource
* The source instance to get sourcemaps for.
* @return Promise of a SourceMapConsumer
*/
fetchSourceMap: function (aSource) {
if (this._sourceMaps.has(aSource)) {
return this._sourceMaps.get(aSource);
}
else if (!aSource || !aSource.sourceMapURL) {
return resolve(null);
}
let sourceMapURL = aSource.sourceMapURL;
if (aSource.url) {
sourceMapURL = this._normalize(sourceMapURL, aSource.url);
}
let result = this._fetchSourceMap(sourceMapURL, aSource.url);
// The promises in `_sourceMaps` must be the exact same instances
// as returned by `_fetchSourceMap` for `clearSourceMapCache` to work.
this._sourceMaps.set(aSource, result);
return result;
},
/**
* Return a promise of a SourceMapConsumer for the source map for
* `aSource`. The resolved result may be null if the source does not
* have a source map or source maps are disabled.
*/
getSourceMap: function(aSource) {
return resolve(this._sourceMaps.get(aSource));
},
/**
* Set a SourceMapConsumer for the source map for
* |aSource|.
*/
setSourceMap: function(aSource, aMap) {
this._sourceMaps.set(aSource, resolve(aMap));
},
/**
* Return a promise of a SourceMapConsumer for the source map located at
* |aAbsSourceMapURL|, which must be absolute. If there is already such a
* promise extant, return it. This will not fetch if source maps are
* disabled.
*
* @param string aAbsSourceMapURL
* The source map URL, in absolute form, not relative.
* @param string aScriptURL
* When the source map URL is a data URI, there is no sourceRoot on the
* source map, and the source map's sources are relative, we resolve
* them from aScriptURL.
*/
_fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
if (!this._useSourceMaps) {
return resolve(null);
}
else if (this._sourceMapCache[aAbsSourceMapURL]) {
return this._sourceMapCache[aAbsSourceMapURL];
}
let fetching = fetch(aAbsSourceMapURL, { loadFromCache: false })
.then(({ content }) => {
let map = new SourceMapConsumer(content);
this._setSourceMapRoot(map, aAbsSourceMapURL, aSourceURL);
return map;
})
.then(null, error => {
if (!DevToolsUtils.reportingDisabled) {
DevToolsUtils.reportException("TabSources.prototype._fetchSourceMap", error);
}
return null;
});
this._sourceMapCache[aAbsSourceMapURL] = fetching;
return fetching;
},
/**
* Sets the source map's sourceRoot to be relative to the source map url.
*/
_setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) {
const base = this._dirname(
aAbsSourceMapURL.indexOf("data:") === 0
? aScriptURL
: aAbsSourceMapURL);
aSourceMap.sourceRoot = aSourceMap.sourceRoot
? this._normalize(aSourceMap.sourceRoot, base)
: base;
},
_dirname: function (aPath) {
return Services.io.newURI(
".", null, Services.io.newURI(aPath, null, null)).spec;
},
/**
* Clears the source map cache. Source maps are cached by URL so
* they can be reused across separate Debugger instances (once in
* this cache, they will never be reparsed again). They are
* also cached by Debugger.Source objects for usefulness. By default
* this just removes the Debugger.Source cache, but you can remove
* the lower-level URL cache with the `hard` option.
*
* @param aSourceMapURL string
* The source map URL to uncache
* @param opts object
* An object with the following properties:
* - hard: Also remove the lower-level URL cache, which will
* make us completely forget about the source map.
*/
clearSourceMapCache: function(aSourceMapURL, opts = { hard: false }) {
let oldSm = this._sourceMapCache[aSourceMapURL];
if (opts.hard) {
delete this._sourceMapCache[aSourceMapURL];
}
if (oldSm) {
// Clear out the current cache so all sources will get the new one
for (let [source, sm] of this._sourceMaps.entries()) {
if (sm === oldSm) {
this._sourceMaps.delete(source);
}
}
}
},
/*
* Forcefully change the source map of a source, changing the
* sourceMapURL and installing the source map in the cache. This is
* necessary to expose changes across Debugger instances
* (pretty-printing is the use case). Generate a random url if one
* isn't specified, allowing you to set "anonymous" source maps.
*
* @param aSource Debugger.Source
* The source to change the sourceMapURL property
* @param aUrl string
* The source map URL (optional)
* @param aMap SourceMapConsumer
* The source map instance
*/
setSourceMapHard: function(aSource, aUrl, aMap) {
let url = aUrl;
if (!url) {
// This is a littly hacky, but we want to forcefully set a
// sourcemap regardless of sourcemap settings. We want to
// literally change the sourceMapURL so that all debuggers will
// get this and pretty-printing will Just Work (Debugger.Source
// instances are per-debugger, so we can't key off that). To
// avoid tons of work serializing the sourcemap into a data url,
// just make a fake URL and stick the sourcemap there.
url = "internal://sourcemap" + (this._anonSourceMapId++) + '/';
}
aSource.sourceMapURL = url;
// Forcefully set the sourcemap cache. This will be used even if
// sourcemaps are disabled.
this._sourceMapCache[url] = resolve(aMap);
},
/**
* Return the non-source-mapped location of the given Debugger.Frame. If the
* frame does not have a script, the location's properties are all null.
*
* @param Debugger.Frame aFrame
* The frame whose location we are getting.
* @returns Object
* Returns an object of the form { source, line, column }
*/
getFrameLocation: function (aFrame) {
if (!aFrame || !aFrame.script) {
return new GeneratedLocation();
}
return new GeneratedLocation(
this.createNonSourceMappedActor(aFrame.script.source),
aFrame.script.getOffsetLine(aFrame.offset),
getOffsetColumn(aFrame.offset, aFrame.script)
);
},
/**
* Returns a promise of the location in the original source if the source is
* source mapped, otherwise a promise of the same location. This can
* be called with a source from *any* Debugger instance and we make
* sure to that it works properly, reusing source maps if already
* fetched. Use this from any actor that needs sourcemapping.
*/
getOriginalLocation: function (generatedLocation) {
let {
generatedSourceActor,
generatedLine,
generatedColumn
} = generatedLocation;
let source = generatedSourceActor.source;
let url = source ? source.url : generatedSourceActor._originalUrl;
// In certain scenarios the source map may have not been fetched
// yet (or at least tied to this Debugger.Source instance), so use
// `fetchSourceMap` instead of `getSourceMap`. This allows this
// function to be called from anywere (across debuggers) and it
// should just automatically work.
return this.fetchSourceMap(source).then(map => {
if (map) {
let {
source: originalUrl,
line: originalLine,
column: originalColumn,
name: originalName
} = map.originalPositionFor({
line: generatedLine,
column: generatedColumn == null ? Infinity : generatedColumn
});
// Since the `Debugger.Source` instance may come from a
// different `Debugger` instance (any actor can call this
// method), we can't rely on any of the source discovery
// setup (`_discoverSources`, etc) to have been run yet. So
// we have to assume that the actor may not already exist,
// and we might need to create it, so use `source` and give
// it the required parameters for a sourcemapped source.
return new OriginalLocation(
originalUrl ? this.source({
originalUrl: originalUrl,
generatedSource: source
}) : null,
originalLine,
originalColumn,
originalName
);
}
// No source map
return OriginalLocation.fromGeneratedLocation(generatedLocation);
});
},
/**
* Returns a promise of the location in the generated source corresponding to
* the original source and line given.
*
* When we pass a script S representing generated code to `sourceMap`,
* above, that returns a promise P. The process of resolving P populates
* the tables this function uses; thus, it won't know that S's original
* source URLs map to S until P is resolved.
*/
getGeneratedLocation: function (originalLocation) {
let { originalSourceActor } = originalLocation;
// Both original sources and normal sources could have sourcemaps,
// because normal sources can be pretty-printed which generates a
// sourcemap for itself. Check both of the source properties to make it work
// for both kinds of sources.
let source = originalSourceActor.source || originalSourceActor.generatedSource;
// See comment about `fetchSourceMap` in `getOriginalLocation`.
return this.fetchSourceMap(source).then((map) => {
if (map) {
let {
originalLine,
originalColumn
} = originalLocation;
let {
line: generatedLine,
column: generatedColumn
} = map.generatedPositionFor({
source: originalSourceActor.url,
line: originalLine,
column: originalColumn == null ? 0 : originalColumn,
bias: SourceMapConsumer.LEAST_UPPER_BOUND
});
return new GeneratedLocation(
this.createNonSourceMappedActor(source),
generatedLine,
generatedColumn
);
}
return GeneratedLocation.fromOriginalLocation(originalLocation);
});
},
/**
* Returns true if URL for the given source is black boxed.
*
* @param aURL String
* The URL of the source which we are checking whether it is black
* boxed or not.
*/
isBlackBoxed: function (aURL) {
return this.blackBoxedSources.has(aURL);
},
/**
* Add the given source URL to the set of sources that are black boxed.
*
* @param aURL String
* The URL of the source which we are black boxing.
*/
blackBox: function (aURL) {
this.blackBoxedSources.add(aURL);
},
/**
* Remove the given source URL to the set of sources that are black boxed.
*
* @param aURL String
* The URL of the source which we are no longer black boxing.
*/
unblackBox: function (aURL) {
this.blackBoxedSources.delete(aURL);
},
/**
* Returns true if the given URL is pretty printed.
*
* @param aURL String
* The URL of the source that might be pretty printed.
*/
isPrettyPrinted: function (aURL) {
return this.prettyPrintedSources.has(aURL);
},
/**
* Add the given URL to the set of sources that are pretty printed.
*
* @param aURL String
* The URL of the source to be pretty printed.
*/
prettyPrint: function (aURL, aIndent) {
this.prettyPrintedSources.set(aURL, aIndent);
},
/**
* Return the indent the given URL was pretty printed by.
*/
prettyPrintIndent: function (aURL) {
return this.prettyPrintedSources.get(aURL);
},
/**
* Remove the given URL from the set of sources that are pretty printed.
*
* @param aURL String
* The URL of the source that is no longer pretty printed.
*/
disablePrettyPrint: function (aURL) {
this.prettyPrintedSources.delete(aURL);
},
/**
* Normalize multiple relative paths towards the base paths on the right.
*/
_normalize: function (...aURLs) {
dbg_assert(aURLs.length > 1, "Should have more than 1 URL");
let base = Services.io.newURI(aURLs.pop(), null, null);
let url;
while ((url = aURLs.pop())) {
base = Services.io.newURI(url, null, base);
}
return base.spec;
},
iter: function () {
let actors = Object.keys(this._sourceMappedSourceActors).map(k => {
return this._sourceMappedSourceActors[k];
});
for (let actor of this._sourceActors.values()) {
if (!this._sourceMaps.has(actor.source)) {
actors.push(actor);
}
}
return actors;
}
};
/*
* Checks if a source should never be displayed to the user because
* it's either internal or we don't support in the UI yet.
*/
function isHiddenSource(aSource) {
// Ignore the internal Function.prototype script
return aSource.text === '() {\n}';
}
/**
* Returns true if its argument is not null.
*/
function isNotNull(aThing) {
return aThing !== null;
}
exports.TabSources = TabSources;
exports.isHiddenSource = isHiddenSource;
@@ -13,6 +13,7 @@ let { RootActor } = require("devtools/server/actors/root");
let { DebuggerServer } = require("devtools/server/main");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
let { dbg_assert } = DevToolsUtils;
let { TabSources, isHiddenSource } = require("./utils/TabSources");
let makeDebugger = require("./utils/make-debugger");
let mapURIToAddonID = require("./utils/map-uri-to-addon-id");
@@ -598,6 +599,7 @@ function TabActor(aConnection)
// A map of actor names to actor instances provided by extensions.
this._extraActors = {};
this._exited = false;
this._sources = null;
// Map of DOM stylesheets to StyleSheetActors
this._styleSheetActors = new Map();
@@ -767,6 +769,14 @@ TabActor.prototype = {
return null;
},
get sources() {
if (!this._sources) {
dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
this._sources = new TabSources(this.threadActor);
}
return this._sources;
},
/**
* This is called by BrowserTabList.getList for existing tab actors prior to
* calling |form| below. It can be used to do any async work that may be
@@ -1109,6 +1119,7 @@ TabActor.prototype = {
this._contextPool = null;
this.threadActor.exit();
this.threadActor = null;
this._sources = null;
},
/**
@@ -1429,6 +1440,7 @@ TabActor.prototype = {
// TODO bug 997119: move that code to ThreadActor by listening to window-ready
let threadActor = this.threadActor;
if (isTopLevel) {
this.sources.reset({ sourceMaps: true });
threadActor.clearDebuggees();
if (threadActor.dbg) {
threadActor.dbg.enabled = true;
@@ -1877,6 +1889,15 @@ BrowserAddonActor.prototype = {
return this._global;
},
get sources() {
if (!this._sources) {
dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
this._sources = new TabSources(this._threadActor, this._allowSource);
}
return this._sources;
},
form: function BAA_form() {
dbg_assert(this.actorID, "addon should have an actorID.");
if (!this._consoleActor) {
@@ -1957,6 +1978,7 @@ BrowserAddonActor.prototype = {
this._contextPool.removeActor(this._threadActor);
this._threadActor = null;
this._sources = null;
return { type: "detached" };
},
@@ -2030,6 +2052,20 @@ BrowserAddonActor.prototype = {
return false;
},
/**
* 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: function(aSource) {
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") {
return false;
}
return true;
},
/**
* Yield the current set of globals associated with this addon that should be
* added as debuggees.
+1
View File
@@ -23,3 +23,4 @@ let server = devtools.require("devtools/server/main");
this.DebuggerServer = server.DebuggerServer;
this.ActorPool = server.ActorPool;
this.OriginalLocation = server.OriginalLocation;
+12 -6
View File
@@ -12,7 +12,8 @@
*/
let { Ci, Cc, CC, Cu, Cr } = require("chrome");
let Services = require("Services");
let { ActorPool, RegisteredActorFactory, ObservedActorFactory } = require("devtools/server/actors/common");
let { ActorPool, OriginalLocation, RegisteredActorFactory,
ObservedActorFactory } = require("devtools/server/actors/common");
let { LocalDebuggerTransport, ChildDebuggerTransport } =
require("devtools/toolkit/transport/transport");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
@@ -537,6 +538,11 @@ var DebuggerServer = {
constructor: "AnimationsActor",
type: { tab: true }
});
this.registerModule("devtools/server/actors/promises", {
prefix: "promises",
constructor: "PromisesActor",
type: { global: true, tab: true }
});
},
/**
@@ -1130,9 +1136,14 @@ EventEmitter.decorate(DebuggerServer);
if (this.exports) {
exports.DebuggerServer = DebuggerServer;
exports.ActorPool = ActorPool;
exports.OriginalLocation = OriginalLocation;
}
// Needed on B2G (See header note)
this.DebuggerServer = DebuggerServer;
this.ActorPool = ActorPool;
this.OriginalLocation = OriginalLocation;
// When using DebuggerServer.addActors, some symbols are expected to be in
// the scope of the added actor even before the corresponding modules are
@@ -1143,11 +1154,6 @@ includes.forEach(name => {
DebuggerServer[name] = this[name];
});
// Export ActorPool for requirers of main.js
if (this.exports) {
exports.ActorPool = ActorPool;
}
/**
* Creates a DebuggerServerConnection.
*
+2
View File
@@ -75,6 +75,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [
'actors/preference.js',
'actors/pretty-print-worker.js',
'actors/profiler.js',
'actors/promises.js',
'actors/root.js',
'actors/script.js',
'actors/settings.js',
@@ -99,6 +100,7 @@ EXTRA_JS_MODULES.devtools.server.actors.utils += [
'actors/utils/map-uri-to-addon-id.js',
'actors/utils/ScriptStore.js',
'actors/utils/stack.js',
'actors/utils/TabSources.js'
]
FAIL_ON_WARNINGS = True
@@ -209,6 +209,20 @@ function initTestDebuggerServer(aServer = DebuggerServer)
aServer.init(function () { return true; });
}
/**
* Initialize the testing debugger server with a tab whose title is |title|.
*/
function startTestDebuggerServer(title, server = DebuggerServer) {
initTestDebuggerServer(server);
addTestGlobal(title);
DebuggerServer.addTabActors();
let transport = DebuggerServer.connectPipe();
let client = new DebuggerClient(transport);
return connect(client).then(() => client);
}
function initTestTracerServer(aServer = DebuggerServer)
{
aServer.registerModule("xpcshell-test/testactors");
@@ -246,6 +260,11 @@ function get_chrome_actors(callback)
});
}
function getChromeActors(client, server = DebuggerServer) {
server.allowChromeProcess = true;
return client.getProcess().then(response => response.form);
}
/**
* Takes a relative file path and returns the absolute file url for it.
*/
@@ -21,13 +21,13 @@ function run_test()
function test_get_actor() {
let bpStore = new BreakpointActorMap();
let location = {
sourceActor: { actor: 'actor1' },
line: 3
originalSourceActor: { actor: 'actor1' },
originalLine: 3
};
let columnLocation = {
sourceActor: { actor: 'actor2' },
line: 5,
column: 15
originalSourceActor: { actor: 'actor2' },
originalLine: 5,
originalColumn: 15
};
// Shouldn't have breakpoint
@@ -59,9 +59,9 @@ function test_set_actor() {
// Breakpoint with column
let bpStore = new BreakpointActorMap();
let location = {
sourceActor: { actor: 'actor1' },
line: 10,
column: 9
originalSourceActor: { actor: 'actor1' },
originalLine: 10,
originalColumn: 9
};
bpStore.setActor(location, {});
do_check_true(!!bpStore.getActor(location),
@@ -69,8 +69,8 @@ function test_set_actor() {
// Breakpoint without column (whole line breakpoint)
location = {
sourceActor: { actor: 'actor2' },
line: 103
originalSourceActor: { actor: 'actor2' },
originalLine: 103
};
bpStore.setActor(location, {});
do_check_true(!!bpStore.getActor(location),
@@ -81,9 +81,9 @@ function test_delete_actor() {
// Breakpoint with column
let bpStore = new BreakpointActorMap();
let location = {
sourceActor: { actor: 'actor1' },
line: 10,
column: 9
originalSourceActor: { actor: 'actor1' },
originalLine: 10,
originalColumn: 9
};
bpStore.setActor(location, {});
bpStore.deleteActor(location);
@@ -92,8 +92,8 @@ function test_delete_actor() {
// Breakpoint without column (whole line breakpoint)
location = {
sourceActor: { actor: 'actor2' },
line: 103
originalSourceActor: { actor: 'actor2' },
originalLine: 103
};
bpStore.setActor(location, {});
bpStore.deleteActor(location);
@@ -103,14 +103,14 @@ function test_delete_actor() {
function test_find_actors() {
let bps = [
{ sourceActor: { actor: "actor1" }, line: 10 },
{ sourceActor: { actor: "actor1" }, line: 10, column: 3 },
{ sourceActor: { actor: "actor1" }, line: 10, column: 10 },
{ sourceActor: { actor: "actor1" }, line: 23, column: 89 },
{ sourceActor: { actor: "actor2" }, line: 10, column: 1 },
{ sourceActor: { actor: "actor2" }, line: 20, column: 5 },
{ sourceActor: { actor: "actor2" }, line: 30, column: 34 },
{ sourceActor: { actor: "actor2" }, line: 40, column: 56 }
{ originalSourceActor: { actor: "actor1" }, originalLine: 10 },
{ originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 3 },
{ originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 10 },
{ originalSourceActor: { actor: "actor1" }, originalLine: 23, originalColumn: 89 },
{ originalSourceActor: { actor: "actor2" }, originalLine: 10, originalColumn: 1 },
{ originalSourceActor: { actor: "actor2" }, originalLine: 20, originalColumn: 5 },
{ originalSourceActor: { actor: "actor2" }, originalLine: 30, originalColumn: 34 },
{ originalSourceActor: { actor: "actor2" }, originalLine: 40, originalColumn: 56 }
];
let bpStore = new BreakpointActorMap();
@@ -130,8 +130,8 @@ function test_find_actors() {
// Breakpoints by URL
bpSet = new Set(bps.filter(bp => { return bp.sourceActor.actorID === "actor1" }));
for (let bp of bpStore.findActors({ sourceActor: { actorID: "actor1" } })) {
bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1" }));
for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" } })) {
bpSet.delete(bp);
}
do_check_eq(bpSet.size, 0,
@@ -139,15 +139,15 @@ function test_find_actors() {
// Breakpoints by URL and line
bpSet = new Set(bps.filter(bp => { return bp.sourceActor.actorID === "actor1" && bp.line === 10; }));
bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1" && bp.originalLine === 10; }));
let first = true;
for (let bp of bpStore.findActors({ sourceActor: { actorID: "actor1" }, line: 10 })) {
for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" }, originalLine: 10 })) {
if (first) {
do_check_eq(bp.column, undefined,
do_check_eq(bp.originalColumn, undefined,
"Should always get the whole line breakpoint first");
first = false;
} else {
do_check_neq(bp.column, undefined,
do_check_neq(bp.originalColumn, undefined,
"Should not get the whole line breakpoint any time other than first.");
}
bpSet.delete(bp);
@@ -161,9 +161,9 @@ function test_duplicate_actors() {
// Breakpoint with column
let location = {
sourceActor: { actorID: "foo-actor" },
line: 10,
column: 9
originalSourceActor: { actorID: "foo-actor" },
originalLine: 10,
originalColumn: 9
};
bpStore.setActor(location, {});
bpStore.setActor(location, {});
@@ -172,8 +172,8 @@ function test_duplicate_actors() {
// Breakpoint without column (whole line breakpoint)
location = {
sourceActor: { actorID: "foo-actor" },
line: 15
originalSourceActor: { actorID: "foo-actor" },
originalLine: 15
};
bpStore.setActor(location, {});
bpStore.setActor(location, {});
@@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check conditional breakpoint when condition throws and make sure it is ignored
* Check conditional breakpoint when condition throws and make sure it pauses
*/
var gDebuggee;
@@ -33,8 +33,8 @@ function test_simple_breakpoint()
}, function (aResponse, bpClient) {
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
// Check the return value.
do_check_eq(aPacket.why.type, "debuggerStatement");
do_check_eq(aPacket.frame.where.line, 4);
do_check_eq(aPacket.why.type, "breakpointConditionThrown");
do_check_eq(aPacket.frame.where.line, 3);
// Remove the breakpoint.
bpClient.remove(function (aResponse) {
@@ -53,8 +53,7 @@ function test_simple_breakpoint()
Components.utils.evalInSandbox("debugger;\n" + // 1
"var a = 1;\n" + // 2
"var b = 2;\n" + // 3
"debugger;", // 4
"var b = 2;\n", // 3
gDebuggee,
"1.8",
"test.js",
@@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that the PromisesActor exists in the TabActors and ChromeActors.
*/
add_task(function*() {
let client = yield startTestDebuggerServer("promises-actor-test");
let response = yield listTabs(client);
let targetTab = findTab(response.tabs, "promises-actor-test");
ok(targetTab, "Found our target tab.")
// Attach to the TabActor and check the response
client.request({ to: targetTab.actor, type: "attach" }, response => {
ok(!("error" in response), "Expect no error in response.");
ok(response.from, targetTab.actor,
"Expect the target TabActor in response form field.");
ok(response.type, "tabAttached",
"Expect tabAttached in the response type.");
is(typeof response.promisesActor === "string",
"Should have a tab context PromisesActor.");
});
let chromeActors = yield getChromeActors(client);
ok(typeof chromeActors.promisesActor === "string",
"Should have a chrome context PromisesActor.");
});
@@ -5,6 +5,7 @@ const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/se
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/script");
const { DebuggerServer } = require("devtools/server/main");
const { TabSources } = require("devtools/server/actors/utils/TabSources");
const promise = require("promise");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
@@ -91,6 +92,13 @@ TestTabActor.prototype = {
return this._global.__name;
},
get sources() {
if (!this._sources) {
this._sources = new TabSources(this.threadActor);
}
return this._sources;
},
form: function() {
let response = { actor: this.actorID, title: this._global.__name };
@@ -124,6 +132,7 @@ TestTabActor.prototype = {
},
onReload: function(aRequest) {
this.sources.reset({ sourceMaps: true });
this.threadActor.clearDebuggees();
this.threadActor.dbg.addDebuggees();
return {};
@@ -66,6 +66,7 @@ support-files =
[test_eval-03.js]
[test_eval-04.js]
[test_eval-05.js]
[test_promises_actor_exist.js]
[test_protocol_abort.js]
[test_protocol_async.js]
[test_protocol_children.js]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+114 -5
View File
@@ -126,6 +126,113 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma
sourceRoot: '',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
// This mapping is identical to above, but uses the indexed format instead.
exports.indexedTestMap = {
version: 3,
file: 'min.js',
sections: [
{
offset: {
line: 0,
column: 0
},
map: {
version: 3,
sources: [
"one.js"
],
sourcesContent: [
' ONE.foo = function (bar) {\n' +
' return baz(bar);\n' +
' };',
],
names: [
"bar",
"baz"
],
mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
file: "min.js",
sourceRoot: "/the/root"
}
},
{
offset: {
line: 1,
column: 0
},
map: {
version: 3,
sources: [
"two.js"
],
sourcesContent: [
' TWO.inc = function (n) {\n' +
' return n + 1;\n' +
' };'
],
names: [
"n"
],
mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
file: "min.js",
sourceRoot: "/the/root"
}
}
]
};
exports.indexedTestMapDifferentSourceRoots = {
version: 3,
file: 'min.js',
sections: [
{
offset: {
line: 0,
column: 0
},
map: {
version: 3,
sources: [
"one.js"
],
sourcesContent: [
' ONE.foo = function (bar) {\n' +
' return baz(bar);\n' +
' };',
],
names: [
"bar",
"baz"
],
mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
file: "min.js",
sourceRoot: "/the/root"
}
},
{
offset: {
line: 1,
column: 0
},
map: {
version: 3,
sources: [
"two.js"
],
sourcesContent: [
' TWO.inc = function (n) {\n' +
' return n + 1;\n' +
' };'
],
names: [
"n"
],
mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
file: "min.js",
sourceRoot: "/different/root"
}
}
]
};
exports.testMapWithSourcesContent = {
version: 3,
file: 'min.js',
@@ -168,12 +275,13 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma
function assertMapping(generatedLine, generatedColumn, originalSource,
originalLine, originalColumn, name, map, assert,
originalLine, originalColumn, name, bias, map, assert,
dontTestGenerated, dontTestOriginal) {
if (!dontTestOriginal) {
var origMapping = map.originalPositionFor({
line: generatedLine,
column: generatedColumn
column: generatedColumn,
bias: bias
});
assert.equal(origMapping.name, name,
'Incorrect name, expected ' + JSON.stringify(name)
@@ -206,7 +314,8 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma
var genMapping = map.generatedPositionFor({
source: originalSource,
line: originalLine,
column: originalColumn
column: originalColumn,
bias: bias
});
assert.equal(genMapping.line, generatedLine,
'Incorrect line, expected ' + JSON.stringify(generatedLine)
@@ -521,7 +630,7 @@ define('lib/source-map/util', ['require', 'exports', 'module' , ], function(requ
return cmp;
}
cmp = strcmp(mappingA.name, mappingB.name);
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp) {
return cmp;
}
@@ -531,7 +640,7 @@ define('lib/source-map/util', ['require', 'exports', 'module' , ], function(requ
return cmp;
}
return mappingA.generatedColumn - mappingB.generatedColumn;
return strcmp(mappingA.name, mappingB.name);
};
exports.compareByOriginalPositions = compareByOriginalPositions;
@@ -19,9 +19,10 @@ define("test/source-map/test-base64-vlq", ["require", "exports", "module"], func
exports['test normal encoding and decoding'] = function (assert, util) {
var result = {};
for (var i = -255; i < 256; i++) {
base64VLQ.decode(base64VLQ.encode(i), result);
var str = base64VLQ.encode(i);
base64VLQ.decode(str, 0, result);
assert.equal(result.value, i);
assert.equal(result.rest, "");
assert.equal(result.rest, str.length);
}
};
@@ -20,7 +20,7 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f
return a - b;
}
exports['test too high'] = function (assert, util) {
exports['test too high with default (glb) bias'] = function (assert, util) {
var needle = 30;
var haystack = [2,4,6,8,10,12,14,16,18,20];
@@ -31,7 +31,7 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f
assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 20);
};
exports['test too low'] = function (assert, util) {
exports['test too low with default (glb) bias'] = function (assert, util) {
var needle = 1;
var haystack = [2,4,6,8,10,12,14,16,18,20];
@@ -42,6 +42,30 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f
assert.equal(binarySearch.search(needle, haystack, numberCompare), -1);
};
exports['test too high with lub bias'] = function (assert, util) {
var needle = 30;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(binarySearch.search(needle, haystack, numberCompare,
binarySearch.LEAST_UPPER_BOUND), -1);
};
exports['test too low with lub bias'] = function (assert, util) {
var needle = 1;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare,
binarySearch.LEAST_UPPER_BOUND)], 2);
};
exports['test exact search'] = function (assert, util) {
var needle = 4;
var haystack = [2,4,6,8,10,12,14,16,18,20];
@@ -49,13 +73,37 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f
assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 4);
};
exports['test fuzzy search'] = function (assert, util) {
exports['test fuzzy search with default (glb) bias'] = function (assert, util) {
var needle = 19;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 18);
};
exports['test fuzzy search with lub bias'] = function (assert, util) {
var needle = 19;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare,
binarySearch.LEAST_UPPER_BOUND)], 20);
};
exports['test multiple matches'] = function (assert, util) {
var needle = 5;
var haystack = [1, 1, 2, 5, 5, 5, 13, 21];
assert.equal(binarySearch.search(needle, haystack, numberCompare,
binarySearch.LEAST_UPPER_BOUND), 3);
};
exports['test multiple matches at the beginning'] = function (assert, util) {
var needle = 1;
var haystack = [1, 1, 2, 5, 5, 5, 13, 21];
assert.equal(binarySearch.search(needle, haystack, numberCompare,
binarySearch.LEAST_UPPER_BOUND), 0);
};
});
function run_test() {
runSourceMapTests('test/source-map/test-binary-search', do_throw);
@@ -56,34 +56,55 @@ define("test/source-map/test-dog-fooding", ["require", "exports", "module"], fun
var smc = new SourceMapConsumer(smg.toString());
// Exact
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, smc, assert);
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert);
// Fuzzy
// Generated to original
util.assertMapping(2, 0, null, null, null, null, smc, assert, true);
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
util.assertMapping(3, 0, null, null, null, null, smc, assert, true);
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
util.assertMapping(4, 0, null, null, null, null, smc, assert, true);
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
util.assertMapping(5, 0, null, null, null, null, smc, assert, true);
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true);
util.assertMapping(6, 0, null, null, null, null, smc, assert, true);
util.assertMapping(6, 9, null, null, null, null, smc, assert, true);
util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, smc, assert, true);
// Generated to original with default (glb) bias.
util.assertMapping(2, 0, null, null, null, null, null, smc, assert, true);
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert, true);
util.assertMapping(3, 0, null, null, null, null, null, smc, assert, true);
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert, true);
util.assertMapping(4, 0, null, null, null, null, null, smc, assert, true);
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert, true);
util.assertMapping(5, 0, null, null, null, null, null, smc, assert, true);
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert, true);
util.assertMapping(6, 0, null, null, null, null, null, smc, assert, true);
util.assertMapping(6, 9, null, null, null, null, null, smc, assert, true);
util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert, true);
// Original to generated
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, smc, assert, null, true);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, smc, assert, null, true);
// Generated to original with lub bias.
util.assertMapping(2, 0, '/wu/tang/gza.coffee', 1, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(2, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(3, 0, '/wu/tang/gza.coffee', 2, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(3, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(4, 0, '/wu/tang/gza.coffee', 3, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(4, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(5, 0, '/wu/tang/gza.coffee', 4, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(5, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(6, 0, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(6, 9, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
util.assertMapping(6, 13, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
// Original to generated with default (glb) bias
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, null, smc, assert, null, true);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, null, smc, assert, null, true);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, null, smc, assert, null, true);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, null, smc, assert, null, true);
// Original to generated with lub bias.
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 1, 1, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 2, 3, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 3, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 4, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
util.assertMapping(null, null, '/wu/tang/gza.coffee', 6, 19, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
};
});
@@ -15,6 +15,8 @@ Components.utils.import('resource://test/Utils.jsm');
define("test/source-map/test-source-map-consumer", ["require", "exports", "module"], function (require, exports, module) {
var SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
var IndexedSourceMapConsumer = require('source-map/source-map-consumer').IndexedSourceMapConsumer;
var BasicSourceMapConsumer = require('source-map/source-map-consumer').BasicSourceMapConsumer;
var SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
exports['test that we can instantiate with a string or an object'] = function (assert, util) {
@@ -26,6 +28,18 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
});
};
exports['test that the object returned from new SourceMapConsumer inherits from SourceMapConsumer'] = function (assert, util) {
assert.ok(new SourceMapConsumer(util.testMap) instanceof SourceMapConsumer);
}
exports['test that a BasicSourceMapConsumer is returned for sourcemaps without sections'] = function(assert, util) {
assert.ok(new SourceMapConsumer(util.testMap) instanceof BasicSourceMapConsumer);
};
exports['test that an IndexedSourceMapConsumer is returned for sourcemaps with sections'] = function(assert, util) {
assert.ok(new SourceMapConsumer(util.indexedTestMap) instanceof IndexedSourceMapConsumer);
};
exports['test that the `sources` field has the original sources'] = function (assert, util) {
var map;
var sources;
@@ -36,6 +50,18 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
assert.equal(sources[1], '/the/root/two.js');
assert.equal(sources.length, 2);
map = new SourceMapConsumer(util.indexedTestMap);
sources = map.sources;
assert.equal(sources[0], '/the/root/one.js');
assert.equal(sources[1], '/the/root/two.js');
assert.equal(sources.length, 2);
map = new SourceMapConsumer(util.indexedTestMapDifferentSourceRoots);
sources = map.sources;
assert.equal(sources[0], '/the/root/one.js');
assert.equal(sources[1], '/different/root/two.js');
assert.equal(sources.length, 2);
map = new SourceMapConsumer(util.testMapNoSourceRoot);
sources = map.sources;
assert.equal(sources[0], 'one.js');
@@ -101,34 +127,107 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
exports['test mapping tokens back exactly'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', map, assert);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
};
exports['test mapping tokens back exactly in indexed source map'] = function (assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
};
exports['test mapping tokens back exactly'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
};
exports['test mapping tokens fuzzy'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
// Finding original positions
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true);
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true);
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true);
// Finding original positions with default (glb) bias.
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true);
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true);
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true);
// Finding generated positions
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true);
// Finding original positions with lub bias.
util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
// Finding generated positions with default (glb) bias.
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true);
// Finding generated positions with lub bias.
util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
};
exports['test mapping tokens fuzzy in indexed source map'] = function (assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
// Finding original positions with default (glb) bias.
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true);
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true);
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true);
// Finding original positions with lub bias.
util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
// Finding generated positions with default (glb) bias.
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true);
// Finding generated positions with lub bias.
util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
};
exports['test mappings and end of lines'] = function (assert, util) {
@@ -145,14 +244,22 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
generated: { line: 2, column: 2 },
source: 'bar.js'
});
smg.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 1 },
source: 'baz.js'
});
var map = SourceMapConsumer.fromSourceMap(smg);
// When finding original positions, mappings end at the end of the line.
util.assertMapping(2, 1, null, null, null, null, map, assert, true)
util.assertMapping(2, 1, null, null, null, null, null, map, assert, true)
// When finding generated positions, mappings do not end at the end of the line.
util.assertMapping(1, 1, 'bar.js', 2, 1, null, map, assert, null, true);
util.assertMapping(1, 1, 'bar.js', 2, 1, null, null, map, assert, null, true);
// When finding generated positions with, mappings end at the end of the source.
util.assertMapping(null, null, 'bar.js', 3, 1, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
};
exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) {
@@ -193,6 +300,29 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
});
};
exports['test eachMapping for indexed source maps'] = function(assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
map.eachMapping(function (mapping) {
assert.ok(mapping.generatedLine >= previousLine);
if (mapping.source) {
assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
}
if (mapping.generatedLine === previousLine) {
assert.ok(mapping.generatedColumn >= previousColumn);
previousColumn = mapping.generatedColumn;
}
else {
previousLine = mapping.generatedLine;
previousColumn = -Infinity;
}
});
};
exports['test iterating over mappings in a different order'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var previousLine = -Infinity;
@@ -221,6 +351,34 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
}, null, SourceMapConsumer.ORIGINAL_ORDER);
};
exports['test iterating over mappings in a different order in indexed source maps'] = function (assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
var previousSource = "";
map.eachMapping(function (mapping) {
assert.ok(mapping.source >= previousSource);
if (mapping.source === previousSource) {
assert.ok(mapping.originalLine >= previousLine);
if (mapping.originalLine === previousLine) {
assert.ok(mapping.originalColumn >= previousColumn);
previousColumn = mapping.originalColumn;
}
else {
previousLine = mapping.originalLine;
previousColumn = -Infinity;
}
}
else {
previousSource = mapping.source;
previousLine = -Infinity;
previousColumn = -Infinity;
}
}, null, SourceMapConsumer.ORIGINAL_ORDER);
};
exports['test that we can set the context for `this` in eachMapping'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var context = {};
@@ -229,6 +387,14 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
}, context);
};
exports['test that we can set the context for `this` in eachMapping in indexed source maps'] = function (assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
var context = {};
map.eachMapping(function () {
assert.equal(this, context);
}, context);
};
exports['test that the `sourcesContent` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sourcesContent = map.sourcesContent;
@@ -276,6 +442,26 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
}, Error);
};
exports['test that we can get the original source content for the sources on an indexed source map'] = function (assert, util) {
var map = new SourceMapConsumer(util.indexedTestMap);
var sources = map.sources;
assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.throws(function () {
map.sourceContentFor("");
}, Error);
assert.throws(function () {
map.sourceContentFor("/the/root/three.js");
}, Error);
assert.throws(function () {
map.sourceContentFor("three.js");
}, Error);
};
exports['test sourceRoot + generatedPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
@@ -314,7 +500,7 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
assert.equal(pos.column, 2);
};
exports['test allGeneratedPositionsFor'] = function (assert, util) {
exports['test allGeneratedPositionsFor for line'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
});
@@ -357,7 +543,7 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
assert.equal(mappings[1].column, 3);
};
exports['test allGeneratedPositionsFor for line with no mappings'] = function (assert, util) {
exports['test allGeneratedPositionsFor for line fuzzy'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
});
@@ -383,10 +569,12 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
source: 'bar.coffee'
});
assert.equal(mappings.length, 0);
assert.equal(mappings.length, 1);
assert.equal(mappings[0].line, 4);
assert.equal(mappings[0].column, 2);
};
exports['test allGeneratedPositionsFor source map with no mappings'] = function (assert, util) {
exports['test allGeneratedPositionsFor for empty source map'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
});
@@ -400,6 +588,64 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
assert.equal(mappings.length, 0);
};
exports['test allGeneratedPositionsFor for column'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 2 },
source: 'foo.coffee'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 3 },
source: 'foo.coffee'
});
map = new SourceMapConsumer(map.toString());
var mappings = map.allGeneratedPositionsFor({
line: 1,
column: 1,
source: 'foo.coffee'
});
assert.equal(mappings.length, 2);
assert.equal(mappings[0].line, 1);
assert.equal(mappings[0].column, 2);
assert.equal(mappings[1].line, 1);
assert.equal(mappings[1].column, 3);
};
exports['test allGeneratedPositionsFor for column fuzzy'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 2 },
source: 'foo.coffee'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 3 },
source: 'foo.coffee'
});
map = new SourceMapConsumer(map.toString());
var mappings = map.allGeneratedPositionsFor({
line: 1,
column: 0,
source: 'foo.coffee'
});
assert.equal(mappings.length, 2);
assert.equal(mappings[0].line, 1);
assert.equal(mappings[0].column, 2);
assert.equal(mappings[1].line, 1);
assert.equal(mappings[1].column, 3);
};
exports['test computeColumnSpans'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated.js'
@@ -544,6 +790,20 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
'Source should be relative the host of the source root.');
};
exports['test indexed source map errors when sections are out of order by line'] = function(assert, util) {
// Make a deep copy of the indexedTestMap
var misorderedIndexedTestMap = JSON.parse(JSON.stringify(util.indexedTestMap));
misorderedIndexedTestMap.sections[0].offset = {
line: 2,
column: 0
};
assert.throws(function() {
new SourceMapConsumer(misorderedIndexedTestMap);
}, Error);
};
exports['test github issue #64'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
@@ -103,6 +103,27 @@ define("test/source-map/test-source-map-generator", ["require", "exports", "modu
});
};
exports['test adding mappings with skipValidation'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.',
skipValidation: true
});
// Not enough info, caught by `util.getArgs`
assert.throws(function () {
map.addMapping({});
});
// Original file position, but no source. Not checked.
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 }
});
});
};
exports['test that the correct mappings are being generated'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'min.js',
@@ -660,6 +681,48 @@ define("test/source-map/test-source-map-generator", ["require", "exports", "modu
});
};
exports['test applySourceMap with unexact match'] = function (assert, util) {
var map1 = new SourceMapGenerator({
file: 'bundled-source'
});
map1.addMapping({
generated: { line: 1, column: 4 },
original: { line: 1, column: 4 },
source: 'transformed-source'
});
map1.addMapping({
generated: { line: 2, column: 4 },
original: { line: 2, column: 4 },
source: 'transformed-source'
});
var map2 = new SourceMapGenerator({
file: 'transformed-source'
});
map2.addMapping({
generated: { line: 2, column: 0 },
original: { line: 1, column: 0 },
source: 'original-source'
});
var expectedMap = new SourceMapGenerator({
file: 'bundled-source'
});
expectedMap.addMapping({
generated: { line: 1, column: 4 },
original: { line: 1, column: 4 },
source: 'transformed-source'
});
expectedMap.addMapping({
generated: { line: 2, column: 4 },
original: { line: 1, column: 0 },
source: 'original-source'
});
map1.applySourceMap(new SourceMapConsumer(map2.toJSON()));
util.assertEqualMaps(assert, map1.toJSON(), expectedMap.toJSON());
};
});
function run_test() {
runSourceMapTests('test/source-map/test-source-map-generator', do_throw);
@@ -0,0 +1,377 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete popup keyboard usage test";
let HUD, popup, jsterm, inputNode, completeNode;
let test = asyncTest(function*() {
yield loadTab(TEST_URI);
let hud = yield openConsole();
yield consoleOpened(hud);
yield popupHideAfterTab();
yield testReturnKey();
yield dontShowArrayNumbers();
yield testReturnWithNoSelection();
yield popupHideAfterReturnWithNoSelection();
yield testCompletionInText();
yield popupHideAfterCompletionInText();
HUD = popup = jsterm = inputNode = completeNode = null;
});
let consoleOpened = Task.async(function*(aHud) {
let deferred = promise.defer();
HUD = aHud;
info("web console opened");
jsterm = HUD.jsterm;
yield jsterm.execute("window.foobarBug585991={" +
"'item0': 'value0'," +
"'item1': 'value1'," +
"'item2': 'value2'," +
"'item3': 'value3'" +
"}");
yield jsterm.execute("window.testBug873250a = 'hello world';"
+ "window.testBug873250b = 'hello world 2';");
popup = jsterm.autocompletePopup;
completeNode = jsterm.completeNode;
inputNode = jsterm.inputNode;
ok(!popup.isOpen, "popup is not open");
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
// 4 values, and the following properties:
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
// __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
// toLocaleString toString toSource unwatch valueOf watch constructor.
is(popup.itemCount, 19, "popup.itemCount is correct");
let sameItems = popup.getItems().reverse().map(function(e) {return e.label;});
ok(sameItems.every(function(prop, index) {
return [
"__defineGetter__",
"__defineSetter__",
"__lookupGetter__",
"__lookupSetter__",
"__proto__",
"constructor",
"hasOwnProperty",
"isPrototypeOf",
"item0",
"item1",
"item2",
"item3",
"propertyIsEnumerable",
"toLocaleString",
"toSource",
"toString",
"unwatch",
"valueOf",
"watch",
][index] === prop}), "getItems returns the items we expect");
is(popup.selectedIndex, 18,
"Index of the first item from bottom is selected.");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
is(completeNode.value, prefix + "valueOf",
"completeNode.value holds valueOf");
EventUtils.synthesizeKey("VK_UP", {});
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
let currentSelectionIndex = popup.selectedIndex;
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
ok(popup.selectedIndex > currentSelectionIndex,
"Index is greater after PGDN");
currentSelectionIndex = popup.selectedIndex;
EventUtils.synthesizeKey("VK_PAGE_UP", {});
ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
EventUtils.synthesizeKey("VK_END", {});
is(popup.selectedIndex, 18, "index is last after End");
EventUtils.synthesizeKey("VK_HOME", {});
is(popup.selectedIndex, 0, "index is first after Home");
info("press Tab and wait for popup to hide");
popup._panel.addEventListener("popuphidden", function popupHidden() {
popup._panel.removeEventListener("popuphidden", popupHidden, false);
deferred.resolve();
}, false);
EventUtils.synthesizeKey("VK_TAB", {});
}, false);
info("wait for completion: window.foobarBug585991.");
jsterm.setInputValue("window.foobarBug585991");
EventUtils.synthesizeKey(".", {});
return deferred.promise;
});
function popupHideAfterTab()
{
let deferred = promise.defer();
// At this point the completion suggestion should be accepted.
ok(!popup.isOpen, "popup is not open");
is(inputNode.value, "window.foobarBug585991.watch",
"completion was successful after VK_TAB");
ok(!completeNode.value, "completeNode is empty");
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 19, "popup.itemCount is correct");
is(popup.selectedIndex, 18, "First index from bottom is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
is(inputNode.value, "window.foobarBug585991.",
"completion was cancelled");
ok(!completeNode.value, "completeNode is empty");
deferred.resolve();
}, false);
info("press Escape to close the popup");
executeSoon(function() {
EventUtils.synthesizeKey("VK_ESCAPE", {});
});
}, false);
info("wait for completion: window.foobarBug585991.");
executeSoon(function() {
jsterm.setInputValue("window.foobarBug585991");
EventUtils.synthesizeKey(".", {});
});
return deferred.promise;
}
function testReturnKey()
{
let deferred = promise.defer();
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 19, "popup.itemCount is correct");
is(popup.selectedIndex, 18, "First index from bottom is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
is(completeNode.value, prefix + "valueOf",
"completeNode.value holds valueOf");
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
ok(!popup.isOpen, "popup is not open after VK_RETURN");
is(inputNode.value, "window.foobarBug585991.valueOf",
"completion was successful after VK_RETURN");
ok(!completeNode.value, "completeNode is empty");
deferred.resolve();
}, false);
info("press Return to accept suggestion. wait for popup to hide");
executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
}, false);
info("wait for completion suggestions: window.foobarBug585991.");
executeSoon(function() {
jsterm.setInputValue("window.foobarBug58599");
EventUtils.synthesizeKey("1", {});
EventUtils.synthesizeKey(".", {});
});
return deferred.promise;
}
function dontShowArrayNumbers()
{
let deferred = promise.defer();
info("dontShowArrayNumbers");
content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
let jsterm = HUD.jsterm;
let popup = jsterm.autocompletePopup;
let completeNode = jsterm.completeNode;
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
let sameItems = popup.getItems().map(function(e) {return e.label;});
ok(!sameItems.some(function(prop, index) { prop === "0"; }),
"Completing on an array doesn't show numbers.");
popup._panel.addEventListener("popuphidden", function popupHidden() {
popup._panel.removeEventListener("popuphidden", popupHidden, false);
deferred.resolve();
}, false);
info("wait for popup to hide");
executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
}, false);
info("wait for popup to show");
executeSoon(() => {
jsterm.setInputValue("window.foobarBug585991");
EventUtils.synthesizeKey(".", {});
});
return deferred.promise;
}
function testReturnWithNoSelection()
{
let deferred = promise.defer();
info("test pressing return with open popup, but no selection, see bug 873250");
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown);
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 2, "popup.itemCount is correct");
isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
info("press Return and wait for popup to hide");
popup._panel.addEventListener("popuphidden", function popupHidden() {
popup._panel.removeEventListener("popuphidden", popupHidden);
deferred.resolve();
});
executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
});
executeSoon(() => {
info("wait for popup to show");
jsterm.setInputValue("window.testBu");
EventUtils.synthesizeKey("g", {});
});
return deferred.promise;
}
function popupHideAfterReturnWithNoSelection()
{
ok(!popup.isOpen, "popup is not open after VK_RETURN");
is(inputNode.value, "", "inputNode is empty after VK_RETURN");
is(completeNode.value, "", "completeNode is empty");
is(jsterm.history[jsterm.history.length-1], "window.testBug",
"jsterm history is correct");
return promise.resolve();
}
function testCompletionInText()
{
info("test that completion works inside text, see bug 812618");
let deferred = promise.defer();
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown);
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 2, "popup.itemCount is correct");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
ok(!completeNode.value, "completeNode.value is empty");
let items = popup.getItems().reverse().map(e => e.label);
let sameItems = items.every((prop, index) =>
["testBug873250a", "testBug873250b"][index] === prop);
ok(sameItems, "getItems returns the items we expect");
info("press Tab and wait for popup to hide");
popup._panel.addEventListener("popuphidden", function popupHidden() {
popup._panel.removeEventListener("popuphidden", popupHidden);
deferred.resolve();
});
EventUtils.synthesizeKey("VK_TAB", {});
});
jsterm.setInputValue("dump(window.testBu)");
inputNode.selectionStart = inputNode.selectionEnd = 18;
EventUtils.synthesizeKey("g", {});
return deferred.promise;
}
function popupHideAfterCompletionInText()
{
// At this point the completion suggestion should be accepted.
ok(!popup.isOpen, "popup is not open");
is(inputNode.value, "dump(window.testBug873250b)",
"completion was successful after VK_TAB");
is(inputNode.selectionStart, 26, "cursor location is correct");
is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
ok(!completeNode.value, "completeNode is empty");
return promise.resolve();
}
@@ -45,6 +45,16 @@
margin: 2px;
}
.dbg-breakpoint-condition-thrown-message {
display: none;
color: var(--theme-highlight-red);
}
.dbg-breakpoint.dbg-breakpoint-condition-thrown .dbg-breakpoint-condition-thrown-message {
display: block;
-moz-padding-start: 0;
}
/* Sources toolbar */
#sources-toolbar > .devtools-toolbarbutton,