diff options
Diffstat (limited to 'devtools/platform')
-rw-r--r-- | devtools/platform/components.conf | 14 | ||||
-rw-r--r-- | devtools/platform/moz.build | 23 | ||||
-rw-r--r-- | devtools/platform/nsIJSInspector.idl | 75 | ||||
-rw-r--r-- | devtools/platform/nsJSInspector.cpp | 119 | ||||
-rw-r--r-- | devtools/platform/nsJSInspector.h | 38 | ||||
-rw-r--r-- | devtools/platform/tests/xpcshell/.eslintrc.js | 19 | ||||
-rw-r--r-- | devtools/platform/tests/xpcshell/test_nsjsinspector.js | 66 | ||||
-rw-r--r-- | devtools/platform/tests/xpcshell/xpcshell.ini | 6 |
8 files changed, 360 insertions, 0 deletions
diff --git a/devtools/platform/components.conf b/devtools/platform/components.conf new file mode 100644 index 0000000000..7e7d451b83 --- /dev/null +++ b/devtools/platform/components.conf @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{ec5aa99c-7abb-4142-ac5f-aab2419e38e2}', + 'contract_ids': ['@mozilla.org/jsinspector;1'], + 'type': 'mozilla::jsinspector::nsJSInspector', + 'headers': ['/devtools/platform/nsJSInspector.h'], + }, +] diff --git a/devtools/platform/moz.build b/devtools/platform/moz.build new file mode 100644 index 0000000000..2a2cba6f0a --- /dev/null +++ b/devtools/platform/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] + +XPIDL_SOURCES += [ + "nsIJSInspector.idl", +] + +XPIDL_MODULE = "jsinspector" + +SOURCES += [ + "nsJSInspector.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "xul" diff --git a/devtools/platform/nsIJSInspector.idl b/devtools/platform/nsIJSInspector.idl new file mode 100644 index 0000000000..40ad495234 --- /dev/null +++ b/devtools/platform/nsIJSInspector.idl @@ -0,0 +1,75 @@ +/* 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/. */ + +#include "nsISupports.idl" + +/** + * Utilities for running nested event loops, asking them to return, and + * keeping track of which ones are still running. + */ +[scriptable, uuid(6758d0d7-e96a-4c5c-bca8-3bcbe5a15943)] +interface nsIJSInspector : nsISupports +{ + /** + * Process the current thread's event queue, calling event handlers until + * a call to exitNestedEventLoop, below, asks us to return. + * + * The name 'enterNestedEventLoop' may be misleading if read too literally. + * This method loops calling event handlers until one asks it to stop, and + * then returns. So by that point, the nested event loop has been not only + * entered, but also run and exited. + * + * When enterNestedEventLoop calls an event handler, that handler may itself + * call enterNestedEventLoop, and so on, so that there may be arbitrarily + * many such calls on the stack at the same time. + * + * We say an enterNestedEventLoop call is "running" if it has not yet been + * asked to return, or "stopped" if it has been asked to return once it has + * finished processing the current event. + * + * @param requestor A token of the caller's choice to identify this event + * loop. + * + * @return depth The number of running enterNestedEventLoop calls + * remaining, now that this one has returned. + * + * (Note that not all calls still on the stack are + * necessary running; exitNestedEventLoop can ask any + * number of enterNestedEventLoop calls to return.) + */ + unsigned long enterNestedEventLoop(in jsval requestor); + + /** + * Stop the youngest running enterNestedEventLoop call, asking it to return + * once it has finished processing the current event. + * + * The name 'exitNestedEventLoop' may be misleading if read too literally. + * The affected event loop does not return immediately when this method is + * called. Rather, this method simply returns to its caller; the affected + * loop's current event handler is allowed to run to completion; and then + * that loop returns without processing any more events. + * + * This method ignores loops that have already been stopped, and operates on + * the youngest loop that is still running. Each call to this method stops + * another running loop. + * + * @return depth The number of running enterNestedEventLoop calls + * remaining, now that one has been stopped. + * + * @throws NS_ERROR_FAILURE if there are no running enterNestedEventLoop calls. + */ + unsigned long exitNestedEventLoop(); + + /** + * The number of running enterNestedEventLoop calls on the stack. + * This count does not include stopped enterNestedEventLoop calls. + */ + readonly attribute unsigned long eventLoopNestLevel; + + /** + * The |requestor| value that was passed to the youngest running + * enterNestedEventLoop call. + */ + readonly attribute jsval lastNestRequestor; +}; diff --git a/devtools/platform/nsJSInspector.cpp b/devtools/platform/nsJSInspector.cpp new file mode 100644 index 0000000000..02822fb568 --- /dev/null +++ b/devtools/platform/nsJSInspector.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsJSInspector.h" +#include "jsfriendapi.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsArray.h" +#include "nsTArray.h" + +#define JSINSPECTOR_CONTRACTID "@mozilla.org/jsinspector;1" + +#define JSINSPECTOR_CID \ + { \ + 0xec5aa99c, 0x7abb, 0x4142, { \ + 0xac, 0x5f, 0xaa, 0xb2, 0x41, 0x9e, 0x38, 0xe2 \ + } \ + } + +namespace mozilla { +namespace jsinspector { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSInspector) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIJSInspector) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSInspector) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSInspector) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSInspector) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSInspector) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSInspector) + tmp->mRequestors.Clear(); + tmp->mLastRequestor = JS::NullValue(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSInspector) + for (uint32_t i = 0; i < tmp->mRequestors.Length(); ++i) { + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRequestors[i]) + } + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLastRequestor) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +nsJSInspector::nsJSInspector() + : mNestedLoopLevel(0), mRequestors(1), mLastRequestor(JS::NullValue()) {} + +nsJSInspector::~nsJSInspector() { + MOZ_ASSERT(mRequestors.Length() == 0); + MOZ_ASSERT(mLastRequestor.isNull()); + mozilla::DropJSObjects(this); +} + +NS_IMETHODIMP +nsJSInspector::EnterNestedEventLoop(JS::Handle<JS::Value> requestor, + uint32_t* out) { + nsresult rv = NS_OK; + + mLastRequestor = requestor; + mRequestors.AppendElement(requestor); + mozilla::HoldJSObjects(this); + + mozilla::dom::AutoNoJSAPI nojsapi; + + uint32_t nestLevel = ++mNestedLoopLevel; + if (!SpinEventLoopUntil([&]() { return mNestedLoopLevel < nestLevel; })) { + rv = NS_ERROR_UNEXPECTED; + } + + NS_ASSERTION(mNestedLoopLevel <= nestLevel, + "nested event didn't unwind properly"); + + if (mNestedLoopLevel == nestLevel) { + mLastRequestor = mRequestors.ElementAt(--mNestedLoopLevel); + } + + *out = mNestedLoopLevel; + return rv; +} + +NS_IMETHODIMP +nsJSInspector::ExitNestedEventLoop(uint32_t* out) { + if (mNestedLoopLevel > 0) { + mRequestors.RemoveElementAt(--mNestedLoopLevel); + if (mNestedLoopLevel > 0) + mLastRequestor = mRequestors.ElementAt(mNestedLoopLevel - 1); + else + mLastRequestor = JS::NullValue(); + } else { + return NS_ERROR_FAILURE; + } + + *out = mNestedLoopLevel; + + return NS_OK; +} + +NS_IMETHODIMP +nsJSInspector::GetEventLoopNestLevel(uint32_t* out) { + *out = mNestedLoopLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsJSInspector::GetLastNestRequestor(JS::MutableHandle<JS::Value> out) { + out.set(mLastRequestor); + return NS_OK; +} + +} // namespace jsinspector +} // namespace mozilla diff --git a/devtools/platform/nsJSInspector.h b/devtools/platform/nsJSInspector.h new file mode 100644 index 0000000000..61cc864976 --- /dev/null +++ b/devtools/platform/nsJSInspector.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef COMPONENTS_JSINSPECTOR_H +#define COMPONENTS_JSINSPECTOR_H + +#include "nsIJSInspector.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" +#include "nsTArray.h" +#include "js/Value.h" +#include "js/RootingAPI.h" + +namespace mozilla { +namespace jsinspector { + +class nsJSInspector final : public nsIJSInspector { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSInspector) + NS_DECL_NSIJSINSPECTOR + + nsJSInspector(); + + private: + ~nsJSInspector(); + + uint32_t mNestedLoopLevel; + nsTArray<JS::Heap<JS::Value> > mRequestors; + JS::Heap<JS::Value> mLastRequestor; +}; + +} // namespace jsinspector +} // namespace mozilla + +#endif diff --git a/devtools/platform/tests/xpcshell/.eslintrc.js b/devtools/platform/tests/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..dc7f2c85c4 --- /dev/null +++ b/devtools/platform/tests/xpcshell/.eslintrc.js @@ -0,0 +1,19 @@ +"use strict"; + +// Parent config file for all devtools xpcshell files. +module.exports = { + extends: ["plugin:mozilla/xpcshell-test"], + rules: { + // Allow non-camelcase so that run_test doesn't produce a warning. + camelcase: "off", + // Allow using undefined variables so that tests can refer to functions + // and variables defined in head.js files, without having to maintain a + // list of globals in each .eslintrc file. + // Note that bug 1168340 will eventually help auto-registering globals + // from head.js files. + "no-undef": "off", + "block-scoped-var": "off", + // Tests can always import anything. + "mozilla/reject-some-requires": "off", + }, +}; diff --git a/devtools/platform/tests/xpcshell/test_nsjsinspector.js b/devtools/platform/tests/xpcshell/test_nsjsinspector.js new file mode 100644 index 0000000000..a5e63c0cdf --- /dev/null +++ b/devtools/platform/tests/xpcshell/test_nsjsinspector.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the basic functionality of the nsIJSInspector component. +var gCount = 0; +const MAX = 10; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + +// Emulate 10 simultaneously-debugged windows from 3 separate client connections. +var requestor = count => ({ + url: "http://foo/bar/" + count, + connection: "conn" + (count % 3), +}); + +function run_test() { + test_nesting(); +} + +function test_nesting() { + Assert.equal(inspector.eventLoopNestLevel, 0); + + Services.tm.dispatchToMainThread({ run: enterEventLoop }); + + Assert.equal(inspector.enterNestedEventLoop(requestor(gCount)), 0); + Assert.equal(inspector.eventLoopNestLevel, 0); + Assert.equal(inspector.lastNestRequestor, null); +} + +function enterEventLoop() { + if (gCount++ < MAX) { + Services.tm.dispatchToMainThread({ run: enterEventLoop }); + + Object.create(requestor(gCount)); + + Assert.equal(inspector.eventLoopNestLevel, gCount); + Assert.equal(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + Assert.equal( + inspector.lastNestRequestor.connection, + requestor(gCount - 1).connection + ); + Assert.equal(inspector.enterNestedEventLoop(requestor(gCount)), gCount); + } else { + Assert.equal(gCount, MAX + 1); + Services.tm.dispatchToMainThread({ run: exitEventLoop }); + } +} + +function exitEventLoop() { + if (inspector.lastNestRequestor != null) { + Assert.equal(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + Assert.equal( + inspector.lastNestRequestor.connection, + requestor(gCount - 1).connection + ); + if (gCount-- > 1) { + Services.tm.dispatchToMainThread({ run: exitEventLoop }); + } + + Assert.equal(inspector.exitNestedEventLoop(), gCount); + Assert.equal(inspector.eventLoopNestLevel, gCount); + } +} diff --git a/devtools/platform/tests/xpcshell/xpcshell.ini b/devtools/platform/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..91430971a7 --- /dev/null +++ b/devtools/platform/tests/xpcshell/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +tags = devtools +firefox-appdir = browser +skip-if = toolkit == 'android' + +[test_nsjsinspector.js] |