From 2a40ef2903888b479852b9d71d66e2425e1ed0e2 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 15 Apr 2021 10:14:56 +0800 Subject: [PATCH] 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) --- .../test/general/browser_mcb_redirect.js | 314 +++ .../test_mcb_double_redirect_image.html | 23 + .../test/general/test_mcb_redirect.html | 15 + .../test/general/test_mcb_redirect.sjs | 22 + .../test/general/test_mcb_redirect_image.html | 23 + caps/BasePrincipal.cpp | 38 +- caps/BasePrincipal.h | 4 +- caps/nsPrincipal.h | 1 + dom/base/Link.cpp | 21 +- dom/base/Link.h | 1 - dom/base/URL.cpp | 22 +- dom/base/URL.h | 2 - dom/base/URLSearchParams.cpp | 51 +- dom/base/URLSearchParams.h | 13 +- dom/base/nsContentUtils.cpp | 51 + dom/base/nsContentUtils.h | 19 + dom/base/nsXMLHttpRequest.cpp | 48 +- dom/base/test/test_urlSearchParams.html | 92 +- .../test/test_Object.prototype_props.html | 3 - dom/fetch/Fetch.cpp | 61 +- dom/fetch/FetchDriver.cpp | 38 +- dom/html/HTMLAreaElement.h | 1 - dom/tests/mochitest/fetch/test_fetch_cors.js | 20 + dom/webidl/ChromeUtils.webidl | 14 + dom/webidl/SystemDictionaries.webidl | 22 - dom/webidl/URLUtils.webidl | 2 +- dom/webidl/moz.build | 1 - dom/workers/ServiceWorkerManager.cpp | 6 +- dom/workers/URL.cpp | 20 +- dom/workers/URL.h | 2 - dom/workers/test/urlSearchParams_worker.js | 54 +- js/src/jsiter.cpp | 9 - .../getOwnPropertyNames-__proto__.js | 10 +- js/xpconnect/src/ExportHelpers.cpp | 24 +- js/xpconnect/src/Sandbox.cpp | 6 +- js/xpconnect/tests/chrome/test_xrayToJS.xul | 6 +- js/xpconnect/tests/unit/test_bug1131707.js | 22 + js/xpconnect/tests/unit/test_bug1150771.js | 13 + js/xpconnect/tests/unit/test_bug1151385.js | 9 + js/xpconnect/tests/unit/test_bug1170311.js | 5 + js/xpconnect/tests/unit/xpcshell.ini | 4 + js/xpconnect/wrappers/XrayWrapper.cpp | 24 +- .../web-platform/tests/url/interfaces.html | 2 +- testing/xpcshell/head.js | 5 +- .../components/places/tests/cpp/mock_Link.h | 21 +- .../devtools/debugger/debugger-controller.js | 24 +- toolkit/devtools/debugger/debugger-panes.js | 46 +- toolkit/devtools/debugger/test/browser.ini | 33 +- .../test/browser_dbg_break-on-dom-event-03.js | 101 + ...bg_breakpoints-condition-thrown-message.js | 106 ++ .../browser_dbg_conditional-breakpoints-05.js | 141 ++ .../browser_dbg_server-conditional-bp-01.js | 1 + .../browser_dbg_server-conditional-bp-05.js | 171 ++ .../test/browser_dbg_source-maps-01.js | 8 +- .../test/browser_dbg_source-maps-03.js | 4 +- .../test/browser_dbg_source-maps-04.js | 4 +- .../debugger/test/doc_event-listeners-04.html | 23 + toolkit/devtools/server/actors/common.js | 140 ++ toolkit/devtools/server/actors/promises.js | 38 + toolkit/devtools/server/actors/script.js | 1692 ++++++----------- toolkit/devtools/server/actors/tracer.js | 25 +- .../server/actors/utils/ScriptStore.js | 12 + .../server/actors/utils/TabSources.js | 751 ++++++++ toolkit/devtools/server/actors/webbrowser.js | 36 + toolkit/devtools/server/dbg-server.jsm | 1 + toolkit/devtools/server/main.js | 18 +- toolkit/devtools/server/moz.build | 2 + .../devtools/server/tests/unit/head_dbg.js | 19 + .../tests/unit/test_breakpoint-actor-map.js | 68 +- .../unit/test_conditional_breakpoint-03.js | 9 +- .../tests/unit/test_promises_actor_exist.js | 29 + .../devtools/server/tests/unit/testactors.js | 9 + .../devtools/server/tests/unit/xpcshell.ini | 1 + toolkit/devtools/sourcemap/SourceMap.jsm | 1490 ++++++++++----- toolkit/devtools/sourcemap/source-map.js | 1490 ++++++++++----- .../devtools/sourcemap/tests/unit/Utils.jsm | 119 +- .../sourcemap/tests/unit/test_base64_vlq.js | 5 +- .../tests/unit/test_binary_search.js | 54 +- .../sourcemap/tests/unit/test_dog_fooding.js | 69 +- .../tests/unit/test_source_map_consumer.js | 314 ++- .../tests/unit/test_source_map_generator.js | 63 + ...webconsole_bug_585991_autocomplete_keys.js | 377 ++++ .../themes/shared/devtools/debugger.inc.css | 10 + 83 files changed, 5946 insertions(+), 2721 deletions(-) create mode 100644 browser/base/content/test/general/browser_mcb_redirect.js create mode 100644 browser/base/content/test/general/test_mcb_double_redirect_image.html create mode 100644 browser/base/content/test/general/test_mcb_redirect.html create mode 100644 browser/base/content/test/general/test_mcb_redirect.sjs create mode 100644 browser/base/content/test/general/test_mcb_redirect_image.html delete mode 100644 dom/webidl/SystemDictionaries.webidl create mode 100644 js/xpconnect/tests/unit/test_bug1131707.js create mode 100644 js/xpconnect/tests/unit/test_bug1150771.js create mode 100644 js/xpconnect/tests/unit/test_bug1151385.js create mode 100644 js/xpconnect/tests/unit/test_bug1170311.js create mode 100644 toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-03.js create mode 100644 toolkit/devtools/debugger/test/browser_dbg_breakpoints-condition-thrown-message.js create mode 100644 toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-05.js create mode 100644 toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-05.js create mode 100644 toolkit/devtools/debugger/test/doc_event-listeners-04.html create mode 100644 toolkit/devtools/server/actors/promises.js create mode 100644 toolkit/devtools/server/actors/utils/TabSources.js create mode 100644 toolkit/devtools/server/tests/unit/test_promises_actor_exist.js create mode 100644 toolkit/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js diff --git a/browser/base/content/test/general/browser_mcb_redirect.js b/browser/base/content/test/general/browser_mcb_redirect.js new file mode 100644 index 0000000000..549fd55332 --- /dev/null +++ b/browser/base/content/test/general/browser_mcb_redirect.js @@ -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); +} diff --git a/browser/base/content/test/general/test_mcb_double_redirect_image.html b/browser/base/content/test/general/test_mcb_double_redirect_image.html new file mode 100644 index 0000000000..1b54774ec7 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_double_redirect_image.html @@ -0,0 +1,23 @@ + + + + + + Bug 1082837 + + + +
+ + + diff --git a/browser/base/content/test/general/test_mcb_redirect.html b/browser/base/content/test/general/test_mcb_redirect.html new file mode 100644 index 0000000000..88af791a31 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect.html @@ -0,0 +1,15 @@ + + + + + + Bug 418354 + + +
script blocked
+ + + diff --git a/browser/base/content/test/general/test_mcb_redirect.sjs b/browser/base/content/test/general/test_mcb_redirect.sjs new file mode 100644 index 0000000000..9a1811dfa2 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect.sjs @@ -0,0 +1,22 @@ +function handleRequest(request, response) { + var page = "bug 418354 and bug 1082837"; + + 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); +} diff --git a/browser/base/content/test/general/test_mcb_redirect_image.html b/browser/base/content/test/general/test_mcb_redirect_image.html new file mode 100644 index 0000000000..c70cd89879 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect_image.html @@ -0,0 +1,23 @@ + + + + + + Bug 1082837 + + + +
+ + + diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index e8b954f82d..139f0cfa3b 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -28,7 +28,7 @@ OriginAttributes::CreateSuffix(nsACString& aStr) const { MOZ_RELEASE_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); - nsRefPtr usp = new URLSearchParams(); + nsRefPtr usp = new URLSearchParams(nullptr); nsAutoString value; if (mAppId != nsIScriptSecurityManager::NO_APP_ID) { @@ -108,8 +108,8 @@ OriginAttributes::PopulateFromSuffix(const nsACString& aStr) return false; } - nsRefPtr usp = new URLSearchParams(); - usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1), nullptr); + nsRefPtr 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 expandedPrincipal = - do_QueryInterface(aPrincipal); - if (expandedPrincipal) { - return false; - } - - return true; -} - } // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index 15d72f4f6e..8312bbea96 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -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(aPrin); } static already_AddRefed CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs); diff --git a/caps/nsPrincipal.h b/caps/nsPrincipal.h index 45e9c9bed5..5380312284 100644 --- a/caps/nsPrincipal.h +++ b/caps/nsPrincipal.h @@ -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(); diff --git a/dom/base/Link.cpp b/dom/base/Link.cpp index 856f3e3296..fe91eb6cb1 100644 --- a/dom/base/Link.cpp +++ b/dom/base/Link.cpp @@ -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; } } diff --git a/dom/base/Link.h b/dom/base/Link.h index 5bb0d8c364..95f9367831 100644 --- a/dom/base/Link.h +++ b/dom/base/Link.h @@ -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); diff --git a/dom/base/URL.cpp b/dom/base/URL.cpp index 7718736e4c..2007be4990 100644 --- a/dom/base/URL.cpp +++ b/dom/base/URL.cpp @@ -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(); } } diff --git a/dom/base/URL.h b/dom/base/URL.h index 1b94127ce7..a6a1194bf1 100644 --- a/dom/base/URL.h +++ b/dom/base/URL.h @@ -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); diff --git a/dom/base/URLSearchParams.cpp b/dom/base/URLSearchParams.cpp index 749757d6e9..a8cb14079f 100644 --- a/dom/base/URLSearchParams.cpp +++ b/dom/base/URLSearchParams.cpp @@ -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 sp = new URLSearchParams(); - sp->ParseInput(NS_ConvertUTF16toUTF8(aInit), nullptr); + nsRefPtr 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 sp = new URLSearchParams(); + nsRefPtr 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); } } diff --git a/dom/base/URLSearchParams.h b/dom/base/URLSearchParams.h index b2df860679..ce54707e7d 100644 --- a/dom/base/URLSearchParams.h +++ b/dom/base/URLSearchParams.h @@ -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 mSearchParams; - nsTArray> mObservers; + nsRefPtr mObserver; nsCOMPtr mDecoder; }; diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 50600c8f67..9dc4625673 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -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 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 docCurURI = aDoc->GetDocumentURI(); + nsCOMPtr docOrigURI = aDoc->GetOriginalURI(); + + nsCOMPtr 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); +} \ No newline at end of file diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 819f338f65..8e6f302677 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2489,6 +2489,25 @@ public: static already_AddRefed 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(); diff --git a/dom/base/nsXMLHttpRequest.cpp b/dom/base/nsXMLHttpRequest.cpp index a4585e0fd3..27ec5f5c1e 100644 --- a/dom/base/nsXMLHttpRequest.cpp +++ b/dom/base/nsXMLHttpRequest.cpp @@ -2716,50 +2716,10 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable& 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 principalURI; - mPrincipal->GetURI(getter_AddRefs(principalURI)); - - nsIScriptContext* sc = GetContextForEventHandlers(&rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr doc = - nsContentUtils::GetDocumentFromScriptContext(sc); - - nsCOMPtr docCurURI; - nsCOMPtr docOrigURI; - net::ReferrerPolicy referrerPolicy = net::RP_Default; - - if (doc) { - docCurURI = doc->GetDocumentURI(); - docOrigURI = doc->GetOriginalURI(); - referrerPolicy = doc->GetReferrerPolicy(); - } - - nsCOMPtr 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 owner = GetOwner(); + nsCOMPtr doc = owner ? owner->GetExtantDoc() : nullptr; + nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc, + httpChannel); } // Some extensions override the http protocol handler and provide their own diff --git a/dom/base/test/test_urlSearchParams.html b/dom/base/test/test_urlSearchParams.html index 46fa37ea5b..b43c790b70 100644 --- a/dom/base/test/test_urlSearchParams.html +++ b/dom/base/test/test_urlSearchParams.html @@ -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, diff --git a/dom/bindings/test/test_Object.prototype_props.html b/dom/bindings/test/test_Object.prototype_props.html index e5501ce65e..03147eb03d 100644 --- a/dom/bindings/test/test_Object.prototype_props.html +++ b/dom/bindings/test/test_Object.prototype_props.html @@ -7,9 +7,6 @@ + + + diff --git a/toolkit/devtools/server/actors/common.js b/toolkit/devtools/server/actors/common.js index ad4321239f..00ffa6e338 100644 --- a/toolkit/devtools/server/actors/common.js +++ b/toolkit/devtools/server/actors/common.js @@ -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) { diff --git a/toolkit/devtools/server/actors/promises.js b/toolkit/devtools/server/actors/promises.js new file mode 100644 index 0000000000..c4857cc3b6 --- /dev/null +++ b/toolkit/devtools/server/actors/promises.js @@ -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); + } +}); diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 34f8fe94e3..984c9b2bb4 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -8,7 +8,7 @@ const Services = require("Services"); const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome"); -const { ActorPool, getOffsetColumn } = require("devtools/server/actors/common"); +const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common"); const { DebuggerServer } = require("devtools/server/main"); const DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); const { dbg_assert, dumpn, update, fetch } = DevToolsUtils; @@ -29,6 +29,7 @@ loader.lazyGetter(this, "Debugger", () => { loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true); loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true); loader.lazyRequireGetter(this, "CssLogic", "devtools/styleinspector/css-logic", true); +loader.lazyRequireGetter(this, "events", "sdk/event/core"); let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array", @@ -75,28 +76,23 @@ function BreakpointActorMap() { BreakpointActorMap.prototype = { /** - * Return the number of instances of BreakpointActor in this instance of - * BreakpointActorMap. + * Return the number of BreakpointActors in this BreakpointActorMap. * * @returns Number - * The number of instances of BreakpointACtor in this instance of - * BreakpointActorMap. + * The number of BreakpointActor in this BreakpointActorMap. */ get size() { return this._size; }, /** - * Generate all instances of BreakpointActor that match the given query in - * this instance of BreakpointActorMap. + * Generate all BreakpointActors that match the given location in + * this BreakpointActorMap. * - * @param Object query - * An optional object with the following properties: - * - source (optional) - * - line (optional, requires source) - * - column (optional, requires line) + * @param OriginalLocation location + * The location for which matching BreakpointActors should be generated. */ - findActors: function* (query = {}) { + findActors: function* (location = new OriginalLocation()) { function* findKeys(object, key) { if (key !== undefined) { if (key in object) { @@ -110,9 +106,21 @@ BreakpointActorMap.prototype = { } } - query.sourceActorID = query.sourceActor ? query.sourceActor.actorID : undefined; - query.beginColumn = query.column ? query.column : undefined; - query.endColumn = query.column ? query.column + 1 : undefined; + let query = { + sourceActorID: location.originalSourceActor ? location.originalSourceActor.actorID : undefined, + line: location.originalLine, + }; + + // If location contains a line, assume we are searching for a whole line + // breakpoint, and set begin/endColumn accordingly. Otherwise, we are + // searching for all breakpoints, so begin/endColumn should be left unset. + if (location.originalLine) { + query.beginColumn = location.originalColumn ? location.originalColumn : 0; + query.endColumn = location.originalColumn ? location.originalColumn + 1 : Infinity; + } else { + query.beginColumn = location.originalColumn ? query.originalColumn : undefined; + query.endColumn = location.originalColumn ? query.originalColumn + 1 : undefined; + } for (let sourceActorID of findKeys(this._actors, query.sourceActorID)) for (let line of findKeys(this._actors[sourceActorID], query.line)) @@ -123,20 +131,17 @@ BreakpointActorMap.prototype = { }, /** - * Return the instance of BreakpointActor at the given location in this - * instance of BreakpointActorMap. + * Return the BreakpointActor at the given location in this + * BreakpointActorMap. * - * @param Object location - * An object with the following properties: - * - source - * - line - * - column (optional) + * @param OriginalLocation location + * The location for which the BreakpointActor should be returned. * * @returns BreakpointActor actor - * The instance of BreakpointActor at the given location. + * The BreakpointActor at the given location. */ - getActor: function (location) { - for (let actor of this.findActors(location)) { + getActor: function (originalLocation) { + for (let actor of this.findActors(originalLocation)) { return actor; } @@ -144,24 +149,22 @@ BreakpointActorMap.prototype = { }, /** - * Set the given instance of BreakpointActor to the given location in this - * instance of BreakpointActorMap. + * Set the given BreakpointActor to the given location in this + * BreakpointActorMap. * - * @param Object location - * An object with the following properties: - * - source - * - line - * - column (optional) + * @param OriginalLocation location + * The location to which the given BreakpointActor should be set. * * @param BreakpointActor actor - * The instance of BreakpointActor to be set to the given location. + * The BreakpointActor to be set to the given location. */ setActor: function (location, actor) { - let { sourceActor, line, column } = location; + let { originalSourceActor, originalLine, originalColumn } = location; - let sourceActorID = sourceActor.actorID; - let beginColumn = column ? column : 0; - let endColumn = column ? column + 1 : Infinity; + let sourceActorID = originalSourceActor.actorID; + let line = originalLine; + let beginColumn = originalColumn ? originalColumn : 0; + let endColumn = originalColumn ? originalColumn + 1 : Infinity; if (!this._actors[sourceActorID]) { this._actors[sourceActorID] = []; @@ -179,21 +182,19 @@ BreakpointActorMap.prototype = { }, /** - * Delete the instance of BreakpointActor from the given location in this - * instance of BreakpointActorMap. + * Delete the BreakpointActor from the given location in this + * BreakpointActorMap. * - * @param Object location - * An object with the following properties: - * - source - * - line - * - column (optional) + * @param OriginalLocation location + * The location from which the BreakpointActor should be deleted. */ deleteActor: function (location) { - let { sourceActor, line, column } = location; + let { originalSourceActor, originalLine, originalColumn } = location; - let sourceActorID = sourceActor.actorID; - let beginColumn = column ? column : 0; - let endColumn = column ? column + 1 : Infinity; + let sourceActorID = originalSourceActor.actorID; + let line = originalLine; + let beginColumn = originalColumn ? originalColumn : 0; + let endColumn = originalColumn ? originalColumn + 1 : Infinity; if (this._actors[sourceActorID]) { if (this._actors[sourceActorID][line]) { @@ -443,21 +444,19 @@ function ThreadActor(aParent, aGlobal) this._threadLifetimePool = null; this._tabClosed = false; this._scripts = null; - this._sources = null; + this._pauseOnDOMEvents = null; this._options = { useSourceMaps: false, autoBlackBox: false }; - this.breakpointActorMap = new BreakpointActorMap; - this.sourceActorStore = new SourceActorStore; - this.blackBoxedSources = new Set; - this.prettyPrintedSources = new Map; + this.breakpointActorMap = new BreakpointActorMap(); + this.sourceActorStore = new SourceActorStore(); // A map of actorID -> actor for breakpoints created and managed by the // server. - this._hiddenBreakpoints = new Map; + this._hiddenBreakpoints = new Map(); this.global = aGlobal; @@ -467,6 +466,8 @@ function ThreadActor(aParent, aGlobal) this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this); this.onDebuggerStatement = this.onDebuggerStatement.bind(this); this.onNewScript = this.onNewScript.bind(this); + this._onWindowReady = this._onWindowReady.bind(this); + events.on(this._parent, "window-ready", this._onWindowReady); // Set a wrappedJSObject property so |this| can be sent via the observer svc // for the xpcshell harness. this.wrappedJSObject = this; @@ -518,11 +519,7 @@ ThreadActor.prototype = { }, get sources() { - if (!this._sources) { - this._sources = new ThreadSources(this, this._options, - this._allowSource, this.onNewSource); - } - return this._sources; + return this._parent.sources; }, get youngestFrame() { @@ -620,6 +617,7 @@ ThreadActor.prototype = { // things like breakpoints across connections. this._sourceActorStore = null; + events.off(this._parent, "window-ready", this._onWindowReady); this.clearDebuggees(); this.conn.removeActorPool(this._threadLifetimePool); this._threadLifetimePool = null; @@ -662,6 +660,10 @@ ThreadActor.prototype = { this._state = "attached"; update(this._options, aRequest.options || {}); + this.sources.reconfigure(this._options); + this.sources.on('newSource', (name, source) => { + this.onNewSource(source); + }); // Initialize an event loop stack. This can't be done in the constructor, // because this.conn is not yet initialized by the actor pool at that time. @@ -716,8 +718,8 @@ ThreadActor.prototype = { } update(this._options, aRequest.options || {}); - // Clear existing sources, so they can be recreated on next access. - this._sources = null; + // Update the global source store + this.sources.reconfigure(this._options); return {}; }, @@ -742,9 +744,10 @@ ThreadActor.prototype = { } packet.why = aReason; - let loc = this.sources.getFrameLocation(aFrame); - this.sources.getOriginalLocation(loc).then(aOrigPosition => { - if (!aOrigPosition.sourceActor) { + let generatedLocation = this.sources.getFrameLocation(aFrame); + this.sources.getOriginalLocation(generatedLocation) + .then((originalLocation) => { + if (!originalLocation.originalSourceActor) { // The only time the source actor will be null is if there // was a sourcemap and it tried to look up the original // location but there was no original URL. This is a strange @@ -759,9 +762,9 @@ ThreadActor.prototype = { } packet.frame.where = { - source: aOrigPosition.sourceActor.form(), - line: aOrigPosition.line, - column: aOrigPosition.column + source: originalLocation.originalSourceActor.form(), + line: originalLocation.originalLine, + column: originalLocation.originalColumn }; resolve(onPacket(packet)) .then(null, error => { @@ -806,9 +809,9 @@ ThreadActor.prototype = { _makeOnEnterFrame: function ({ pauseAndRespond }) { return aFrame => { const generatedLocation = this.sources.getFrameLocation(aFrame); - let { sourceActor } = this.synchronize(this.sources.getOriginalLocation( + let { originalSourceActor } = this.synchronize(this.sources.getOriginalLocation( generatedLocation)); - let url = sourceActor.url; + let url = originalSourceActor.url; return this.sources.isBlackBoxed(url) ? undefined @@ -821,9 +824,9 @@ ThreadActor.prototype = { // onPop is called with 'this' set to the current frame. const generatedLocation = thread.sources.getFrameLocation(this); - const { sourceActor } = thread.synchronize(thread.sources.getOriginalLocation( + const { originalSourceActor } = thread.synchronize(thread.sources.getOriginalLocation( generatedLocation)); - const url = sourceActor.url; + const url = originalSourceActor.url; if (thread.sources.isBlackBoxed(url)) { return undefined; @@ -882,15 +885,15 @@ ThreadActor.prototype = { // 2.2. The source we are in is black boxed. // Cases 2.1 and 2.2 - if (newLocation.url == null - || thread.sources.isBlackBoxed(newLocation.url)) { + if (newLocation.originalUrl == null + || thread.sources.isBlackBoxed(newLocation.originalUrl)) { return undefined; } // Cases 1.1, 1.2 and 1.3 if (this !== startFrame - || startLocation.url !== newLocation.url - || startLocation.line !== newLocation.line) { + || startLocation.originalUrl !== newLocation.originalUrl + || startLocation.originalLine !== newLocation.originalLine) { return pauseAndRespond(this); } @@ -1008,6 +1011,16 @@ ThreadActor.prototype = { } }, + /** + * If we are tasked with breaking on the load event, we have to add the + * listener early enough. + */ + _onWindowReady: function () { + this._maybeListenToEvents({ + pauseOnDOMEvents: this._pauseOnDOMEvents + }); + }, + /** * Handle a protocol request to resume execution of the debuggee. */ @@ -1208,7 +1221,7 @@ ThreadActor.prototype = { this.threadLifetimePool.addActor(actor); let scripts = this.scripts.getScriptsBySourceAndLine(script.source, line); let entryPoints = findEntryPointsForLine(scripts, line); - setBreakpointOnEntryPoints(this, actor, entryPoints); + setBreakpointForActorAtEntryPoints(actor, entryPoints); this._hiddenBreakpoints.set(actor.actorID, actor); break; } @@ -1288,16 +1301,16 @@ ThreadActor.prototype = { form.depth = i; frames.push(form); - let promise = this.sources.getOriginalLocation({ - sourceActor: this.sources.createNonSourceMappedActor(frame.script.source), - line: form.where.line, - column: form.where.column - }).then((aOrigLocation) => { - let sourceForm = aOrigLocation.sourceActor.form(); + let promise = this.sources.getOriginalLocation(new GeneratedLocation( + this.sources.createNonSourceMappedActor(frame.script.source), + form.where.line, + form.where.column + )).then((originalLocation) => { + let sourceForm = originalLocation.originalSourceActor.form(); form.where = { source: sourceForm, - line: aOrigLocation.line, - column: aOrigLocation.column + line: originalLocation.originalLine, + column: originalLocation.originalColumn }; form.source = sourceForm; }); @@ -1903,9 +1916,9 @@ ThreadActor.prototype = { // Don't pause if we are currently stepping (in or over) or the frame is // black-boxed. const generatedLocation = this.sources.getFrameLocation(aFrame); - const { sourceActor } = this.synchronize(this.sources.getOriginalLocation( + const { originalSourceActor } = this.synchronize(this.sources.getOriginalLocation( generatedLocation)); - const url = sourceActor ? sourceActor.url : null; + const url = originalSourceActor ? originalSourceActor.url : null; return this.sources.isBlackBoxed(url) || aFrame.onStep ? undefined @@ -1997,18 +2010,6 @@ ThreadActor.prototype = { }); }, - /** - * Check if scripts from the provided source URL are allowed to be stored in - * the cache. - * - * @param aSourceUrl String - * The url of the script's source that will be stored. - * @returns true, if the script can be added, false otherwise. - */ - _allowSource: function (aSource) { - return !isHiddenSource(aSource); - }, - /** * Restore any pre-existing breakpoints to the scripts that we have access to. */ @@ -2030,21 +2031,39 @@ ThreadActor.prototype = { * @returns true, if the script was added; false otherwise. */ _addScript: function (aScript) { - if (!this._allowSource(aScript.source)) { + if (!this.sources.allowSource(aScript.source)) { return false; } // Set any stored breakpoints. + let promises = []; + let sourceActor = this.sources.createNonSourceMappedActor(aScript.source); let endLine = aScript.startLine + aScript.lineCount - 1; - let source = this.sources.createNonSourceMappedActor(aScript.source); - for (let bpActor of this.breakpointActorMap.findActors({ sourceActor: source })) { - // Limit the search to the line numbers contained in the new script. - if (bpActor.generatedLocation.line >= aScript.startLine - && bpActor.generatedLocation.line <= endLine) { - source.setBreakpointForActor(bpActor); + for (let _actor of this.breakpointActorMap.findActors()) { + // XXX bug 1142115: We do async work in here, so we need to + // create a fresh binding because for/of does not yet do that in + // SpiderMonkey + let actor = _actor; + + if (actor.isPending) { + promises.push(sourceActor._setBreakpointForActor(actor)); + } else { + promises.push(this.sources.getGeneratedLocation(actor.originalLocation) + .then((generatedLocation) => { + // Limit the search to the line numbers contained in the new script. + if (generatedLocation.generatedSourceActor.actorID === sourceActor.actorID && + generatedLocation.generatedLine >= aScript.startLine && + generatedLocation.generatedLine <= endLine) { + sourceActor._setBreakpointForActorAtLocation(actor, generatedLocation); + } + })); } } + if (promises.length > 0) { + this.synchronize(Promise.all(promises)); + } + // Go ahead and establish the source actors for this script, which // fetches sourcemaps if available and sends onNewSource // notifications @@ -2275,6 +2294,11 @@ SourceActor.prototype = { _addonID: null, _addonPath: null, + get isSourceMapped() { + return this._originalURL || this._generatedSource || + this.threadActor.sources.isPrettyPrinted(this.url); + }, + get threadActor() { return this._threadActor; }, get dbg() { return this.threadActor.dbg; }, get scripts() { return this.threadActor.scripts; }, @@ -2574,7 +2598,7 @@ SourceActor.prototype = { */ _invertSourceMap: function ({ code, mappings }) { const generator = new SourceMapGenerator({ file: this.url }); - return DevToolsUtils.yieldingEach(mappings, m => { + return DevToolsUtils.yieldingEach(mappings._array, m => { let mapping = { generated: { line: m.generatedLine, @@ -2700,43 +2724,394 @@ SourceActor.prototype = { }, /** - * Handle a protocol request to set a breakpoint. + * Handle a request to set a breakpoint. + * + * @param JSON request + * A JSON object representing the request. + * + * @returns Promise + * A promise that resolves to a JSON object representing the + * response. */ - onSetBreakpoint: function(aRequest) { + onSetBreakpoint: function (request) { if (this.threadActor.state !== "paused") { - return { error: "wrongState", - message: "Breakpoints can only be set while the debuggee is paused."}; + return { + error: "wrongState", + message: "Cannot set breakpoint while debuggee is running." + }; } - return this.setBreakpoint(aRequest.location.line, aRequest.location.column, - aRequest.condition); + let { location: { line, column }, condition } = request; + let location = new OriginalLocation(this, line, column); + return this._setBreakpoint(location, condition).then((actor) => { + if (actor.isPending) { + return { + error: "noCodeAtLocation", + actor: actor.actorID + }; + } else { + let response = { + actor: actor.actorID + }; + + let actualLocation = actor.originalLocation; + if (!actualLocation.equals(location)) { + response.actualLocation = actualLocation.toJSON(); + } + + return response; + } + }); }, - /** Get or create the BreakpointActor for the breakpoint at the given location. + /** + * Get or create a BreakpointActor for the given location in the original + * source, and ensure it is set as a breakpoint handler on all scripts that + * match the given location. * - * NB: This will override a pre-existing BreakpointActor's condition with - * the given the location's condition. + * @param OriginalLocation originalLocation + * An OriginalLocation representing the location of the breakpoint in + * the original source. + * @param String condition + * A string that is evaluated whenever the breakpoint is hit. If the + * string evaluates to false, the breakpoint is ignored. * - * @param Object originalLocation - * The original location of the breakpoint. - * @param Object generatedLocation - * The generated location of the breakpoint. * @returns BreakpointActor + * A BreakpointActor representing the breakpoint. */ - _getOrCreateBreakpointActor: function (originalLocation, generatedLocation, - condition) - { - let actor = this.breakpointActorMap.getActor(generatedLocation); + _setBreakpoint: function (originalLocation, condition) { + let actor = this.breakpointActorMap.getActor(originalLocation); if (!actor) { - actor = new BreakpointActor(this.threadActor, originalLocation, - generatedLocation, condition); + actor = new BreakpointActor(this.threadActor, originalLocation); this.threadActor.threadLifetimePool.addActor(actor); - this.breakpointActorMap.setActor(generatedLocation, actor); - return actor; + this.breakpointActorMap.setActor(originalLocation, actor); } actor.condition = condition; - return actor; + + return this._setBreakpointForActor(actor); + }, + + /* + * Ensure the given BreakpointActor is set as a breakpoint handler on all + * scripts that match its location in the original source. + * + * If there are no scripts that match the location of the BreakpointActor, + * we slide its location to the next closest line (for line breakpoints) or + * column (for column breakpoint) that does. + * + * If breakpoint sliding fails, then either there are no scripts that contain + * any code for the given location, or they were all garbage collected before + * the debugger started running. We cannot distinguish between these two + * cases, so we insert the BreakpointActor in the BreakpointActorMap as + * a pending breakpoint. Whenever a new script is introduced, this method is + * called again for each pending breakpoint. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * + * @returns A Promise that resolves to the given BreakpointActor. + */ + _setBreakpointForActor: function (actor) { + let { originalLocation } = actor; + + if (this.isSourceMapped) { + // TODO: Refactor breakpoint sliding for source mapped sources. + return this.threadActor.sources.getGeneratedLocation(originalLocation) + .then((generatedLocation) => { + return generatedLocation.generatedSourceActor + ._setBreakpointForActorAtLocationWithSliding( + actor, + generatedLocation + ); + }); + } else { + // If this is a non-source mapped source, the original location and + // generated location are the same, so we can safely convert between them. + let generatedLocation = GeneratedLocation.fromOriginalLocation(originalLocation); + let { generatedColumn } = generatedLocation; + + // Try to set the breakpoint on the generated location directly. If this + // succeeds, we can avoid the more expensive breakpoint sliding algorithm + // below. + if (this._setBreakpointForActorAtLocation(actor, generatedLocation)) { + return Promise.resolve(actor); + } + + // There were no scripts that matched the given location, so we need to + // perform breakpoint sliding. + if (generatedColumn === undefined) { + // To perform breakpoint sliding for line breakpoints, we need to build + // a map from line numbers to a list of entry points for each line, + // implemented as a sparse array. An entry point is a (script, offsets) + // pair, and represents all offsets in that script that are entry points + // for the corresponding line. + let lineToEntryPointsMap = []; + + // Iterate over all scripts that correspond to this source actor. + let scripts = this.scripts.getScriptsBySourceActor(this); + for (let script of scripts) { + // Get all offsets for each line in the current script. This returns + // a map from line numbers fo a list of offsets for each line, + // implemented as a sparse array. + let lineToOffsetsMap = script.getAllOffsets(); + + // Iterate over each line, and add their list of offsets to the map + // from line numbers to entry points by forming a (script, offsets) + // pair, where script is the current script, and offsets is the list + // of offsets for the current line. + for (let line = 0; line < lineToOffsetsMap.length; ++line) { + let offsets = lineToOffsetsMap[line]; + if (offsets) { + let entryPoints = lineToEntryPointsMap[line]; + if (!entryPoints) { + // We dont have a list of entry points for the current line + // number yet, so create it and add it to the map. + entryPoints = []; + lineToEntryPointsMap[line] = entryPoints; + } + entryPoints.push({ script, offsets }); + } + } + } + + let { + originalSourceActor, + originalLine, + originalColumn + } = originalLocation; + + // Now that we have a map from line numbers to a list of entry points + // for each line, we can use it to perform breakpoint sliding. Start + // at the original line of the breakpoint actor, and keep incrementing + // it by one, until either we find a line that has at least one entry + // point, or we go past the last line in the map. + // + // Note that by computing the entire map up front, and implementing it + // as a sparse array, we can easily tell when we went past the last line + // in the map. + let actualLine = originalLine; + while (actualLine < lineToEntryPointsMap.length) { + let entryPoints = lineToEntryPointsMap[actualLine]; + if (entryPoints) { + setBreakpointForActorAtEntryPoints(actor, entryPoints); + break; + } + ++actualLine; + } + if (actualLine === lineToEntryPointsMap.length) { + // We went past the last line in the map, so breakpoint sliding + // failed. Keep the BreakpointActor in the BreakpointActorMap as a + // pending breakpoint, so we can try again whenever a new script is + // introduced. + return Promise.resolve(actor); + } + + // If the actual line on which the BreakpointActor was set differs from + // the original line that was requested, the BreakpointActor and the + // BreakpointActorMap need to be updated accordingly. + if (actualLine !== originalLine) { + let actualLocation = new OriginalLocation( + originalSourceActor, + actualLine + ); + let existingActor = this.breakpointActorMap.getActor(actualLocation); + if (existingActor) { + actor.onDelete(); + this.breakpointActorMap.deleteActor(originalLocation); + actor = existingActor; + } else { + this.breakpointActorMap.deleteActor(originalLocation); + actor.originalLocation = actualLocation; + this.breakpointActorMap.setActor(actualLocation, actor); + } + } + + return Promise.resolve(actor); + } else { + // TODO: Implement breakpoint sliding for column breakpoints + return Promise.resolve(actor); + } + } + }, + + /* + * Ensure the given BreakpointActor is set as breakpoint handler on all + * scripts that match the given location in the generated source. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * @param GeneratedLocation generatedLocation + * A GeneratedLocation representing the location in the generated + * source for which the given BreakpointActor is to be set as a + * breakpoint handler. + * + * @returns A Boolean that is true if the BreakpointActor was set as a + * breakpoint handler on at least one script, and false otherwise. + */ + _setBreakpointForActorAtLocation: function (actor, generatedLocation) { + let { generatedLine, generatedColumn } = generatedLocation; + + // Find all scripts that match the given source actor and line number. + let scripts = this.scripts.getScriptsBySourceActorAndLine( + this, + generatedLine + ).filter((script) => !actor.hasScript(script)); + + // Find all entry points that correspond to the given location. + let entryPoints = []; + if (generatedColumn === undefined) { + // This is a line breakpoint, so we are interested in all offsets + // that correspond to the given line number. + for (let script of scripts) { + let offsets = script.getLineOffsets(generatedLine); + if (offsets.length > 0) { + entryPoints.push({ script, offsets }); + } + } + } else { + // This is a column breakpoint, so we are interested in all column + // offsets that correspond to the given line *and* column number. + for (let script of scripts) { + let columnToOffsetMap = script.getAllColumnOffsets() + .filter(({ lineNumber }) => { + return lineNumber === generatedLine; + }); + for (let { columnNumber: column, offset } of columnToOffsetMap) { + // TODO: What we are actually interested in here is a range of + // columns, rather than a single one. + if (column == generatedColumn) { + entryPoints.push({ script, offsets: [offset] }); + } + } + } + } + + if (entryPoints.length === 0) { + return false; + } + setBreakpointForActorAtEntryPoints(actor, entryPoints); + return true; + }, + + /* + * Ensure the given BreakpointActor is set as breakpoint handler on all + * scripts that match the given location in the generated source. + * + * TODO: This method is bugged, because it performs breakpoint sliding on + * generated locations. Breakpoint sliding should be performed on original + * locations, because there is no guarantee that the next line in the + * generated source corresponds to the next line in an original source. + * + * The only place this method is still used is from setBreakpointForActor + * when called for a source mapped source. Once that code has been refactored, + * this method can be removed. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * @param GeneratedLocation generatedLocation + * A GeneratedLocation representing the location in the generated + * source for which the given BreakpointActor is to be set as a + * breakpoint handler. + * + * @returns A Boolean that is true if the BreakpointActor was set as a + * breakpoint handler on at least one script, and false otherwise. + */ + _setBreakpointForActorAtLocationWithSliding: function (actor, generatedLocation) { + let originalLocation = actor.originalLocation; + let { generatedLine, generatedColumn } = generatedLocation; + + // Find all scripts matching the given location. We will almost always have + // a `source` object to query, but multiple inline HTML scripts are all + // represented by a single SourceActor even though they have separate source + // objects, so we need to query based on the url of the page for them. + let scripts = this.scripts.getScriptsBySourceActorAndLine(this, generatedLine); + if (scripts.length === 0) { + // Since we did not find any scripts to set the breakpoint on now, return + // early. When a new script that matches this breakpoint location is + // introduced, the breakpoint actor will already be in the breakpoint + // store and the breakpoint will be set at that time. This is similar to + // GDB's "pending" breakpoints for shared libraries that aren't loaded + // yet. + return actor; + } + + // Ignore scripts for which the BreakpointActor is already a breakpoint + // handler. + scripts = scripts.filter((script) => !actor.hasScript(script)); + + let actualGeneratedLocation; + + // If generatedColumn is something other than 0, assume this is a column + // breakpoint and do not perform breakpoint sliding. + if (generatedColumn) { + this._setBreakpointAtColumn(scripts, generatedLocation, actor); + actualGeneratedLocation = generatedLocation; + } else { + let result; + if (actor.scripts.size === 0) { + // If the BreakpointActor is not a breakpoint handler for any script, its + // location is not yet fixed. Use breakpoint sliding to select the first + // line greater than or equal to the requested line that has one or more + // offsets. + result = this._findNextLineWithOffsets(scripts, generatedLine); + } else { + // If the BreakpointActor is a breakpoint handler for at least one script, + // breakpoint sliding has already been carried out, so select the + // requested line, even if it does not have any offsets. + let entryPoints = findEntryPointsForLine(scripts, generatedLine) + if (entryPoints) { + result = { + line: generatedLine, + entryPoints: entryPoints + }; + } + } + + if (!result) { + return actor; + } + + if (result.line !== generatedLine) { + actualGeneratedLocation = new GeneratedLocation( + generatedLocation.generatedSourceActor, + result.line, + generatedLocation.generatedColumn + ); + } else { + actualGeneratedLocation = generatedLocation; + } + + setBreakpointForActorAtEntryPoints(actor, result.entryPoints); + } + + return Promise.resolve().then(() => { + if (actualGeneratedLocation.generatedSourceActor.source) { + return this.threadActor.sources.getOriginalLocation(actualGeneratedLocation); + } else { + return OriginalLocation.fromGeneratedLocation(actualGeneratedLocation); + } + }).then((actualOriginalLocation) => { + if (!actualOriginalLocation.equals(originalLocation)) { + // Check whether we already have a breakpoint actor for the actual + // location. If we do have an existing actor, then the actor we created + // above is redundant and must be destroyed. If we do not have an existing + // actor, we need to update the breakpoint store with the new location. + + let existingActor = this.breakpointActorMap.getActor(actualOriginalLocation); + if (existingActor) { + actor.onDelete(); + this.breakpointActorMap.deleteActor(originalLocation); + actor = existingActor; + } else { + actor.originalLocation = actualOriginalLocation; + this.breakpointActorMap.deleteActor(originalLocation); + this.breakpointActorMap.setActor(actualOriginalLocation, actor); + } + } + + return actor; + }); }, /** @@ -2751,12 +3126,14 @@ SourceActor.prototype = { * @returns Object * The RDP response. */ - _setBreakpointAtColumn: function (scripts, location, actor) { + _setBreakpointAtColumn: function (scripts, generatedLocation, actor) { // Debugger.Script -> array of offset mappings const scriptsAndOffsetMappings = new Map(); for (let script of scripts) { - this._findClosestOffsetMappings(location, script, scriptsAndOffsetMappings); + this._findClosestOffsetMappings(generatedLocation, + script, + scriptsAndOffsetMappings); } for (let [script, mappings] of scriptsAndOffsetMappings) { @@ -2765,10 +3142,6 @@ SourceActor.prototype = { } actor.addScript(script, this.threadActor); } - - return { - actor: actor.actorID - }; }, /** @@ -2803,181 +3176,6 @@ SourceActor.prototype = { return null; }, - /** - * Get or create a BreakpointActor for the given location, and set it as a - * breakpoint handler on all scripts that match the given location for which - * the BreakpointActor is not already a breakpoint handler. - * - * It is possible that no scripts match the given location, because they have - * all been garbage collected. In that case, the BreakpointActor is not set as - * a breakpoint handler for any script, but is still inserted in the - * BreakpointActorMap as a pending breakpoint. Whenever a new script is - * introduced, we call this method again to see if there are now any scripts - * that matches the given location. - * - * The first time we find one or more scripts that matches the given location, - * we check if any of these scripts has any entry points for the given - * location. If not, we assume that the given location does not have any code. - * - * If the given location does not contain any code, we slide the breakpoint - * down to the next closest line that does, and update the BreakpointActorMap - * accordingly. Note that we only do so if the breakpoint actor is still - * pending (i.e. is not set as a breakpoint handler for any script). - * - * @param Number originalLine - * The line number of the breakpoint in the original source. - * @param Number originalColumn - * The column number of the breakpoint in the original source. - * @param String condition - * A condition for the breakpoint. - */ - setBreakpoint: function (originalLine, originalColumn, condition) { - let originalLocation = { - sourceActor: this, - line: originalLine, - column: originalColumn - }; - - return this.threadActor.sources.getGeneratedLocation(originalLocation) - .then(generatedLocation => { - let actor = this._getOrCreateBreakpointActor(originalLocation, - generatedLocation, - condition); - return generatedLocation.sourceActor.setBreakpointForActor(actor); - }); - }, - - /* - * Set the given BreakpointActor as breakpoint handler on all scripts that - * match the given location for which the BreakpointActor is not already a - * breakpoint handler. - * - * @param BreakpointActor actor - * The BreakpointActor to set as breakpoint handler. - */ - setBreakpointForActor: function (actor) { - let originalLocation = actor.originalLocation; - let generatedLocation = { - sourceActor: this, - line: actor.generatedLocation.line, - column: actor.generatedLocation.column - }; - - let { line: generatedLine, column: generatedColumn } = generatedLocation; - - // Find all scripts matching the given location. We will almost always have - // a `source` object to query, but multiple inline HTML scripts are all - // represented by a single SourceActor even though they have separate source - // objects, so we need to query based on the url of the page for them. - let scripts = this.source - ? this.scripts.getScriptsBySourceAndLine(this.source, generatedLine) - : this.scripts.getScriptsByURLAndLine(this._originalUrl, generatedLine); - - if (scripts.length === 0) { - // Since we did not find any scripts to set the breakpoint on now, return - // early. When a new script that matches this breakpoint location is - // introduced, the breakpoint actor will already be in the breakpoint - // store and the breakpoint will be set at that time. This is similar to - // GDB's "pending" breakpoints for shared libraries that aren't loaded - // yet. - return Promise.resolve({ - actor: actor.actorID - }); - } - - // Ignore scripts for which the BreakpointActor is already a breakpoint - // handler. - scripts = scripts.filter((script) => !actor.hasScript(script)); - - let actualLocation; - - // If generatedColumn is something other than 0, assume this is a column - // breakpoint and do not perform breakpoint sliding. - if (generatedColumn) { - this._setBreakpointAtColumn(scripts, generatedLocation, actor); - actualLocation = generatedLocation; - } else { - let result; - if (actor.scripts.size === 0) { - // If the BreakpointActor is not a breakpoint handler for any script, its - // location is not yet fixed. Use breakpoint sliding to select the first - // line greater than or equal to the requested line that has one or more - // offsets. - result = this._findNextLineWithOffsets(scripts, generatedLine); - } else { - // If the BreakpointActor is a breakpoint handler for at least one script, - // breakpoint sliding has already been carried out, so select the - // requested line, even if it does not have any offsets. - let entryPoints = findEntryPointsForLine(scripts, generatedLine) - if (entryPoints) { - result = { - line: generatedLine, - entryPoints: entryPoints - }; - } - } - - if (!result) { - return Promise.resolve({ - error: "noCodeAtLineColumn", - actor: actor.actorID - }); - } - - if (result.line !== generatedLine) { - actualLocation = { - sourceActor: generatedLocation.sourceActor, - line: result.line, - column: generatedLocation.column - }; - - // Check whether we already have a breakpoint actor for the actual - // location. If we do have an existing actor, then the actor we created - // above is redundant and must be destroyed. If we do not have an existing - // actor, we need to update the breakpoint store with the new location. - - let existingActor = this.breakpointActorMap.getActor(actualLocation); - if (existingActor) { - actor.onDelete(); - this.breakpointActorMap.deleteActor(generatedLocation); - actor = existingActor; - } else { - actor.generatedLocation = actualLocation; - this.breakpointActorMap.deleteActor(generatedLocation); - this.breakpointActorMap.setActor(actualLocation, actor); - setBreakpointOnEntryPoints(this.threadActor, actor, result.entryPoints); - } - } else { - setBreakpointOnEntryPoints(this.threadActor, actor, result.entryPoints); - actualLocation = generatedLocation; - } - } - - return Promise.resolve().then(() => { - if (actualLocation.sourceActor.source) { - return this.threadActor.sources.getOriginalLocation({ - sourceActor: actualLocation.sourceActor, - line: actualLocation.line, - column: actualLocation.column - }); - } else { - return actualLocation; - } - }).then((actualLocation) => { - let response = { actor: actor.actorID }; - if (actualLocation.sourceActor.url !== originalLocation.sourceActor.url || - actualLocation.line !== originalLocation.line) - { - response.actualLocation = { - source: actualLocation.sourceActor.form(), - line: actualLocation.line, - column: actualLocation.column - }; - } - return response; - }); - }, - /** * Find all of the offset mappings associated with `aScript` that are closest * to `aTargetLocation`. If new offset mappings are found that are closer to @@ -3019,7 +3217,7 @@ SourceActor.prototype = { aScript, aScriptsAndOffsetMappings) { let offsetMappings = aScript.getAllColumnOffsets() - .filter(({ lineNumber }) => lineNumber === aTargetLocation.line); + .filter(({ lineNumber }) => lineNumber === aTargetLocation.generatedLine); // Attempt to find the current closest offset distance from the target // location by grabbing any offset mapping in the map by doing one iteration @@ -3028,13 +3226,13 @@ SourceActor.prototype = { let closestDistance = Infinity; if (aScriptsAndOffsetMappings.size) { for (let mappings of aScriptsAndOffsetMappings.values()) { - closestDistance = Math.abs(aTargetLocation.column - mappings[0].columnNumber); + closestDistance = Math.abs(aTargetLocation.generatedColumn - mappings[0].columnNumber); break; } } for (let mapping of offsetMappings) { - let currentDistance = Math.abs(aTargetLocation.column - mapping.columnNumber); + let currentDistance = Math.abs(aTargetLocation.generatedColumn - mapping.columnNumber); if (currentDistance > closestDistance) { continue; @@ -3062,6 +3260,7 @@ SourceActor.prototype.requestTypes = { "setBreakpoint": SourceActor.prototype.onSetBreakpoint }; +exports.SourceActor = SourceActor; /** * Determine if a given value is non-primitive. @@ -3329,23 +3528,17 @@ ObjectActor.prototype = { }; } - const generatedLocation = { - sourceActor: this.threadActor.sources.createNonSourceMappedActor(this.obj.script.source), - line: this.obj.script.startLine, - // TODO bug 901138: use Debugger.Script.prototype.startColumn. - column: 0 - }; - - return this.threadActor.sources.getOriginalLocation(generatedLocation) - .then(({ sourceActor, line, column }) => { - - return { - from: this.actorID, - source: sourceActor.form(), - line: line, - column: column - }; - }); + return this.threadActor.sources.getOriginalLocation(new GeneratedLocation( + this.threadActor.sources.createNonSourceMappedActor(this.obj.script.source), + this.obj.script.startLine, + 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn + )).then((originalLocation) => { + return { + source: originalLocation.originalSourceActor.form(), + line: originalLocation.originalLine, + column: originalLocation.originalColumn + }; + }); }, /** @@ -3419,6 +3612,11 @@ ObjectActor.prototype = { continue; } + // Ignore __proto__ on Object.prototye. + if (!obj.proto && name == "__proto__") { + continue; + } + let desc = null, getter = null; try { desc = obj.getOwnPropertyDescriptor(name); @@ -4569,11 +4767,11 @@ FrameActor.prototype = { form.this = threadActor.createValueGrip(this.frame.this); form.arguments = this._args(); if (this.frame.script) { - var loc = this.threadActor.sources.getFrameLocation(this.frame); + var generatedLocation = this.threadActor.sources.getFrameLocation(this.frame); form.where = { - source: loc.sourceActor.form(), - line: loc.line, - column: loc.column + source: generatedLocation.generatedSourceActor.form(), + line: generatedLocation.generatedLine, + column: generatedLocation.generatedColumn }; } @@ -4629,21 +4827,10 @@ FrameActor.prototype.requestTypes = { * * @param ThreadActor aThreadActor * The parent thread actor that contains this breakpoint. - * @param object aOriginalLocation - * An object with the following properties: - * - sourceActor: A SourceActor that represents the source - * - line: the specified line - * - column: the specified column - * @param object aGeneratedLocation - * An object with the following properties: - * - sourceActor: A SourceActor that represents the source - * - line: the specified line - * - column: the specified column - * @param string aCondition - * Optional. A condition which, when false, will cause the breakpoint to - * be skipped. + * @param OriginalLocation aOriginalLocation + * The original location of the breakpoint. */ -function BreakpointActor(aThreadActor, aOriginalLocation, aGeneratedLocation, aCondition) +function BreakpointActor(aThreadActor, aOriginalLocation) { // The set of Debugger.Script instances that this breakpoint has been set // upon. @@ -4651,8 +4838,8 @@ function BreakpointActor(aThreadActor, aOriginalLocation, aGeneratedLocation, aC this.threadActor = aThreadActor; this.originalLocation = aOriginalLocation; - this.generatedLocation = aGeneratedLocation; - this.condition = aCondition; + this.condition = null; + this.isPending = true; } BreakpointActor.prototype = { @@ -4678,6 +4865,7 @@ BreakpointActor.prototype = { */ addScript: function (aScript) { this.scripts.add(aScript); + this.isPending = false; }, /** @@ -4692,17 +4880,44 @@ BreakpointActor.prototype = { /** * Check if this breakpoint has a condition that doesn't error and - * evaluates to true in aFrame + * evaluates to true in aFrame. * * @param aFrame Debugger.Frame * The frame to evaluate the condition in + * @returns Object + * - result: boolean|undefined + * True when the conditional breakpoint should trigger a pause, false otherwise. + * If the condition evaluation failed/killed, `result` will be `undefined`. + * - message: string + * The thrown message converted to a string, when the condition throws. */ - isValidCondition: function(aFrame) { - if (!this.condition) { - return true; + checkCondition: function(aFrame) { + let completion = aFrame.eval(this.condition); + if (completion) { + if (completion.throw) { + // The evaluation failed and threw + let message = "Unknown exception"; + try { + if (completion.throw.getOwnPropertyDescriptor) { + message = completion.throw.getOwnPropertyDescriptor("message").value; + } else if (completion.toString) { + message = completion.toString(); + } + } catch (ex) {} + return { + result: true, + message: message + }; + } else if (completion.yield) { + dbg_assert(false, + "Shouldn't ever get yield completions from an eval"); + } else { + return { result: completion.return ? true : false }; + } + } else { + // The evaluation was killed (possibly by the slow script dialog) + return { result: undefined }; } - var res = aFrame.eval(this.condition); - return res.return; }, /** @@ -4714,24 +4929,38 @@ BreakpointActor.prototype = { hit: function (aFrame) { // Don't pause if we are currently stepping (in or over) or the frame is // black-boxed. - let loc = this.threadActor.sources.getFrameLocation(aFrame); - let { sourceActor } = this.threadActor.synchronize( - this.threadActor.sources.getOriginalLocation(loc)); - let url = sourceActor.url; + let generatedLocation = this.threadActor.sources.getFrameLocation(aFrame); + let { originalSourceActor } = this.threadActor.synchronize( + this.threadActor.sources.getOriginalLocation(generatedLocation)); + let url = originalSourceActor.url; if (this.threadActor.sources.isBlackBoxed(url) - || aFrame.onStep - || !this.isValidCondition(aFrame)) { + || aFrame.onStep) { return undefined; } let reason = {}; + if (this.threadActor._hiddenBreakpoints.has(this.actorID)) { reason.type = "pauseOnDOMEvents"; - } else { + } else if (!this.condition) { reason.type = "breakpoint"; // TODO: add the rest of the breakpoints on that line (bug 676602). reason.actors = [ this.actorID ]; + } else { + let { result, message } = this.checkCondition(aFrame) + + if (result) { + if (!message) { + reason.type = "breakpoint"; + } else { + reason.type = "breakpointConditionThrown"; + reason.message = message; + } + reason.actors = [ this.actorID ]; + } else { + return undefined; + } } return this.threadActor._pauseAndRespond(aFrame, reason); }, @@ -4744,8 +4973,8 @@ BreakpointActor.prototype = { */ onDelete: function (aRequest) { // Remove from the breakpoint store. - if (this.generatedLocation) { - this.threadActor.breakpointActorMap.deleteActor(this.generatedLocation); + if (this.originalLocation) { + this.threadActor.breakpointActorMap.deleteActor(this.originalLocation); } this.threadActor.threadLifetimePool.removeActor(this); // Remove the actual breakpoint from the associated scripts. @@ -5052,732 +5281,13 @@ update(AddonThreadActor.prototype, { constructor: AddonThreadActor, // A constant prefix that will be used to form the actor ID by the server. - actorPrefix: "addonThread", - - /** - * Override the eligibility check for scripts and sources to make - * sure every script and source with a URL is stored when debugging - * add-ons. - */ - _allowSource: function(aSource) { - let url = aSource.url; - - if (isHiddenSource(aSource)) { - return false; - } - - // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it. - if (url === "resource://gre/modules/addons/XPIProvider.jsm") { - return false; - } - - return true; - }, - + actorPrefix: "addonThread" }); exports.AddonThreadActor = AddonThreadActor; -/** - * Manages the sources for a thread. Handles source maps, locations in the - * sources, etc for ThreadActors. - */ -function ThreadSources(aThreadActor, aOptions, aAllowPredicate, - aOnNewSource) { - this._thread = aThreadActor; - this._useSourceMaps = aOptions.useSourceMaps; - this._autoBlackBox = aOptions.autoBlackBox; - this._allow = aAllowPredicate; - this._onNewSource = DevToolsUtils.makeInfallible( - aOnNewSource, - "ThreadSources.prototype._onNewSource" - ); - this._anonSourceMapId = 1; - - // 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$/; - -ThreadSources.prototype = { - /** - * 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), - "ThreadSources.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._allow(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._onNewSource(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._onNewSource(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