author | Mihai Alexandru Michis <malexandru@mozilla.com> |
Mon, 20 May 2019 12:53:43 +0300 | |
changeset 474463 | 97dae745c1b3ef2292127ba1c4e90b1345c8f576 |
parent 474462 | 17d1c1e26e31551fd7baf9b3ea80f8f2382385a0 (current diff) |
parent 474455 | 72731b10931017b247d79f754145726e52672b71 (diff) |
child 474464 | d6e1e3c79dc608b6eafd8d021f77a712d1a475d7 |
child 474484 | acb185a74efcf22bb8491f8eb95cedff393c6184 |
child 474504 | eefb2904757e04b1e4b5f3ba7c480442ce01575e |
push id | 113160 |
push user | malexandru@mozilla.com |
push date | Mon, 20 May 2019 09:59:28 +0000 |
treeherder | mozilla-inbound@d6e1e3c79dc6 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 68.0a1 |
first release with | nightly linux32
97dae745c1b3
/
68.0a1
/
20190520095418
/
files
nightly linux64
97dae745c1b3
/
68.0a1
/
20190520095418
/
files
nightly mac
97dae745c1b3
/
68.0a1
/
20190520095418
/
files
nightly win32
97dae745c1b3
/
68.0a1
/
20190520095418
/
files
nightly win64
97dae745c1b3
/
68.0a1
/
20190520095418
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
68.0a1
/
20190520095418
/
pushlog to previous
nightly linux64
68.0a1
/
20190520095418
/
pushlog to previous
nightly mac
68.0a1
/
20190520095418
/
pushlog to previous
nightly win32
68.0a1
/
20190520095418
/
pushlog to previous
nightly win64
68.0a1
/
20190520095418
/
pushlog to previous
|
--- a/devtools/client/debugger/test/mochitest/browser.ini +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -661,16 +661,17 @@ support-files = examples/doc-windowless-workers.html examples/doc-windowless-workers-early-breakpoint.html examples/simple-worker.js examples/doc-worker-scopes.html examples/scopes-worker.js examples/doc-event-handler.html examples/doc-eval-throw.html examples/doc-sourceURL-breakpoint.html + examples/doc-step-in-uninitialized.html [browser_dbg-asm.js] [browser_dbg-audiocontext.js] [browser_dbg-async-stepping.js] [browser_dbg-sourcemapped-breakpoint-console.js] skip-if = (os == "win" && ccov) # Bug 1453549 [browser_dbg-xhr-breakpoints.js] [browser_dbg-xhr-run-to-completion.js] @@ -774,16 +775,17 @@ skip-if = os == "win" || (verify) # Bug skip-if = os == 'linux' && !asan # bug 1447118 [browser_dbg-sources.js] [browser_dbg-sources-arrow-keys.js] [browser_dbg-sources-named-eval.js] [browser_dbg-sources-querystring.js] skip-if = true [browser_dbg-stepping.js] skip-if = debug || (verify && (os == 'win')) || (os == "win" && os_version == "6.1") +[browser_dbg-step-in-uninitialized.js] [browser_dbg-tabs.js] [browser_dbg-tabs-keyboard.js] skip-if = os == "win" [browser_dbg-tabs-pretty-print.js] [browser_dbg-tabs-without-urls.js] [browser_dbg-toggling-tools.js] [browser_dbg-react-app.js] skip-if = os == "win"
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg-step-in-uninitialized.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/>. */ + +// When stepping into a function, 'let' variables should show as uninitialized +// instead of undefined. + +function findNodeValue(dbg, text) { + for (let index = 0;; index++) { + var elem = findElement(dbg, "scopeNode", index); + if (elem && elem.innerText == text) { + return findElement(dbg, "scopeValue", index).innerText; + } + } +} + +add_task(async function test() { + const dbg = await initDebugger("doc-step-in-uninitialized.html"); + invokeInTab("main"); + await waitForPaused(dbg, "doc-step-in-uninitialized.html"); + + await stepOver(dbg); + await stepIn(dbg); + + assertDebugLine(dbg, 8); + assertPausedLocation(dbg); + + // We step past the 'let x' at the start of the function because it is not + // a breakpoint position. + ok(findNodeValue(dbg, "x") == "undefined", "x undefined"); + ok(findNodeValue(dbg, "y") == "(uninitialized)", "y uninitialized"); + + await stepOver(dbg); + + assertDebugLine(dbg, 9); + + ok(findNodeValue(dbg, "y") == "3", "y initialized"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/examples/doc-step-in-uninitialized.html @@ -0,0 +1,11 @@ +<script> +function main() { + debugger; + foo(); +} +function foo() { + let x; + let y = 3; + return 0; +} +</script>
--- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -40,18 +40,20 @@ support-files = html_statistics-test-page.html html_status-codes-test-page.html html_tracking-protection.html html_api-calls-test-page.html html_copy-as-curl.html html_curl-utils.html html_open-request-in-tab.html html_worker-test-page.html + html_websocket-test-page.html js_worker-test.js js_worker-test2.js + js_websocket-worker-test.js sjs_content-type-test-server.sjs sjs_cors-test-server.sjs sjs_https-redirect-test-server.sjs sjs_hsts-test-server.sjs sjs_json-test-server.sjs sjs_method-test-server.sjs sjs_set-cookie-same-site.sjs sjs_simple-test-server.sjs @@ -219,9 +221,10 @@ skip-if = true # Bug 1373558 [browser_net_timeline_ticks.js] skip-if = true # TODO: fix the test [browser_net_timing-division.js] [browser_net_tracking-resources.js] [browser_net_truncate-post-data.js] [browser_net_truncate.js] [browser_net_view-source-debugger.js] [browser_net_waterfall-click.js] +[browser_net_websocket_stacks.js] [browser_net_worker_stacks.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_websocket_stacks.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we get stack traces for the network requests made when creating +// web sockets on the main or worker threads. + +const TOP_FILE_NAME = "html_websocket-test-page.html"; +const TOP_URL = EXAMPLE_URL + TOP_FILE_NAME; +const WORKER_FILE_NAME = "js_websocket-worker-test.js"; + +const EXPECTED_REQUESTS = [ + { + method: "GET", + url: TOP_URL, + causeType: "document", + causeUri: null, + stack: true, + }, + { + method: "GET", + url: "http://localhost:8080/", + causeType: "websocket", + causeUri: TOP_URL, + stack: [ + { fn: "openSocket", file: TOP_FILE_NAME, line: 6 }, + { file: TOP_FILE_NAME, line: 3 }, + ], + }, + { + method: "GET", + url: EXAMPLE_URL + WORKER_FILE_NAME, + causeType: "script", + causeUri: TOP_URL, + stack: [ { file: TOP_FILE_NAME, line: 9 } ], + }, + { + method: "GET", + url: "https://localhost:8081/", + causeType: "websocket", + causeUri: TOP_URL, + stack: [ + { fn: "openWorkerSocket", file: WORKER_FILE_NAME, line: 5 }, + { file: WORKER_FILE_NAME, line: 2 }, + ], + }, +]; + +add_task(async function() { + // Load a different URL first to instantiate the network monitor before we + // load the page we're really interested in. + const { tab, monitor } = await initNetMonitor(SIMPLE_URL); + + const { store, windowRequire, connector } = monitor.panelWin; + const { + getSortedRequests, + } = windowRequire("devtools/client/netmonitor/src/selectors/index"); + + BrowserTestUtils.loadURI(tab.linkedBrowser, TOP_URL); + + await waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); + + is(store.getState().requests.requests.size, EXPECTED_REQUESTS.length, + "All the page events should be recorded."); + + // Wait for stack traces from all requests. + const requests = getSortedRequests(store.getState()); + await Promise.all(requests.map(requestItem => + connector.requestData(requestItem.id, "stackTrace"))); + + validateRequests(EXPECTED_REQUESTS, monitor); + + await teardown(monitor); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/test/html_websocket-test-page.html @@ -0,0 +1,10 @@ +<script> +"use strict"; +openSocket(); + +function openSocket() { + new WebSocket("ws://localhost:8080/"); +} + +new Worker("js_websocket-worker-test.js"); +</script>
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/test/js_websocket-worker-test.js @@ -0,0 +1,6 @@ +"use strict"; +openWorkerSocket(); + +function openWorkerSocket() { + new WebSocket("wss://localhost:8081"); +}
--- a/devtools/server/actors/network-event.js +++ b/devtools/server/actors/network-event.js @@ -257,28 +257,36 @@ const NetworkEventActor = protocol.Actor * The response packet - stack trace. */ async getStackTrace() { let stacktrace = this._stackTrace; // If _stackTrace was "true", it means we are in parent process // and the stack is available from the content process. // Fetch it lazily from here via the message manager. if (stacktrace && typeof stacktrace == "boolean") { + let id; + if (this._cause.type == "websocket") { + // Convert to a websocket URL, as in onNetworkEvent. + id = this._request.url.replace(/^http/, "ws"); + } else { + id = this._channelId; + } + const messageManager = this.netMonitorActor.messageManager; stacktrace = await new Promise(resolve => { const onMessage = ({ data }) => { const { channelId, stack } = data; - if (channelId == this._channelId) { + if (channelId == id) { messageManager.removeMessageListener("debug:request-stack:response", onMessage); resolve(stack); } }; messageManager.addMessageListener("debug:request-stack:response", onMessage); - messageManager.sendAsyncMessage("debug:request-stack:request", this._channelId); + messageManager.sendAsyncMessage("debug:request-stack:request", id); }); this._stackTrace = stacktrace; } return { stacktrace, }; },
--- a/devtools/server/actors/network-monitor.js +++ b/devtools/server/actors/network-monitor.js @@ -211,24 +211,35 @@ const NetworkMonitorActor = ActorClassWi // map channel to actor so we can associate future events with it this._netEvents.set(channelId, actor); return actor; }, // This method is called by NetworkMonitor instance when a new request is fired onNetworkEvent(event) { - const { channelId } = event; + const { channelId, cause, url } = event; const actor = this.getNetworkEventActor(channelId); this._netEvents.set(channelId, actor); - event.cause.stacktrace = this.stackTraces.has(channelId); + // Find the ID which the stack trace collector will use to save this + // channel's stack trace. + let id; + if (cause.type == "websocket") { + // Use the URL, but convert from the http URL which this channel uses to + // the original websocket URL which triggered this channel's construction. + id = url.replace(/^http/, "ws"); + } else { + id = channelId; + } + + event.cause.stacktrace = this.stackTraces.has(id); if (event.cause.stacktrace) { - this.stackTraces.delete(channelId); + this.stackTraces.delete(id); } actor.init(event); this._networkEventActorsByURL.set(actor._request.url, actor); this.conn.sendActorEvent(this.parentID, "networkEvent", { eventActor: actor.form() }); return actor; },
--- a/devtools/server/actors/network-monitor/stack-trace-collector.js +++ b/devtools/server/actors/network-monitor/stack-trace-collector.js @@ -38,34 +38,46 @@ StackTraceCollector.prototype = { Services.obs.removeObserver(this, "network-monitor-alternate-stack"); ChannelEventSinkFactory.getService().unregisterCollector(this); for (const { messageManager } of this.netmonitors) { messageManager.removeMessageListener("debug:request-stack:request", this.onGetStack); } }, - _saveStackTrace(channel, stacktrace) { - if (this.stacktracesById.has(channel.channelId)) { + _saveStackTrace(id, stacktrace) { + if (this.stacktracesById.has(id)) { // We can get up to two stack traces for the same channel: one each from // the two observer topics we are listening to. Use the first stack trace // which is specified, and ignore any later one. return; } for (const { messageManager } of this.netmonitors) { messageManager.sendAsyncMessage("debug:request-stack-available", { - channelId: channel.channelId, + channelId: id, stacktrace: stacktrace && stacktrace.length > 0, }); } - this.stacktracesById.set(channel.channelId, stacktrace); + this.stacktracesById.set(id, stacktrace); }, observe(subject, topic, data) { - const channel = subject.QueryInterface(Ci.nsIHttpChannel); + let channel, id; + try { + channel = subject.QueryInterface(Ci.nsIHttpChannel); + id = channel.channelId; + } catch (e) { + // WebSocketChannels do not have IDs, so use the URL. When a WebSocket is + // opened in a content process, a channel is created locally but the HTTP + // channel for the connection lives entirely in the parent process. When + // the server code running in the parent sees that HTTP channel, it will + // look for the creation stack using the websocket's URL. + channel = subject.QueryInterface(Ci.nsIWebSocketChannel); + id = channel.URI.spec; + } if (!matchRequest(channel, this.filters)) { return; } const stacktrace = []; switch (topic) { case "http-on-opening-request": { @@ -116,33 +128,33 @@ StackTraceCollector.prototype = { frame = frame.parent || frame.asyncParent; } break; } default: throw new Error("Unexpected observe() topic"); } - this._saveStackTrace(channel, stacktrace); + this._saveStackTrace(id, stacktrace); }, // eslint-disable-next-line no-shadow onChannelRedirect(oldChannel, newChannel, flags) { // We can be called with any nsIChannel, but are interested only in HTTP channels try { oldChannel.QueryInterface(Ci.nsIHttpChannel); newChannel.QueryInterface(Ci.nsIHttpChannel); } catch (ex) { return; } const oldId = oldChannel.channelId; const stacktrace = this.stacktracesById.get(oldId); if (stacktrace) { - this._saveStackTrace(newChannel, stacktrace); + this._saveStackTrace(newChannel.channelId, stacktrace); } }, getStackTrace(channelId) { const trace = this.stacktracesById.get(channelId); this.stacktracesById.delete(channelId); return trace; },
--- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -552,26 +552,46 @@ const ThreadActor = ActorClassWithSpec(t } // If the parent actor has been closed, terminate the debuggee script // instead of continuing. Executing JS after the content window is gone is // a bad idea. return this._parentClosed ? null : undefined; }, - _makeOnEnterFrame: function({ pauseAndRespond }) { + _makeOnEnterFrame: function({ thread, pauseAndRespond }) { return frame => { const { generatedSourceActor } = this.sources.getFrameLocation(frame); const url = generatedSourceActor.url; if (this.sources.isBlackBoxed(url)) { return undefined; } - return pauseAndRespond(frame); + // If the initial frame offset is a step target, we are done. + if (frame.script.getOffsetMetadata(frame.offset).isStepStart) { + return pauseAndRespond(frame); + } + + // Continue forward until we get to a valid step target. + const { onStep, onPop } = thread._makeSteppingHooks( + null, "next", false, null + ); + + if (thread.dbg.replaying) { + const offsets = + thread._findReplayingStepOffsets(null, frame, + /* rewinding = */ false); + frame.setReplayingOnStep(onStep, offsets); + } else { + frame.onStep = onStep; + } + + frame.onPop = onPop; + return undefined; }; }, _makeOnPop: function({ thread, pauseAndRespond, startLocation, steppingType }) { const result = function(completion) { // onPop is called with 'this' set to the current frame. const generatedLocation = thread.sources.getFrameLocation(this); @@ -641,17 +661,17 @@ const ThreadActor = ActorClassWithSpec(t _intraFrameLocationIsStepTarget: function(startLocation, script, offset) { // Only allow stepping stops at entry points for the line. if (!script.getOffsetMetadata(offset).isBreakpoint) { return false; } const generatedLocation = this.sources.getScriptOffsetLocation(script, offset); - if (startLocation.generatedUrl !== generatedLocation.generatedUrl) { + if (!startLocation || startLocation.generatedUrl !== generatedLocation.generatedUrl) { return true; } // TODO(logan): When we remove points points, this can be removed too as // we assert that we're at a different frame offset from the last time // we paused. const lineChanged = startLocation.generatedLine !== generatedLocation.generatedLine; const columnChanged = @@ -670,22 +690,16 @@ const ThreadActor = ActorClassWithSpec(t return pausePoint.step; } return script.getOffsetMetadata(offset).isStepStart; }, _makeOnStep: function({ thread, pauseAndRespond, startFrame, startLocation, steppingType, completion, rewinding }) { - // Breaking in place: we should always pause. - if (steppingType === "break") { - return () => pauseAndRespond(this); - } - - // Otherwise take what a "step" means into consideration. return function() { // onStep is called with 'this' set to the current frame. const generatedLocation = thread.sources.getFrameLocation(this); // Always continue execution if either: // // 1. We are in a source mapped region, but inside a null mapping
--- a/dom/websocket/WebSocket.cpp +++ b/dom/websocket/WebSocket.cpp @@ -13,16 +13,17 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/net/WebSocketChannel.h" #include "mozilla/dom/File.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SerializedStackHolder.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerScope.h" #include "nsAutoPtr.h" #include "mozilla/LoadInfo.h" #include "nsGlobalWindow.h" #include "nsIScriptGlobalObject.h" @@ -119,17 +120,18 @@ class WebSocketImpl final : public nsIIn nsresult Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aPrincipal, bool aIsServerSide, const nsAString& aURL, nsTArray<nsString>& aProtocolArray, const nsACString& aScriptFile, uint32_t aScriptLine, uint32_t aScriptColumn); nsresult AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, nsITransportProvider* aTransportProvider, - const nsACString& aNegotiatedExtensions); + const nsACString& aNegotiatedExtensions, + UniquePtr<SerializedStackHolder> aOriginStack); nsresult ParseURL(const nsAString& aURL); nsresult InitializeConnection(nsIPrincipal* aPrincipal, nsICookieSettings* aCookieSettings); // These methods when called can release the WebSocket object void FailConnection(uint16_t reasonCode, const nsACString& aReasonString = EmptyCString()); @@ -1114,21 +1116,23 @@ class ConnectRunnable final : public Web // Raw pointer. This worker runnable runs synchronously. WebSocketImpl* mImpl; bool mConnectionFailed; }; class AsyncOpenRunnable final : public WebSocketMainThreadRunnable { public: - explicit AsyncOpenRunnable(WebSocketImpl* aImpl) + explicit AsyncOpenRunnable(WebSocketImpl* aImpl, + UniquePtr<SerializedStackHolder> aOriginStack) : WebSocketMainThreadRunnable( aImpl->mWorkerRef->Private(), NS_LITERAL_CSTRING("WebSocket :: AsyncOpen")), mImpl(aImpl), + mOriginStack(std::move(aOriginStack)), mErrorCode(NS_OK) { MOZ_ASSERT(mWorkerPrivate); mWorkerPrivate->AssertIsOnWorkerThread(); } nsresult ErrorCode() const { return mErrorCode; } protected: @@ -1154,33 +1158,36 @@ class AsyncOpenRunnable final : public W if (topWindow) { topInner = topWindow->GetCurrentInnerWindow(); } if (topInner) { windowID = topInner->WindowID(); } - mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString()); + mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), + std::move(mOriginStack)); return true; } virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); mErrorCode = mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, - nullptr, EmptyCString()); + nullptr, EmptyCString(), nullptr); return true; } private: // Raw pointer. This worker runs synchronously. WebSocketImpl* mImpl; + UniquePtr<SerializedStackHolder> mOriginStack; + nsresult mErrorCode; }; } // namespace already_AddRefed<WebSocket> WebSocket::ConstructorCommon( const GlobalObject& aGlobal, const nsAString& aUrl, const Sequence<nsString>& aProtocols, @@ -1356,34 +1363,47 @@ already_AddRefed<WebSocket> WebSocket::C } if (NS_IsMainThread()) { MOZ_ASSERT(principal); nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global); nsPIDOMWindowOuter* outerWindow = ownerWindow->GetOuterWindow(); + UniquePtr<SerializedStackHolder> stack; + nsIDocShell* docShell = outerWindow->GetDocShell(); + if (docShell && docShell->GetWatchedByDevtools()) { + stack = GetCurrentStackForNetMonitor(aGlobal.Context()); + } + uint64_t windowID = 0; nsCOMPtr<nsPIDOMWindowOuter> topWindow = outerWindow->GetScriptableTop(); nsCOMPtr<nsPIDOMWindowInner> topInner; if (topWindow) { topInner = topWindow->GetCurrentInnerWindow(); } if (topInner) { windowID = topInner->WindowID(); } aRv = webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider, - aNegotiatedExtensions); + aNegotiatedExtensions, std::move(stack)); } else { MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(), "not yet implemented"); + + UniquePtr<SerializedStackHolder> stack; + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + if (workerPrivate->IsWatchedByDevtools()) { + stack = GetCurrentStackForNetMonitor(aGlobal.Context()); + } + RefPtr<AsyncOpenRunnable> runnable = - new AsyncOpenRunnable(webSocket->mImpl); + new AsyncOpenRunnable(webSocket->mImpl, std::move(stack)); runnable->Dispatch(Canceling, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } aRv = runnable->ErrorCode(); } @@ -1627,17 +1647,18 @@ nsresult WebSocketImpl::Init(JSContext* } return NS_OK; } nsresult WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, nsITransportProvider* aTransportProvider, - const nsACString& aNegotiatedExtensions) { + const nsACString& aNegotiatedExtensions, + UniquePtr<SerializedStackHolder> aOriginStack) { MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty()); nsCString asciiOrigin; nsresult rv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin); NS_ENSURE_SUCCESS(rv, rv); if (aTransportProvider) { @@ -1654,16 +1675,18 @@ nsresult WebSocketImpl::AsyncOpen(nsIPri MOZ_ASSERT(NS_SUCCEEDED(rv)); } rv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_CONTENT_BLOCKED; } + NotifyNetworkMonitorAlternateStack(mChannel, std::move(aOriginStack)); + mInnerWindowID = aInnerWindowID; return NS_OK; } //----------------------------------------------------------------------------- // WebSocketImpl methods: //-----------------------------------------------------------------------------
--- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1038,18 +1038,21 @@ bool ModuleObject::execute(JSContext* cx if (!AssertFrozen(cx, self)) { return false; } #endif RootedScript script(cx, self->script()); // The top-level script if a module is only ever executed once. Clear the - // reference to prevent us keeping this alive unnecessarily. - self->setReservedSlot(ScriptSlot, UndefinedValue()); + // reference at exit to prevent us keeping this alive unnecessarily. This is + // kept while executing so it is available to the debugger. + auto guardA = mozilla::MakeScopeExit([&] { + self->setReservedSlot(ScriptSlot, UndefinedValue()); + }); RootedModuleEnvironmentObject scope(cx, self->environment()); if (!scope) { JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated"); return false; }
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-module-01.js @@ -0,0 +1,26 @@ +// Debug environments for module environments should include variables that are +// are not closed over or exported. + +var g = newGlobal({newCompartment: true}); +var dbg = Debugger(g); +dbg.onEnterFrame = function (frame) { + if (!frame.older) { + return; + } + const env = frame.older.environment; + assertEq(env.names().join(','), "foo,y,x,z"); + assertEq(env.getVariable('x'), 0); + assertEq(env.getVariable('y'), 1); + assertEq(env.getVariable('z'), 2); + env.setVariable('x', 3); + assertEq(env.getVariable('x'), 3); +}; +const m = g.parseModule(` + var x = 0; + export var y = 1; + const z = 2; + foo(); + function foo() {} +`); +m.declarationInstantiation(); +m.evaluation();
--- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -410,21 +410,21 @@ ModuleEnvironmentObject* ModuleEnvironme } MOZ_ASSERT(env->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE); MOZ_ASSERT(!env->inDictionaryMode()); #endif return env; } -ModuleObject& ModuleEnvironmentObject::module() { +ModuleObject& ModuleEnvironmentObject::module() const { return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>(); } -IndirectBindingMap& ModuleEnvironmentObject::importBindings() { +IndirectBindingMap& ModuleEnvironmentObject::importBindings() const { return module().importBindings(); } bool ModuleEnvironmentObject::createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module, HandleAtom localName) { RootedId importNameId(cx, AtomToId(importName)); @@ -1476,38 +1476,47 @@ class DebugEnvironmentProxyHandler : pub Action action, MutableHandleValue vp, AccessResult* accessResult) const { MOZ_ASSERT(&debugEnv->environment() == env); MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut()); *accessResult = ACCESS_GENERIC; LiveEnvironmentVal* maybeLiveEnv = DebugEnvironments::hasLiveEnvironment(*env); - if (env->is<ModuleEnvironmentObject>()) { - /* Everything is aliased and stored in the environment object. */ - return true; - } - - /* Handle unaliased formals, vars, lets, and consts at function scope. */ - if (env->is<CallObject>()) { - CallObject& callobj = env->as<CallObject>(); - RootedFunction fun(cx, &callobj.callee()); - RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun)); - if (!script->ensureHasAnalyzedArgsUsage(cx)) { - return false; + // Handle unaliased formals, vars, lets, and consts at function or module + // scope. + if (env->is<CallObject>() || env->is<ModuleEnvironmentObject>()) { + RootedScript script(cx); + if (env->is<CallObject>()) { + CallObject& callobj = env->as<CallObject>(); + RootedFunction fun(cx, &callobj.callee()); + script = JSFunction::getOrCreateScript(cx, fun); + if (!script->ensureHasAnalyzedArgsUsage(cx)) { + return false; + } + } else { + script = env->as<ModuleEnvironmentObject>().module().maybeScript(); + if (!script) { + *accessResult = ACCESS_LOST; + return true; + } } BindingIter bi(script); while (bi && NameToId(bi.name()->asPropertyName()) != id) { bi++; } if (!bi) { return true; } + if (bi.location().kind() == BindingLocation::Kind::Import) { + return true; + } + if (!bi.hasArgumentSlot()) { if (bi.closedOver()) { return true; } uint32_t i = bi.location().slot(); if (maybeLiveEnv) { AbstractFramePtr frame = maybeLiveEnv->frame(); @@ -1772,16 +1781,20 @@ class DebugEnvironmentProxyHandler : pub return env.is<LexicalEnvironmentObject>() && !env.as<LexicalEnvironmentObject>().isExtensible(); } static Scope* getEnvironmentScope(const JSObject& env) { if (isFunctionEnvironment(env)) { return env.as<CallObject>().callee().nonLazyScript()->bodyScope(); } + if (env.is<ModuleEnvironmentObject>()) { + JSScript* script = env.as<ModuleEnvironmentObject>().module().maybeScript(); + return script ? script->bodyScope() : nullptr; + } if (isNonExtensibleLexicalEnvironment(env)) { return &env.as<LexicalEnvironmentObject>().scope(); } if (env.is<VarEnvironmentObject>()) { return &env.as<VarEnvironmentObject>().scope(); } if (env.is<WasmInstanceEnvironmentObject>()) { return &env.as<WasmInstanceEnvironmentObject>().scope();
--- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -403,18 +403,18 @@ class ModuleEnvironmentObject : public E public: static const Class class_; static const uint32_t RESERVED_SLOTS = 2; static ModuleEnvironmentObject* create(JSContext* cx, HandleModuleObject module); - ModuleObject& module(); - IndirectBindingMap& importBindings(); + ModuleObject& module() const; + IndirectBindingMap& importBindings() const; bool createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module, HandleAtom exportName); bool hasImportBinding(HandlePropertyName name); bool lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut);