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

- Bug 1165162 - Rework the nsIScriptSecurityManager principal-minting API to be originAttributes-centric. r=gabor,r=bholley,sr=sicking (269536132)
- pointer style and mispatch (38b32b3bc)
- more misspatch & pointer style (ff2bc3057)
- Bug 1164014 - Fixing defaultShims. r=billm (bcf7f671a)
- Bug 1164014 - Workaround for GC bug 1172193. r=gkrizsanits (b25cf4dea)
- Bug 1158427 - r=roc (64037cb2c)
- Bug 1157994 - Ensure AudioContext operations are started and resolved in the same order. r=roc (757de0f81)
- Bug 1127188 - Properly handle AudioContext.close() calls right after the creation of an AudioContext. r=ehsan (88362873f)
- Bug 1164011 - interposition for CPOWS. r=billm (24a8134f6)
- Bug 1164014 - Shim optimization. r=billm (9a8498684)
- Bug 1178581 - Interning does not and should not imply infinite lifetime; r=sfink (91dfc5b77)
- Bug 1171053 - Remove JS_BindCallable. r=efaust (2e59b8c62)
- Bug 1174372 - Initialize ExecutableAllocator static fields in JS_Init. r=luke (d02620196)
- missing uid of 968334 (3c73a17db)
- missing uuid of 1152577 (13d58364c)
- add missing uuid of 1050500 (16c61b629)
- Bug 110567 - Remove nsIDocShell::GetURLSearchParams(), r=smaug (5018a0936)
- Bug 1132518, add a flag to nsIFrameTraversal to skip the popup checks, r=mats (8482fd8fd)
- Bug 1132518, make document navigation with F6/Shift+F6 work in e10s. This combines the document and tab navigation mechanisms together, r=smaug (2085e999b)
- Bug 1160307 - Capture async stack frames on Javascript timeline markers. r=fitzgen, r=smaug, r=Paolo (95c3e6b95)
This commit is contained in:
2021-02-13 10:20:05 +08:00
parent 1311ec4a42
commit 2b832c18de
87 changed files with 1347 additions and 1079 deletions
@@ -0,0 +1,134 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* This list allows pre-existing or 'unfixable' JS issues to remain, while we
* detect newly occurring issues in shipping JS. It is a list of regexes
* matching files which have errors:
*/
const kWhitelist = new Set([
/defaults\/profile\/prefs.js$/,
/browser\/content\/browser\/places\/controller.js$/,
]);
let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
// Normally we would use reflect.jsm to get Reflect.parse. However, if
// we do that, then all the AST data is allocated in reflect.jsm's
// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
// zone but not the zone in which our test code lives (since no new
// data is being allocated in it). The cross-compartment wrappers in
// our zone that point to the AST data never get collected, and so the
// AST data itself is never collected. We need to GC both zones at
// once to fix the problem.
const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
init();
/**
* Check if an error should be ignored due to matching one of the whitelist
* objects defined in kWhitelist
*
* @param uri the uri to check against the whitelist
* @return true if the uri should be skipped, false otherwise.
*/
function uriIsWhiteListed(uri) {
for (let whitelistItem of kWhitelist) {
if (whitelistItem.test(uri.spec)) {
return true;
}
}
return false;
}
function parsePromise(uri) {
let promise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("GET", uri, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
let scriptText = this.responseText;
let ast;
try {
info("Checking " + uri);
ast = Reflect.parse(scriptText);
resolve(true);
} catch (ex) {
let errorMsg = "Script error reading " + uri + ": " + ex;
ok(false, errorMsg);
resolve(false);
}
}
};
xhr.onerror = (error) => {
ok(false, "XHR error reading " + uri + ": " + error);
resolve(false);
};
xhr.overrideMimeType("application/javascript");
xhr.send(null);
});
return promise;
}
add_task(function* checkAllTheJS() {
// In debug builds, even on a fast machine, collecting the file list may take
// more than 30 seconds, and parsing all files may take four more minutes.
// For this reason, this test must be explictly requested in debug builds by
// using the "--setpref parse=<filter>" argument to mach. You can specify:
// - A case-sensitive substring of the file name to test (slow).
// - A single absolute URI printed out by a previous run (fast).
// - An empty string to run the test on all files (slowest).
let parseRequested = Services.prefs.prefHasUserValue("parse");
let parseValue = parseRequested && Services.prefs.getCharPref("parse");
if (SpecialPowers.isDebugBuild) {
if (!parseRequested) {
ok(true, "Test disabled on debug build. To run, execute: ./mach" +
" mochitest-browser --setpref parse=<case_sensitive_filter>" +
" browser/base/content/test/general/browser_parsable_script.js");
return;
}
// Request a 15 minutes timeout (30 seconds * 30) for debug builds.
requestLongerTimeout(30);
}
let uris;
// If an absolute URI is specified on the command line, use it immediately.
if (parseValue && parseValue.includes(":")) {
uris = [NetUtil.newURI(parseValue)];
} else {
let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
// This asynchronously produces a list of URLs (sadly, mostly sync on our
// test infrastructure because it runs against jarfiles there, and
// our zipreader APIs are all sync)
let startTimeMs = Date.now();
info("Collecting URIs");
uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");
// Apply the filter specified on the command line, if any.
if (parseValue) {
uris = uris.filter(uri => {
if (uri.spec.includes(parseValue)) {
return true;
}
info("Not checking filtered out " + uri.spec);
return false;
});
}
}
// We create an array of promises so we can parallelize all our parsing
// and file loading activity:
let allPromises = [];
for (let uri of uris) {
if (uriIsWhiteListed(uri)) {
info("Not checking whitelisted " + uri.spec);
continue;
}
allPromises.push(parsePromise(uri.spec));
}
let promiseResults = yield Promise.all(allPromises);
is(promiseResults.filter((x) => !x).length, 0, "There should be 0 parsing errors");
});
+1
View File
@@ -448,6 +448,7 @@
@RESPATH@/components/nsUpdateTimerManager.js
@RESPATH@/components/addoncompat.manifest
@RESPATH@/components/multiprocessShims.js
@RESPATH@/components/defaultShims.js
@RESPATH@/components/remoteTagService.js
@RESPATH@/components/pluginGlue.manifest
@RESPATH@/components/ProcessSingleton.manifest
+39 -3
View File
@@ -8,6 +8,10 @@
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsPrincipal.h"
#include "nsNetUtil.h"
#include "nsNullPrincipal.h"
#include "nsScriptSecurityManager.h"
#include "mozilla/dom/ToJSValue.h"
@@ -15,7 +19,7 @@
namespace mozilla {
void
BasePrincipal::OriginAttributes::CreateSuffix(nsACString& aStr)
OriginAttributes::CreateSuffix(nsACString& aStr)
{
aStr.Truncate();
MOZ_RELEASE_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
@@ -32,14 +36,14 @@ BasePrincipal::OriginAttributes::CreateSuffix(nsACString& aStr)
}
void
BasePrincipal::OriginAttributes::Serialize(nsIObjectOutputStream* aStream) const
OriginAttributes::Serialize(nsIObjectOutputStream* aStream) const
{
aStream->Write32(mAppId);
aStream->WriteBoolean(mInBrowser);
}
nsresult
BasePrincipal::OriginAttributes::Deserialize(nsIObjectInputStream* aStream)
OriginAttributes::Deserialize(nsIObjectInputStream* aStream)
{
nsresult rv = aStream->Read32(&mAppId);
NS_ENSURE_SUCCESS(rv, rv);
@@ -179,4 +183,36 @@ BasePrincipal::GetUnknownAppId(bool* aUnknownAppId)
return NS_OK;
}
already_AddRefed<BasePrincipal>
BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs)
{
// If the URI is supposed to inherit the security context of whoever loads it,
// we shouldn't make a codebase principal for it.
bool inheritsPrincipal;
nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
&inheritsPrincipal);
nsCOMPtr<nsIPrincipal> principal;
if (NS_FAILED(rv) || inheritsPrincipal) {
return nsNullPrincipal::Create();
}
// Check whether the URI knows what its principal is supposed to be.
nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI);
if (uriPrinc) {
nsCOMPtr<nsIPrincipal> principal;
uriPrinc->GetPrincipal(getter_AddRefs(principal));
if (!principal) {
return nsNullPrincipal::Create();
}
nsRefPtr<BasePrincipal> concrete = Cast(principal);
return concrete.forget();
}
// Mint a codebase principal.
nsRefPtr<nsPrincipal> codebase = new nsPrincipal();
rv = codebase->Init(aURI, aAttrs);
NS_ENSURE_SUCCESS(rv, nullptr);
return codebase.forget();
}
} // namespace mozilla
+30 -27
View File
@@ -18,6 +18,35 @@ class nsIObjectInputStream;
namespace mozilla {
class OriginAttributes : public dom::OriginAttributesDictionary
{
public:
OriginAttributes() {}
OriginAttributes(uint32_t aAppId, bool aInBrowser)
{
mAppId = aAppId;
mInBrowser = aInBrowser;
}
bool operator==(const OriginAttributes& aOther) const
{
return mAppId == aOther.mAppId &&
mInBrowser == aOther.mInBrowser;
}
bool operator!=(const OriginAttributes& aOther) const
{
return !(*this == aOther);
}
// Serializes non-default values into the suffix format, i.e.
// |!key1=value1&key2=value2|. If there are no non-default attributes, this
// returns an empty string.
void CreateSuffix(nsACString& aStr);
void Serialize(nsIObjectOutputStream* aStream) const;
nsresult Deserialize(nsIObjectInputStream* aStream);
};
/*
* Base class from which all nsIPrincipal implementations inherit. Use this for
* default implementations and other commonalities between principal
@@ -51,33 +80,7 @@ public:
virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }
struct OriginAttributes : public dom::OriginAttributesDictionary {
OriginAttributes() {}
OriginAttributes(uint32_t aAppId, bool aInBrowser)
{
mAppId = aAppId;
mInBrowser = aInBrowser;
}
bool operator==(const OriginAttributes& aOther) const
{
return mAppId == aOther.mAppId &&
mInBrowser == aOther.mInBrowser;
}
bool operator!=(const OriginAttributes& aOther) const
{
return !(*this == aOther);
}
// Serializes non-default values into the suffix format, i.e.
// |!key1=value1&key2=value2|. If there are no non-default attributes, this
// returns an empty string.
void CreateSuffix(nsACString& aStr);
void Serialize(nsIObjectOutputStream* aStream) const;
nsresult Deserialize(nsIObjectInputStream* aStream);
};
static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs);
const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
uint32_t AppId() const { return mOriginAttributes.mAppId; }
+19 -3
View File
@@ -26,7 +26,7 @@ class DomainPolicyClone;
[ptr] native JSObjectPtr(JSObject);
[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
[scriptable, uuid(ba602ca6-dc7a-457e-a57a-ee5b343fd863)]
[scriptable, uuid(f4c578b8-5bac-4ba1-9582-f1140e09a3b4)]
interface nsIScriptSecurityManager : nsISupports
{
/**
@@ -179,12 +179,28 @@ interface nsIScriptSecurityManager : nsISupports
nsIPrincipal getNoAppCodebasePrincipal(in nsIURI uri);
/**
* Legacy name for getNoAppCodebasePrincipal.
* Legacy method for getting a principal with no origin attributes.
*
* @deprecated use getNoAppCodebasePrincipal instead.
* @deprecated use createCodebasePrincipal instead.
*/
[deprecated] nsIPrincipal getCodebasePrincipal(in nsIURI uri);
/**
* Returns a principal whose origin is composed of |uri| and |originAttributes|.
* See nsIPrincipal.h for a description of origin attributes, and
* SystemDictionaries.webidl for a list of origin attributes and their defaults.
*/
[implicit_jscontext]
nsIPrincipal createCodebasePrincipal(in nsIURI uri, in jsval originAttributes);
/**
* Returns a unique nonce principal with |originAttributes|.
* See nsIPrincipal.h for a description of origin attributes, and
* SystemDictionaries.webidl for a list of origin attributes and their defaults.
*/
[implicit_jscontext]
nsIPrincipal createNullPrincipal(in jsval originAttributes);
/**
* Returns OK if aSourceURI and target have the same "origin"
* (scheme, host, and port).
+2 -2
View File
@@ -54,9 +54,9 @@ public:
// Returns null on failure.
static already_AddRefed<nsNullPrincipal>
Create(const OriginAttributes& aOriginAttributes = OriginAttributes());
Create(const mozilla::OriginAttributes& aOriginAttributes = mozilla::OriginAttributes());
nsresult Init(const OriginAttributes& aOriginAttributes = OriginAttributes());
nsresult Init(const mozilla::OriginAttributes& aOriginAttributes = mozilla::OriginAttributes());
virtual void GetScriptLocation(nsACString &aStr) override;
+1 -1
View File
@@ -34,7 +34,7 @@ public:
nsPrincipal();
// Init() must be called before the principal is in a usable state.
nsresult Init(nsIURI* aCodebase, const OriginAttributes& aOriginAttributes);
nsresult Init(nsIURI* aCodebase, const mozilla::OriginAttributes& aOriginAttributes);
virtual void GetScriptLocation(nsACString& aStr) override;
void SetURI(nsIURI* aURI);
+56 -81
View File
@@ -19,6 +19,7 @@
#include "nsINestedURI.h"
#include "nspr.h"
#include "nsJSPrincipals.h"
#include "mozilla/BasePrincipal.h"
#include "nsSystemPrincipal.h"
#include "nsPrincipal.h"
#include "nsNullPrincipal.h"
@@ -357,8 +358,10 @@ nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel,
return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal);
}
return GetCodebasePrincipalInternal(uri, UNKNOWN_APP_ID,
/* isInBrowserElement */ false, aPrincipal);
OriginAttributes attrs(UNKNOWN_APP_ID, false);
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@@ -968,55 +971,24 @@ nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result)
return NS_OK;
}
nsresult
nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, uint32_t aAppId,
bool aInMozBrowser,
nsIPrincipal **result)
{
// I _think_ it's safe to not create null principals here based on aURI.
// At least all the callers would do the right thing in those cases, as far
// as I can tell. --bz
nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI);
if (uriPrinc) {
nsCOMPtr<nsIPrincipal> principal;
uriPrinc->GetPrincipal(getter_AddRefs(principal));
if (!principal) {
principal = nsNullPrincipal::Create();
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
}
principal.forget(result);
return NS_OK;
}
BasePrincipal::OriginAttributes attrs(aAppId, aInMozBrowser);
nsRefPtr<nsPrincipal> codebase = new nsPrincipal();
nsresult rv = codebase->Init(aURI, attrs);
if (NS_FAILED(rv))
return rv;
NS_ADDREF(*result = codebase);
return NS_OK;
}
NS_IMETHODIMP
nsScriptSecurityManager::GetSimpleCodebasePrincipal(nsIURI* aURI,
nsIPrincipal** aPrincipal)
{
return GetCodebasePrincipalInternal(aURI,
nsIScriptSecurityManager::UNKNOWN_APP_ID,
false, aPrincipal);
OriginAttributes attrs(UNKNOWN_APP_ID, false);
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI,
nsIPrincipal** aPrincipal)
{
return GetCodebasePrincipalInternal(aURI, nsIScriptSecurityManager::NO_APP_ID,
false, aPrincipal);
OriginAttributes attrs(NO_APP_ID, false);
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@@ -1026,6 +998,33 @@ nsScriptSecurityManager::GetCodebasePrincipal(nsIURI* aURI,
return GetNoAppCodebasePrincipal(aURI, aPrincipal);
}
NS_IMETHODIMP
nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes,
JSContext* aCx, nsIPrincipal** aPrincipal)
{
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsScriptSecurityManager::CreateNullPrincipal(JS::Handle<JS::Value> aOriginAttributes,
JSContext* aCx, nsIPrincipal** aPrincipal)
{
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIPrincipal> prin = nsNullPrincipal::Create(attrs);
NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE);
prin.forget(aPrincipal);
return NS_OK;
}
NS_IMETHODIMP
nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI,
uint32_t aAppId,
@@ -1035,7 +1034,10 @@ nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI,
NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
NS_ERROR_INVALID_ARG);
return GetCodebasePrincipalInternal(aURI, aAppId, aInMozBrowser, aPrincipal);
OriginAttributes attrs(aAppId, aInMozBrowser);
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@@ -1044,14 +1046,13 @@ nsScriptSecurityManager::
nsILoadContext* aLoadContext,
nsIPrincipal** aPrincipal)
{
uint32_t appId;
aLoadContext->GetAppId(&appId);
bool isInBrowserElement;
aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
return GetCodebasePrincipalInternal(aURI,
appId,
isInBrowserElement,
aPrincipal);
// XXXbholley - Make this more general in bug 1165466.
OriginAttributes attrs;
aLoadContext->GetAppId(&attrs.mAppId);
aLoadContext->GetIsInBrowserElement(&attrs.mInBrowser);
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@@ -1059,37 +1060,11 @@ nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI,
nsIDocShell* aDocShell,
nsIPrincipal** aPrincipal)
{
return GetCodebasePrincipalInternal(aURI,
aDocShell->GetAppId(),
aDocShell->GetIsInBrowserElement(),
aPrincipal);
}
nsresult
nsScriptSecurityManager::GetCodebasePrincipalInternal(nsIURI *aURI,
uint32_t aAppId,
bool aInMozBrowser,
nsIPrincipal **result)
{
NS_ENSURE_ARG(aURI);
bool inheritsPrincipal;
nsresult rv =
NS_URIChainHasFlags(aURI,
nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
&inheritsPrincipal);
nsCOMPtr<nsIPrincipal> principal;
if (NS_FAILED(rv) || inheritsPrincipal) {
principal = nsNullPrincipal::Create();
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
} else {
rv = CreateCodebasePrincipal(aURI, aAppId, aInMozBrowser,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
}
principal.forget(result);
return NS_OK;
// XXXbholley - Make this more general in bug 1165466.
OriginAttributes attrs(aDocShell->GetAppId(), aDocShell->GetIsInBrowserElement());
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
// static
+4 -9
View File
@@ -22,6 +22,10 @@ class nsIIOService;
class nsIStringBundle;
class nsSystemPrincipal;
namespace mozilla {
class OriginAttributes;
}
/////////////////////////////
// nsScriptSecurityManager //
/////////////////////////////
@@ -99,15 +103,6 @@ private:
// should error out at that point.
static nsIPrincipal* doGetObjectPrincipal(JSObject* obj);
nsresult
GetCodebasePrincipalInternal(nsIURI* aURI, uint32_t aAppId,
bool aInMozBrowser,
nsIPrincipal** result);
nsresult
CreateCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, bool aInMozBrowser,
nsIPrincipal** result);
nsresult
Init();
+29 -36
View File
@@ -193,7 +193,6 @@
#include "nsIWidget.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/URLSearchParams.h"
#include "nsPerformance.h"
#ifdef MOZ_TOOLKIT_SEARCH
@@ -2039,24 +2038,6 @@ nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
mLSHE->GetIsSubFrame(&isSubFrame);
}
// nsDocShell owns a URLSearchParams that is used by
// window.location.searchParams to be in sync with the current location.
if (!mURLSearchParams) {
mURLSearchParams = new URLSearchParams();
}
nsAutoCString search;
nsCOMPtr<nsIURL> url(do_QueryInterface(mCurrentURI));
if (url) {
nsresult rv = url->GetQuery(search);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to get the query from a nsIURL.");
}
}
mURLSearchParams->ParseInput(search, nullptr);
if (!isSubFrame && !isRoot) {
/*
* We don't want to send OnLocationChange notifications when
@@ -2827,16 +2808,16 @@ nsDocShell::GetBusyFlags(uint32_t* aBusyFlags)
}
NS_IMETHODIMP
nsDocShell::TabToTreeOwner(bool aForward, bool* aTookFocus)
nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, bool* aTookFocus)
{
NS_ENSURE_ARG_POINTER(aTookFocus);
nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner);
if (chromeFocus) {
if (aForward) {
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement());
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation));
} else {
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement());
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation));
}
} else {
*aTookFocus = false;
@@ -5869,11 +5850,6 @@ nsDocShell::Destroy()
mParentWidget = nullptr;
mCurrentURI = nullptr;
if (mURLSearchParams) {
mURLSearchParams->RemoveObservers();
mURLSearchParams = nullptr;
}
if (mScriptGlobal) {
mScriptGlobal->DetachFromDocShell();
mScriptGlobal = nullptr;
@@ -14115,12 +14091,6 @@ nsDocShell::GetOpener()
return opener;
}
URLSearchParams*
nsDocShell::GetURLSearchParams()
{
return mURLSearchParams;
}
class JavascriptTimelineMarker : public TimelineMarker
{
public:
@@ -14128,7 +14098,9 @@ public:
const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFileName,
uint32_t aLineNumber)
uint32_t aLineNumber,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JS::Value> aAsyncCause)
: TimelineMarker(aDocShell, aName, TRACING_INTERVAL_START,
NS_ConvertUTF8toUTF16(aReason),
NO_STACK)
@@ -14136,6 +14108,11 @@ public:
, mFileName(aFileName)
, mLineNumber(aLineNumber)
{
JSContext* ctx = nsContentUtils::GetCurrentJSContext();
if (ctx) {
mAsyncStack.init(ctx, aAsyncStack);
mAsyncCause.init(ctx, aAsyncCause);
}
}
void AddDetails(JSContext* aCx, mozilla::dom::ProfileTimelineMarker& aMarker)
@@ -14148,6 +14125,17 @@ public:
stackFrame.mLine.Construct(mLineNumber);
stackFrame.mSource.Construct(mFileName);
stackFrame.mFunctionDisplayName.Construct(mFunctionName);
if (mAsyncStack.isObject() && !mAsyncStack.isNullOrUndefined() &&
mAsyncCause.isString()) {
JS::Rooted<JSObject*> asyncStack(aCx, mAsyncStack.toObjectOrNull());
JS::Rooted<JSString*> asyncCause(aCx, mAsyncCause.toString());
JS::Rooted<JSObject*> parentFrame(aCx);
if (!JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) {
JS_ClearPendingException(aCx);
} else {
stackFrame.mAsyncParent = parentFrame;
}
}
JS::Rooted<JS::Value> newStack(aCx);
if (ToJSValue(aCx, stackFrame, &newStack)) {
@@ -14164,13 +14152,17 @@ private:
nsString mFunctionName;
nsString mFileName;
uint32_t mLineNumber;
JS::PersistentRooted<JS::Value> mAsyncStack;
JS::PersistentRooted<JS::Value> mAsyncCause;
};
void
nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFilename,
const uint32_t aLineNumber)
const uint32_t aLineNumber,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JS::Value> aAsyncCause)
{
bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
@@ -14179,7 +14171,8 @@ nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<JavascriptTimelineMarker>(this, "Javascript", aReason,
aFunctionName, aFilename,
aLineNumber);
aLineNumber, aAsyncStack,
aAsyncCause);
AddProfileTimelineMarker(Move(marker));
}
mJSRunToCompletionDepth++;
-4
View File
@@ -59,7 +59,6 @@
namespace mozilla {
namespace dom {
class EventTarget;
class URLSearchParams;
}
}
@@ -813,9 +812,6 @@ protected:
nsCOMPtr<nsIChannel> mFailedChannel;
uint32_t mFailedLoadType;
// window.location.searchParams is updated in sync with this object.
nsRefPtr<mozilla::dom::URLSearchParams> mURLSearchParams;
// Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or
// the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set.
// Checked in nsMixedContentBlocker, to see if the channels match.
+9 -15
View File
@@ -11,13 +11,6 @@
#include "js/TypeDecls.h"
class nsPresContext;
class nsIPresShell;
namespace mozilla {
namespace dom {
class URLSearchParams;
}
}
%}
/**
@@ -26,7 +19,6 @@ class URLSearchParams;
[ptr] native nsPresContext(nsPresContext);
[ptr] native nsIPresShell(nsIPresShell);
[ptr] native URLSearchParams(mozilla::dom::URLSearchParams);
interface nsIURI;
interface nsIChannel;
@@ -54,7 +46,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(f84b1ae4-2f78-4bad-b36a-6a8516ee6e40)]
[scriptable, builtinclass, uuid(9f2babc4-4c2a-4cf7-929f-a1efc325b0df)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@@ -349,9 +341,12 @@ interface nsIDocShell : nsIDocShellTreeItem
/*
* Tells the docshell to offer focus to its tree owner.
* This is currently only necessary for embedding chrome.
* If forDocumentNavigation is true, then document navigation should be
* performed, where only the root of documents are selected. Otherwise, the
* next element in the parent should be returned. Returns true if focus was
* successfully taken by the tree owner.
*/
void tabToTreeOwner(in boolean forward,
out boolean tookFocus);
bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation);
/**
* Current busy state for DocShell
@@ -1025,9 +1020,6 @@ interface nsIDocShell : nsIDocShellTreeItem
[noscript,notxpcom,nostdcall] void setOpener(in nsITabParent aOpener);
[noscript,notxpcom,nostdcall] nsITabParent getOpener();
// URLSearchParams for the window.location is owned by the docShell.
[noscript,notxpcom] URLSearchParams getURLSearchParams();
/**
* Notify DocShell when the browser is about to start executing JS, and after
* that execution has stopped. This only occurs when the Timeline devtool
@@ -1036,7 +1028,9 @@ interface nsIDocShell : nsIDocShellTreeItem
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
in wstring functionName,
in wstring fileName,
in unsigned long lineNumber);
in unsigned long lineNumber,
in jsval asyncStack,
in jsval asyncCause);
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
/**
@@ -31,4 +31,50 @@ let TESTS = [{
}
}];
if (Services.prefs.getBoolPref("javascript.options.asyncstack")) {
TESTS.push({
desc: "Async stack trace on Javascript marker",
searchFor: (markers) => {
return markers.some(m => (m.name == "Javascript" &&
m.causeName == "promise callback"));
},
setup: function(docShell) {
content.dispatchEvent(new content.Event("promisetest"));
},
check: function(markers) {
markers = markers.filter(m => (m.name == "Javascript" &&
m.causeName == "promise callback"));
ok(markers.length > 0, "Found a Javascript marker");
let frame = markers[0].stack;
ok(frame.asyncParent !== null, "Parent frame has async parent");
is(frame.asyncParent.asyncCause, "Promise",
"Async parent has correct cause");
is(frame.asyncParent.functionDisplayName, "do_promise",
"Async parent has correct function name");
}
}, {
desc: "Async stack trace on Javascript marker with script",
searchFor: (markers) => {
return markers.some(m => (m.name == "Javascript" &&
m.causeName == "promise callback"));
},
setup: function(docShell) {
content.dispatchEvent(new content.Event("promisescript"));
},
check: function(markers) {
markers = markers.filter(m => (m.name == "Javascript" &&
m.causeName == "promise callback"));
ok(markers.length > 0, "Found a Javascript marker");
let frame = markers[0].stack;
ok(frame.asyncParent !== null, "Parent frame has async parent");
is(frame.asyncParent.asyncCause, "Promise",
"Async parent has correct cause");
is(frame.asyncParent.functionDisplayName, "do_promise_script",
"Async parent has correct function name");
}
});
}
timelineContentTest(TESTS);
@@ -21,6 +21,33 @@
}
window.addEventListener("dog", do_xhr, true);
function do_promise() {
new Promise(function(resolve, reject) {
console.time("Bob");
window.setTimeout(function() {
resolve(23);
}, 10);
}).then(function (val) {
console.timeEnd("Bob");
});
}
window.addEventListener("promisetest", do_promise, true);
var globalResolver;
function do_promise_script() {
new Promise(function(resolve, reject) {
console.time("Bob");
globalResolver = resolve;
window.setTimeout("globalResolver(23);", 10);
}).then(function (val) {
console.timeEnd("Bob");
});
}
window.addEventListener("promisescript", do_promise_script, true);
</script>
</body>
+6 -2
View File
@@ -600,7 +600,8 @@ AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
void
AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
JSScript* aScript)
JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause)
{
JS::Rooted<JSFunction*> rootedFunction(aCx);
if (aFunction) {
@@ -645,10 +646,13 @@ AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFuncti
const char16_t* functionNameChars = functionName.isTwoByte() ?
functionName.twoByteChars() : nullptr;
JS::Rooted<JS::Value> asyncCauseValue(aCx, aAsyncCause ? StringValue(aAsyncCause) :
JS::NullValue());
docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
functionNameChars,
filename.BeginReading(),
lineNumber);
lineNumber, aAsyncStack,
asyncCauseValue);
}
}
+11 -5
View File
@@ -359,20 +359,26 @@ private:
public:
DocshellEntryMonitor(JSContext* aCx, const char* aReason);
void Entry(JSContext* aCx, JSFunction* aFunction) override
void Entry(JSContext* aCx, JSFunction* aFunction,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause) override
{
Entry(aCx, aFunction, nullptr);
Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
}
void Entry(JSContext* aCx, JSScript* aScript) override
void Entry(JSContext* aCx, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause) override
{
Entry(aCx, nullptr, aScript);
Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
}
void Exit(JSContext* aCx) override;
private:
void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript);
void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause);
const char* mReason;
};
+1 -1
View File
@@ -349,7 +349,7 @@ nsresult
nsDOMClassInfo::DefineStaticJSVals(JSContext *cx)
{
#define SET_JSID_TO_STRING(_id, _cx, _str) \
if (JSString *str = ::JS_InternString(_cx, _str)) \
if (JSString *str = ::JS_AtomizeAndPinString(_cx, _str)) \
_id = INTERNED_STRING_TO_JSID(_cx, str); \
else \
return NS_ERROR_OUT_OF_MEMORY;
+302 -402
View File
@@ -16,6 +16,7 @@
#include "nsIDOMWindow.h"
#include "nsIEditor.h"
#include "nsPIDOMWindow.h"
#include "nsIDOMChromeWindow.h"
#include "nsIDOMElement.h"
#include "nsIDOMDocument.h"
#include "nsIDOMRange.h"
@@ -33,6 +34,7 @@
#include "nsFrameSelection.h"
#include "mozilla/dom/Selection.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIPrincipal.h"
#include "nsIObserverService.h"
@@ -41,6 +43,7 @@
#include "nsStyleCoord.h"
#include "SelectionCarets.h"
#include "TabChild.h"
#include "nsFrameLoader.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/dom/Element.h"
@@ -521,6 +524,10 @@ nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
nsCOMPtr<nsIContent> newFocus;
nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
getter_AddRefs(newFocus));
if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
return NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
@@ -2348,7 +2355,8 @@ nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
eLeaf,
false, // aVisual
false, // aLockInScrollView
true // aFollowOOFs
true, // aFollowOOFs
false // aSkipPopupChecks
);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2408,9 +2416,19 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
{
*aNextContent = nullptr;
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
if (!docShell)
return NS_OK;
// True if we are navigating by document (F6/Shift+F6) or false if we are
// navigating by element (Tab/Shift+Tab).
bool forDocumentNavigation = false;
// This is used for document navigation only. It will be set to true if we
// start navigating from a starting point. If this starting point is near the
// end of the document (for example, an element on a statusbar), and there
// are no child documents or panels before the end of the document, then we
// will need to ensure that we don't consider the root chrome window when we
// loop around and instead find the next child document/panel, as focus is
// already in that window. This flag will be cleared once we navigate into
// another document.
bool mayFocusRoot = (aStartContent != nullptr);
nsCOMPtr<nsIContent> startContent = aStartContent;
if (!startContent && aType != MOVEFOCUS_CARET) {
@@ -2436,19 +2454,24 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
&nsIContent::sTabFocusModel);
if (aType == MOVEFOCUS_ROOT) {
// These types are for document navigation using F6.
if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
forDocumentNavigation = true;
}
// If moving to the root or first document, find the root element and return.
if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
return NS_OK;
if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
// When looking for the first document, if the root wasn't focusable,
// find the next focusable document.
aType = MOVEFOCUS_FORWARDDOC;
} else {
return NS_OK;
}
}
if (aType == MOVEFOCUS_FORWARDDOC) {
NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true));
return NS_OK;
}
if (aType == MOVEFOCUS_BACKWARDDOC) {
NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false));
return NS_OK;
}
nsIContent* rootContent = doc->GetRootElement();
NS_ENSURE_TRUE(rootContent, NS_OK);
@@ -2460,17 +2483,19 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
startContent = rootContent;
return GetNextTabbableContent(presShell, startContent,
nullptr, startContent,
true, 1, false, aNextContent);
true, 1, false, false, aNextContent);
}
if (aType == MOVEFOCUS_LAST) {
if (!aStartContent)
startContent = rootContent;
return GetNextTabbableContent(presShell, startContent,
nullptr, startContent,
false, 0, false, aNextContent);
false, 0, false, false, aNextContent);
}
bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
bool forward = (aType == MOVEFOCUS_FORWARD ||
aType == MOVEFOCUS_FORWARDDOC ||
aType == MOVEFOCUS_CARET);
bool doNavigation = true;
bool ignoreTabIndex = false;
// when a popup is open, we want to ensure that tab navigation occurs only
@@ -2505,7 +2530,7 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
nsGkAtoms::menuPopupFrame);
}
if (popupFrame) {
if (popupFrame && !forDocumentNavigation) {
// Don't navigate outside of a popup, so pretend that the
// root content is the popup itself
rootContent = popupFrame->GetContent();
@@ -2537,13 +2562,24 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
}
#endif
if (popupFrame) {
rootContent = popupFrame->GetContent();
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
startContent = rootContent;
// When there is a popup open, and no starting content, start the search
// at the topmost popup.
startContent = popupFrame->GetContent();
NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
// Unless we are searching for documents, set the root content to the
// popup as well, so that we don't tab-navigate outside the popup.
// When navigating by documents, we start at the popup but can navigate
// outside of it to look for other panels and documents.
if (!forDocumentNavigation) {
rootContent = startContent;
}
doc = startContent ? startContent->GetComposedDoc() : nullptr;
}
else {
// Otherwise, for content shells, start from the location of the caret.
if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
nsCOMPtr<nsIContent> endSelectionContent;
GetSelectionLocation(doc, presShell,
getter_AddRefs(startContent),
@@ -2597,7 +2633,8 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
nsIContent* originalStartContent = startContent;
LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
LOGFOCUSNAVIGATION((" Tabindex: %d Ignore: %d", tabIndex, ignoreTabIndex));
LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
forward, tabIndex, ignoreTabIndex, forDocumentNavigation));
while (doc) {
if (doNavigation) {
@@ -2606,22 +2643,29 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
skipOriginalContentCheck ? nullptr : originalStartContent,
startContent, forward,
tabIndex, ignoreTabIndex,
forDocumentNavigation,
getter_AddRefs(nextFocus));
NS_ENSURE_SUCCESS(rv, rv);
if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
// Navigation was redirected to a child process, so just return.
return NS_OK;
}
// found a content node to focus.
if (nextFocus) {
LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
// as long as the found node was not the same as the starting node,
// set it as the return value.
if (nextFocus != originalStartContent) {
// set it as the return value. For document navigation, we can return
// the same element in case there is only one content node that could
// be returned, for example, in a child process document.
if (nextFocus != originalStartContent || forDocumentNavigation) {
nextFocus.forget(aNextContent);
}
return NS_OK;
}
if (popupFrame) {
if (popupFrame && !forDocumentNavigation) {
// in a popup, so start again from the beginning of the popup. However,
// if we already started at the beginning, then there isn't anything to
// focus, so just return
@@ -2635,7 +2679,7 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
}
doNavigation = true;
skipOriginalContentCheck = false;
skipOriginalContentCheck = forDocumentNavigation;
ignoreTabIndex = false;
if (aNoParentTraversal) {
@@ -2647,46 +2691,45 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
continue;
}
// reached the beginning or end of the document. Traverse up to the parent
// document and try again.
nsCOMPtr<nsIDocShellTreeItem> docShellParent;
docShell->GetParent(getter_AddRefs(docShellParent));
if (docShellParent) {
// move up to the parent shell and try again from there.
// Reached the beginning or end of the document. Next, navigate up to the
// parent document and try again.
nsCOMPtr<nsPIDOMWindow> piWindow = doc->GetWindow();
NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
// first, get the frame element this window is inside.
nsCOMPtr<nsPIDOMWindow> piWindow = docShell->GetWindow();
NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
// Next, retrieve the parent docshell, document and presshell.
docShell = do_QueryInterface(docShellParent);
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
nsCOMPtr<nsPIDOMWindow> piParentWindow = docShellParent->GetWindow();
NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
doc = piParentWindow->GetExtantDoc();
// Get the frame element this window is inside and, from that, get the
// parent document and presshell. If there is no enclosing frame element,
// then this is a top-level, embedded or remote window.
startContent = piWindow->GetFrameElementInternal();
if (startContent) {
doc = startContent->GetComposedDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
rootContent = doc->GetRootElement();
presShell = doc->GetShell();
rootContent = doc->GetRootElement();
startContent = piWindow->GetFrameElementInternal();
if (startContent) {
nsIFrame* frame = startContent->GetPrimaryFrame();
if (!frame)
return NS_OK;
// We can focus the root element now that we have moved to another document.
mayFocusRoot = true;
frame->IsFocusable(&tabIndex, 0);
if (tabIndex < 0) {
tabIndex = 1;
ignoreTabIndex = true;
}
nsIFrame* frame = startContent->GetPrimaryFrame();
if (!frame) {
return NS_OK;
}
// if the frame is inside a popup, make sure to scan only within the
// popup. This handles the situation of tabbing amongst elements
// inside an iframe which is itself inside a popup. Otherwise,
// navigation would move outside the popup when tabbing outside the
// iframe.
frame->IsFocusable(&tabIndex, 0);
if (tabIndex < 0) {
tabIndex = 1;
ignoreTabIndex = true;
}
// if the frame is inside a popup, make sure to scan only within the
// popup. This handles the situation of tabbing amongst elements
// inside an iframe which is itself inside a popup. Otherwise,
// navigation would move outside the popup when tabbing outside the
// iframe.
if (!forDocumentNavigation) {
popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
nsGkAtoms::menuPopupFrame);
if (popupFrame) {
@@ -2694,17 +2737,13 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
}
}
else {
startContent = rootContent;
tabIndex = forward ? 1 : 0;
}
}
else {
// no parent, so call the tree owner. This will tell the embedder that
// it should take the focus.
// There is no parent, so call the tree owner. This will tell the
// embedder or parent process that it should take the focus.
bool tookFocus;
docShell->TabToTreeOwner(forward, &tookFocus);
// if the tree owner, took the focus, blur the current content
docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
// If the tree owner took the focus, blur the current content.
if (tookFocus) {
nsCOMPtr<nsPIDOMWindow> window = docShell->GetWindow();
if (window->GetFocusedNode() == mFocusedContent)
@@ -2714,6 +2753,23 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
return NS_OK;
}
// If we have reached the end of the top-level document, focus the
// first element in the top-level document. This should always happen
// when navigating by document forwards but when navigating backwards,
// only do this if we started in another document or within a popup frame.
// If the focus started in this window outside a popup however, we should
// continue by looping around to the end again.
if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
// HTML content documents can have their root element focused (a focus
// ring appears around the entire content area frame). This root
// appears in the tab order before all of the elements in the document.
// Chrome documents however cannot be focused directly, so instead we
// focus the first focusable element within the window.
// For example, the urlbar.
nsIContent* root = GetRootForFocus(piWindow, doc, true, true);
return FocusFirst(root, aNextContent);
}
// reset the tab index and start again from the beginning or end
startContent = rootContent;
tabIndex = forward ? 1 : 0;
@@ -2736,6 +2792,7 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
bool aForward,
int32_t aCurrentTabIndex,
bool aIgnoreTabIndex,
bool aForDocumentNavigation,
nsIContent** aResultContent)
{
*aResultContent = nullptr;
@@ -2772,13 +2829,17 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
continue;
}
// For tab navigation, pass false for aSkipPopupChecks so that we don't
// iterate into or out of a popup. For document naviation pass true to
// ignore these boundaries.
nsCOMPtr<nsIFrameEnumerator> frameTraversal;
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
presContext, startFrame,
ePreOrder,
false, // aVisual
false, // aLockInScrollView
true // aFollowOOFs
true, // aFollowOOFs
aForDocumentNavigation // aSkipPopupChecks
);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2803,6 +2864,51 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// Walk frames to find something tabbable matching mCurrentTabIndex
nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
while (frame) {
nsIContent* currentContent = frame->GetContent();
// For document navigation, check if this element is an open panel. Since
// panels aren't focusable (tabIndex would be -1), we'll just assume that
// for document navigation, the tabIndex is 0.
if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
currentContent->IsXULElement(nsGkAtoms::panel)) {
nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
// Check if the panel is open. Closed panels are ignored since you can't
// focus anything in them.
if (popupFrame && popupFrame->IsOpen()) {
// When moving backward, skip the popup we started in otherwise it
// will be selected again.
bool validPopup = true;
if (!aForward) {
nsIContent* content = aStartContent;
while (content) {
if (content == currentContent) {
validPopup = false;
break;
}
content = content->GetParent();
}
}
if (validPopup) {
// Since a panel isn't focusable itself, find the first focusable
// content within the popup. If there isn't any focusable content
// in the popup, skip this popup and continue iterating through the
// frames. We pass the panel itself (currentContent) as the starting
// and root content, so that we only find content within the panel.
// Note also that we pass false for aForDocumentNavigation since we
// want to locate the first content, not the first document.
rv = GetNextTabbableContent(aPresShell, currentContent,
nullptr, currentContent,
true, 1, false, false,
aResultContent);
if (NS_SUCCEEDED(rv) && *aResultContent) {
return rv;
}
}
}
}
// TabIndex not set defaults to 0 for form elements, anchors and other
// elements that are normally focusable. Tabindex defaults to -1
// for elements that are not normally focusable.
@@ -2810,17 +2916,16 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// < 0 not tabbable at all
// == 0 in normal tab order (last after positive tabindexed items)
// > 0 can be tabbed to in the order specified by this value
int32_t tabIndex;
frame->IsFocusable(&tabIndex, 0);
LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
nsIContent* currentContent = frame->GetContent();
if (tabIndex >= 0) {
NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
if (currentContent->IsHTMLElement(nsGkAtoms::img) &&
if (!aForDocumentNavigation &&
currentContent->IsHTMLElement(nsGkAtoms::img) &&
currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
// This is an image with a map. Image map areas are not traversed by
// nsIFrameTraversal so look for the next or previous area element.
@@ -2839,29 +2944,50 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
return NS_OK;
}
// found a node with a matching tab index. Check if it is a child
// frame. If so, navigate into the child frame instead.
nsIDocument* doc = currentContent->GetComposedDoc();
NS_ASSERTION(doc, "content not in document");
nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
if (subdoc) {
if (!subdoc->EventHandlingSuppressed()) {
bool checkSubDocument = true;
if (aForDocumentNavigation) {
// If this is a remote child browser, call NavigateDocument to have
// the child process continue the navigation. Return a special error
// code to have the caller return early. If the child ends up not
// being focusable in some way, the child process will call back
// into document navigation again by calling MoveFocus.
TabParent* remote = TabParent::GetFrom(currentContent);
if (remote) {
remote->NavigateDocument(aForward);
return NS_SUCCESS_DOM_NO_OPERATION;
}
// Next, check if this a non-remote child document.
nsIContent* docRoot = GetRootForChildDocument(currentContent);
if (docRoot) {
// If GetRootForChildDocument returned something then call
// FocusFirst to find the root or first element to focus within
// the child document. If this is a frameset though, skip this and
// fall through to the checkSubDocument block below to iterate into
// the frameset's frames and locate the first focusable frame.
if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
return FocusFirst(docRoot, aResultContent);
}
} else {
// Set checkSubDocument to false, as this was neither a frame
// type element or a child document that was focusable.
checkSubDocument = false;
}
}
if (checkSubDocument) {
// found a node with a matching tab index. Check if it is a child
// frame. If so, navigate into the child frame instead.
nsIDocument* doc = currentContent->GetComposedDoc();
NS_ASSERTION(doc, "content not in document");
nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
if (subdoc && !subdoc->EventHandlingSuppressed()) {
if (aForward) {
// when tabbing forward into a frame, return the root
// frame so that the canvas becomes focused.
nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
if (subframe) {
// If the subframe body is editable by contenteditable,
// we should set the editor's root element rather than the
// actual root element. Otherwise, we should set the focus
// to the root content.
*aResultContent =
nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
if (!*aResultContent ||
!((*aResultContent)->GetPrimaryFrame())) {
*aResultContent =
GetRootForFocus(subframe, subdoc, false, true);
}
*aResultContent = GetRootForFocus(subframe, subdoc, false, true);
if (*aResultContent) {
NS_ADDREF(*aResultContent);
return NS_OK;
@@ -2874,29 +3000,29 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
rv = GetNextTabbableContent(subShell, rootElement,
aOriginalStartContent, rootElement,
aForward, (aForward ? 1 : 0),
false, aResultContent);
false, aForDocumentNavigation, aResultContent);
NS_ENSURE_SUCCESS(rv, rv);
if (*aResultContent)
return NS_OK;
}
}
}
// otherwise, use this as the next content node to tab to, unless
// this was the element we started on. This would happen for
// instance on an element with child frames, where frame navigation
// could return the original element again. In that case, just skip
// it. Also, if the next content node is the root content, then
// return it. This latter case would happen only if someone made a
// popup focusable.
// Also, when going backwards, check to ensure that the focus
// wouldn't be redirected. Otherwise, for example, when an input in
// a textbox is focused, the enclosing textbox would be found and
// the same inner input would be returned again.
else if (currentContent == aRootContent ||
(currentContent != startContent &&
(aForward || !GetRedirectedFocus(currentContent)))) {
NS_ADDREF(*aResultContent = currentContent);
return NS_OK;
// otherwise, use this as the next content node to tab to, unless
// this was the element we started on. This would happen for
// instance on an element with child frames, where frame navigation
// could return the original element again. In that case, just skip
// it. Also, if the next content node is the root content, then
// return it. This latter case would happen only if someone made a
// popup focusable.
// Also, when going backwards, check to ensure that the focus
// wouldn't be redirected. Otherwise, for example, when an input in
// a textbox is focused, the enclosing textbox would be found and
// the same inner input would be returned again.
else if (currentContent == aRootContent ||
(currentContent != startContent &&
(aForward || !GetRedirectedFocus(currentContent)))) {
NS_ADDREF(*aResultContent = currentContent);
return NS_OK;
}
}
}
}
@@ -2929,13 +3055,14 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
if (aCurrentTabIndex == (aForward ? 0 : 1)) {
// if going backwards, the canvas should be focused once the beginning
// has been reached.
// has been reached, so get the root element.
if (!aForward) {
nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
NS_IF_ADDREF(*aResultContent =
GetRootForFocus(window, aRootContent->GetComposedDoc(),
false, true));
nsCOMPtr<nsIContent> docRoot =
GetRootForFocus(window, aRootContent->GetComposedDoc(), false, true);
FocusFirst(docRoot, aResultContent);
}
break;
}
@@ -3041,26 +3168,41 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent,
return tabIndex;
}
nsresult
nsFocusManager::FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent)
{
if (!aRootContent) {
return NS_OK;
}
nsIDocument* doc = aRootContent->GetComposedDoc();
if (doc) {
nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
// If the found content is in a chrome shell, navigate forward one
// tabbable item so that the first item is focused. Note that we
// always go forward and not back here.
nsIPresShell* presShell = doc->GetShell();
if (presShell) {
return GetNextTabbableContent(presShell, aRootContent,
nullptr, aRootContent,
true, 1, false, false,
aNextContent);
}
}
}
NS_ADDREF(*aNextContent = aRootContent);
return NS_OK;
}
nsIContent*
nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
nsIDocument* aDocument,
bool aIsForDocNavigation,
bool aForDocumentNavigation,
bool aCheckVisibility)
{
// the root element's canvas may be focused as long as the document is in a
// a non-chrome shell and does not contain a frameset.
if (aIsForDocNavigation) {
nsCOMPtr<Element> docElement = aWindow->GetFrameElementInternal();
// document navigation skips iframes and frames that are specifically non-focusable
if (docElement) {
if (docElement->NodeInfo()->NameAtom() == nsGkAtoms::iframe)
return nullptr;
nsIFrame* frame = docElement->GetPrimaryFrame();
if (!frame || !frame->IsFocusable(nullptr, 0))
return nullptr;
}
} else {
if (!aForDocumentNavigation) {
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
return nullptr;
@@ -3070,9 +3212,15 @@ nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
if (aCheckVisibility && !IsWindowVisible(aWindow))
return nullptr;
Element *rootElement = aDocument->GetRootElement();
if (!rootElement) {
return nullptr;
// If the body is contenteditable, use the editor's root element rather than
// the actual root element.
nsCOMPtr<nsIContent> rootElement =
nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
if (!rootElement || !rootElement->GetPrimaryFrame()) {
rootElement = aDocument->GetRootElement();
if (!rootElement) {
return nullptr;
}
}
if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
@@ -3081,291 +3229,43 @@ nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
// Finally, check if this is a frameset
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
if (htmlDoc && aDocument->GetHtmlChildElement(nsGkAtoms::frameset)) {
return nullptr;
if (htmlDoc) {
nsIContent* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
if (htmlChild) {
// In document navigation mode, return the frameset so that navigation
// descends into the child frames.
return aForDocumentNavigation ? htmlChild : nullptr;
}
}
return rootElement;
}
void
nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult)
{
*aResult = nullptr;
nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
while (curItem) {
int32_t childCount = 0;
curItem->GetChildCount(&childCount);
if (!childCount) {
curItem.forget(aResult);
return;
}
curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
}
}
void
nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult)
{
*aResult = nullptr;
int32_t childCount = 0;
aItem->GetChildCount(&childCount);
if (childCount) {
aItem->GetChildAt(0, aResult);
if (*aResult)
return;
}
nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
while (curItem) {
nsCOMPtr<nsIDocShellTreeItem> parentItem;
curItem->GetParent(getter_AddRefs(parentItem));
if (!parentItem)
return;
// Note that we avoid using GetChildOffset() here because docshell
// child offsets can't be trusted to be correct. bug 162283.
nsCOMPtr<nsIDocShellTreeItem> iterItem;
childCount = 0;
parentItem->GetChildCount(&childCount);
for (int32_t index = 0; index < childCount; ++index) {
parentItem->GetChildAt(index, getter_AddRefs(iterItem));
if (iterItem == curItem) {
++index;
if (index < childCount) {
parentItem->GetChildAt(index, aResult);
if (*aResult)
return;
}
break;
}
}
curItem = parentItem;
}
}
void
nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult)
{
*aResult = nullptr;
nsCOMPtr<nsIDocShellTreeItem> parentItem;
aItem->GetParent(getter_AddRefs(parentItem));
if (!parentItem)
return;
// Note that we avoid using GetChildOffset() here because docshell
// child offsets can't be trusted to be correct. bug 162283.
int32_t childCount = 0;
parentItem->GetChildCount(&childCount);
nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
for (int32_t index = 0; index < childCount; ++index) {
parentItem->GetChildAt(index, getter_AddRefs(iterItem));
if (iterItem == aItem)
break;
prevItem = iterItem;
}
if (prevItem)
GetLastDocShell(prevItem, aResult);
else
parentItem.forget(aResult);
}
nsIContent*
nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward)
nsFocusManager::GetRootForChildDocument(nsIContent* aContent)
{
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (!pm)
// Check for elements that represent child documents, that is, browsers,
// editors or frames from a frameset. We don't include iframes since we
// consider them to be an integral part of the same window or page.
if (!aContent ||
!(aContent->IsXULElement(nsGkAtoms::browser) ||
aContent->IsXULElement(nsGkAtoms::editor) ||
aContent->IsHTMLElement(nsGkAtoms::frame))) {
return nullptr;
// Iterate through the array backwards if aForward is false.
nsTArray<nsIFrame *> popups;
pm->GetVisiblePopups(popups);
int32_t i = aForward ? 0 : popups.Length() - 1;
int32_t end = aForward ? popups.Length() : -1;
for (; i != end; aForward ? i++ : i--) {
nsIFrame* popupFrame = popups[i];
if (aCurrentPopup) {
// If the current popup is set, then we need to skip over this popup and
// wait until the currently focused popup is found. Once found, the
// current popup will be cleared so that the next popup is used.
if (aCurrentPopup == popupFrame)
aCurrentPopup = nullptr;
continue;
}
// Skip over non-panels
if (!popupFrame->GetContent()->IsXULElement(nsGkAtoms::panel) ||
(aDocument && popupFrame->GetContent()->GetComposedDoc() != aDocument)) {
continue;
}
// Find the first focusable content within the popup. If there isn't any
// focusable content in the popup, skip to the next popup.
nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell();
if (presShell) {
nsCOMPtr<nsIContent> nextFocus;
nsIContent* popup = popupFrame->GetContent();
nsresult rv = GetNextTabbableContent(presShell, popup,
nullptr, popup,
true, 1, false,
getter_AddRefs(nextFocus));
if (NS_SUCCEEDED(rv) && nextFocus) {
return nextFocus.get();
}
}
}
return nullptr;
}
nsIContent*
nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward)
{
// If currentPopup is set, then the starting content is in a panel.
nsIFrame* currentPopup = nullptr;
nsCOMPtr<nsIDocument> doc;
nsCOMPtr<nsIDocShell> startDocShell;
if (aStartContent) {
doc = aStartContent->GetComposedDoc();
if (doc) {
startDocShell = doc->GetWindow()->GetDocShell();
}
// Check if the starting content is inside a panel. Document navigation
// must start from this panel instead of the document root.
nsIContent* content = aStartContent;
while (content) {
if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) {
currentPopup = content->GetPrimaryFrame();
break;
}
content = content->GetParent();
}
}
else if (mFocusedWindow) {
startDocShell = mFocusedWindow->GetDocShell();
doc = mFocusedWindow->GetExtantDoc();
} else if (mActiveWindow) {
startDocShell = mActiveWindow->GetDocShell();
doc = mActiveWindow->GetExtantDoc();
}
if (!startDocShell)
nsIDocument* doc = aContent->GetComposedDoc();
if (!doc) {
return nullptr;
}
// perform a depth first search (preorder) of the docshell tree
// looking for an HTML Frame or a chrome document
nsIContent* content = aStartContent;
nsCOMPtr<nsIDocShellTreeItem> curItem = startDocShell.get();
nsCOMPtr<nsIDocShellTreeItem> nextItem;
do {
// If moving forward, check for a panel in the starting document. If one
// exists with focusable content, return that content instead of the next
// document. If currentPopup is set, then, another panel may exist. If no
// such panel exists, then continue on to check the next document.
// When moving backwards, and the starting content is in a panel, then
// check for additional panels in the starting document. If the starting
// content is not in a panel, move back to the previous document and check
// for panels there.
nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
if (!subdoc || subdoc->EventHandlingSuppressed()) {
return nullptr;
}
bool checkPopups = false;
nsCOMPtr<nsPIDOMWindow> nextFrame = nullptr;
if (doc && (aForward || currentPopup)) {
nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward);
if (popupContent)
return popupContent;
if (!aForward && currentPopup) {
// The starting content was in a popup, yet no other popups were
// found. Move onto the starting content's document.
nextFrame = doc->GetWindow();
}
}
// Look for the next or previous document.
if (!nextFrame) {
if (aForward) {
GetNextDocShell(curItem, getter_AddRefs(nextItem));
if (!nextItem) {
// wrap around to the beginning, which is the top of the tree
startDocShell->GetRootTreeItem(getter_AddRefs(nextItem));
}
}
else {
GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
if (!nextItem) {
// wrap around to the end, which is the last item in the tree
nsCOMPtr<nsIDocShellTreeItem> rootItem;
startDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
GetLastDocShell(rootItem, getter_AddRefs(nextItem));
}
// When going back to the previous document, check for any focusable
// popups in that previous document first.
checkPopups = true;
}
curItem = nextItem;
nextFrame = nextItem ? nextItem->GetWindow() : nullptr;
}
if (!nextFrame)
return nullptr;
// Clear currentPopup for the next iteration
currentPopup = nullptr;
// If event handling is suppressed, move on to the next document. Set
// content to null so that the popup check will be skipped on the next
// loop iteration.
doc = nextFrame->GetExtantDoc();
if (!doc || doc->EventHandlingSuppressed()) {
content = nullptr;
continue;
}
if (checkPopups) {
// When iterating backwards, check the panels of the previous document
// first. If a panel exists that has focusable content, focus that.
// Otherwise, continue on to focus the document.
nsIContent* popupContent = GetNextTabbablePanel(doc, nullptr, false);
if (popupContent)
return popupContent;
}
content = GetRootForFocus(nextFrame, doc, true, true);
if (content && !GetRootForFocus(nextFrame, doc, false, false)) {
// if the found content is in a chrome shell or a frameset, navigate
// forward one tabbable item so that the first item is focused. Note
// that we always go forward and not back here.
nsCOMPtr<nsIContent> nextFocus;
Element* rootElement = doc->GetRootElement();
nsIPresShell* presShell = doc->GetShell();
if (presShell) {
nsresult rv = GetNextTabbableContent(presShell, rootElement,
nullptr, rootElement,
true, 1, false,
getter_AddRefs(nextFocus));
return NS_SUCCEEDED(rv) ? nextFocus.get() : nullptr;
}
}
} while (!content);
return content;
nsCOMPtr<nsPIDOMWindow> window = subdoc->GetWindow();
return GetRootForFocus(window, subdoc, true, true);
}
void
+19 -45
View File
@@ -25,6 +25,12 @@ class nsIDocShellTreeItem;
class nsPIDOMWindow;
class nsIMessageBroadcaster;
namespace mozilla {
namespace dom {
class TabParent;
}
}
struct nsDelayedBlurOrFocusEvent;
/**
@@ -387,6 +393,7 @@ protected:
bool aForward,
int32_t aCurrentTabIndex,
bool aIgnoreTabIndex,
bool aForDocumentNavigation,
nsIContent** aResultContent);
/**
@@ -415,65 +422,32 @@ protected:
int32_t aCurrentTabIndex,
bool aForward);
/**
* Focus the first focusable content within the document with a root node of
* aRootContent. For content documents, this will be aRootContent itself, but
* for chrome documents, this will locate the next focusable content.
*/
nsresult FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent);
/**
* Retrieves and returns the root node from aDocument to be focused. Will
* return null if the root node cannot be focused. There are several reasons
* for this:
*
* - if aIsForDocNavigation is true, and aWindow is in an <iframe>.
* - if aIsForDocNavigation is false, and aWindow is a chrome shell.
* - if aForDocumentNavigation is false and aWindow is a chrome shell.
* - if aCheckVisibility is true and the aWindow is not visible.
* - if aDocument is a frameset document.
*/
nsIContent* GetRootForFocus(nsPIDOMWindow* aWindow,
nsIDocument* aDocument,
bool aIsForDocNavigation,
bool aForDocumentNavigation,
bool aCheckVisibility);
/**
* Get the last docshell child of aItem and return it in aResult.
* Retrieves and returns the root node as with GetRootForFocus but only if
* aContent is a frame with a valid child document.
*/
void GetLastDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult);
/**
* Get the next docshell child of aItem and return it in aResult.
*/
void GetNextDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult);
/**
* Get the previous docshell child of aItem and return it in aResult.
*/
void GetPreviousDocShell(nsIDocShellTreeItem* aItem,
nsIDocShellTreeItem** aResult);
/**
* Determine the first panel with focusable content in document tab order
* from the given document. aForward indicates the direction to scan. If
* aCurrentPopup is set to a panel, the next or previous popup after
* aCurrentPopup after it is used. If aCurrentPopup is null, then the first
* or last popup is used. If a panel has no focusable content, it is skipped.
* Null is returned if no panel is open or no open panel contains a focusable
* element.
*/
nsIContent* GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward);
/**
* Get the tabbable next document from aStartContent or, if null, the
* currently focused frame if aForward is true, or the previously tabbable
* document if aForward is false. If this document is a chrome or frameset
* document, returns the first focusable element within this document,
* otherwise, returns the root node of the document.
*
*
* Panels with focusable content are also placed in the cycling order, just
* after the document containing that panel.
*
* This method would be used for document navigation, which is typically
* invoked by pressing F6.
*/
nsIContent* GetNextTabbableDocument(nsIContent* aStartContent, bool aForward);
nsIContent* GetRootForChildDocument(nsIContent* aContent);
/**
* Retreives a focusable element within the current selection of aWindow.
-120
View File
@@ -61,7 +61,6 @@ nsLocation::nsLocation(nsPIDOMWindow* aWindow, nsIDocShell *aDocShell)
nsLocation::~nsLocation()
{
RemoveURLSearchParams();
}
// QueryInterface implementation for nsLocation
@@ -74,14 +73,11 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(nsLocation)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsLocation)
tmp->RemoveURLSearchParams();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInnerWindow);
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsLocation)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearchParams)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInnerWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -1088,119 +1084,3 @@ nsLocation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return LocationBinding::Wrap(aCx, this, aGivenProto);
}
URLSearchParams*
nsLocation::GetDocShellSearchParams()
{
nsCOMPtr<nsIDocShell> docShell = GetDocShell();
if (!docShell) {
return nullptr;
}
return docShell->GetURLSearchParams();
}
URLSearchParams*
nsLocation::SearchParams()
{
if (!mSearchParams) {
// We must register this object to the URLSearchParams of the docshell in
// order to receive updates.
nsRefPtr<URLSearchParams> searchParams = GetDocShellSearchParams();
if (searchParams) {
searchParams->AddObserver(this);
}
mSearchParams = new URLSearchParams();
mSearchParams->AddObserver(this);
UpdateURLSearchParams();
}
return mSearchParams;
}
void
nsLocation::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);
// We don't need to inform the docShell about this new SearchParams because
// setting the new value the docShell will refresh its value automatically.
}
void
nsLocation::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
{
MOZ_ASSERT(mSearchParams);
// This change comes from content.
if (aSearchParams == mSearchParams) {
nsAutoString search;
mSearchParams->Serialize(search);
SetSearchInternal(search);
return;
}
// This change comes from the docShell.
#ifdef DEBUG
{
nsRefPtr<URLSearchParams> searchParams = GetDocShellSearchParams();
MOZ_ASSERT(searchParams);
MOZ_ASSERT(aSearchParams == searchParams);
}
#endif
nsAutoString search;
aSearchParams->Serialize(search);
mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(search), this);
}
void
nsLocation::UpdateURLSearchParams()
{
if (!mSearchParams) {
return;
}
nsAutoCString search;
nsCOMPtr<nsIURI> uri;
nsresult rv = GetURI(getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!uri)) {
return;
}
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
if (url) {
nsresult rv = url->GetQuery(search);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to get the query from a nsIURL.");
}
}
mSearchParams->ParseInput(search, this);
}
void
nsLocation::RemoveURLSearchParams()
{
if (mSearchParams) {
mSearchParams->RemoveObserver(this);
mSearchParams = nullptr;
nsRefPtr<URLSearchParams> docShellSearchParams = GetDocShellSearchParams();
if (docShellSearchParams) {
docShellSearchParams->RemoveObserver(this);
}
}
}
-15
View File
@@ -14,7 +14,6 @@
#include "nsCycleCollectionParticipant.h"
#include "js/TypeDecls.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/URLSearchParams.h"
#include "nsPIDOMWindow.h"
class nsIURI;
@@ -27,7 +26,6 @@ class nsIDocShellLoadInfo;
class nsLocation final : public nsIDOMLocation
, public nsWrapperCache
, public mozilla::dom::URLSearchParamsObserver
{
typedef mozilla::ErrorResult ErrorResult;
@@ -122,10 +120,6 @@ public:
aError = SetSearch(aSeach);
}
mozilla::dom::URLSearchParams* SearchParams();
void SetSearchParams(mozilla::dom::URLSearchParams& aSearchParams);
void GetHash(nsAString& aHash, ErrorResult& aError)
{
aError = GetHash(aHash);
@@ -144,17 +138,10 @@ public:
}
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
// URLSearchParamsObserver
void URLSearchParamsUpdated(mozilla::dom::URLSearchParams* aSearchParams) override;
protected:
virtual ~nsLocation();
nsresult SetSearchInternal(const nsAString& aSearch);
void UpdateURLSearchParams();
void RemoveURLSearchParams();
mozilla::dom::URLSearchParams* GetDocShellSearchParams();
// In the case of jar: uris, we sometimes want the place the jar was
// fetched from as the URI instead of the jar: uri itself. Pass in
@@ -173,9 +160,7 @@ protected:
nsString mCachedHash;
nsCOMPtr<nsPIDOMWindow> mInnerWindow;
nsRefPtr<mozilla::dom::URLSearchParams> mSearchParams;
nsWeakPtr mDocShell;
};
#endif // nsLocation_h__
+1 -1
View File
@@ -596,7 +596,7 @@ CreateInterfaceObject(JSContext* cx, JS::Handle<JSObject*> global,
// Might as well intern, since we're going to need an atomized
// version of name anyway when we stick our constructor on the
// global.
JS::Rooted<JSString*> nameStr(cx, JS_InternString(cx, name));
JS::Rooted<JSString*> nameStr(cx, JS_AtomizeAndPinString(cx, name));
if (!nameStr) {
return nullptr;
}
+30 -30
View File
@@ -1726,10 +1726,10 @@ GetCallbackFromCallbackObject(T& aObj)
}
static inline bool
InternJSString(JSContext* cx, jsid& jid, const char* chars)
AtomizeAndPinJSString(JSContext* cx, jsid& id, const char* chars)
{
if (JSString *str = ::JS_InternString(cx, chars)) {
jid = INTERNED_STRING_TO_JSID(cx, str);
if (JSString *str = ::JS_AtomizeAndPinString(cx, chars)) {
id = INTERNED_STRING_TO_JSID(cx, str);
return true;
}
return false;
@@ -2374,25 +2374,9 @@ inline bool
AddStringToIDVector(JSContext* cx, JS::AutoIdVector& vector, const char* name)
{
return vector.growBy(1) &&
InternJSString(cx, *(vector[vector.length() - 1]).address(), name);
AtomizeAndPinJSString(cx, *(vector[vector.length() - 1]).address(), name);
}
// We use one constructor JSNative to represent all DOM interface objects (so
// we can easily detect when we need to wrap them in an Xray wrapper). We store
// the real JSNative in the mNative member of a JSNativeHolder in the
// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
// specific interface object. We also store the NativeProperties in the
// JSNativeHolder.
// Note that some interface objects are not yet a JSFunction but a normal
// JSObject with a DOMJSClass, those do not use these slots.
enum {
CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0
};
bool
Constructor(JSContext* cx, unsigned argc, JS::Value* vp);
// Implementation of the bits that XrayWrapper needs
/**
@@ -2483,6 +2467,22 @@ XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
extern NativePropertyHooks sEmptyNativePropertyHooks;
// We use one constructor JSNative to represent all DOM interface objects (so
// we can easily detect when we need to wrap them in an Xray wrapper). We store
// the real JSNative in the mNative member of a JSNativeHolder in the
// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
// specific interface object. We also store the NativeProperties in the
// JSNativeHolder.
// Note that some interface objects are not yet a JSFunction but a normal
// JSObject with a DOMJSClass, those do not use these slots.
enum {
CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0
};
bool
Constructor(JSContext* cx, unsigned argc, JS::Value* vp);
inline bool
UseDOMXray(JSObject* obj)
{
@@ -3065,31 +3065,31 @@ CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
}
/*
* Holds a jsid that is initialized to an interned string, with conversion to
* Handle<jsid>.
* Holds a jsid that is initialized to a pinned string, with automatic
* conversion to Handle<jsid>, as it is held live forever by pinning.
*/
class InternedStringId
class PinnedStringId
{
jsid jid;
jsid id;
public:
InternedStringId() : jid(JSID_VOID) {}
PinnedStringId() : id(JSID_VOID) {}
bool init(JSContext *cx, const char *string) {
JSString* str = JS_InternString(cx, string);
JSString* str = JS_AtomizeAndPinString(cx, string);
if (!str)
return false;
jid = INTERNED_STRING_TO_JSID(cx, str);
id = INTERNED_STRING_TO_JSID(cx, str);
return true;
}
operator const jsid& () {
return jid;
return id;
}
operator JS::Handle<jsid> () {
/* This is safe because we have interned the string. */
return JS::Handle<jsid>::fromMarkedLocation(&jid);
/* This is safe because we have pinned the string. */
return JS::Handle<jsid>::fromMarkedLocation(&id);
}
};
+4 -4
View File
@@ -12236,7 +12236,7 @@ class CGResolveSystemBinding(CGAbstractMethod):
jsidInits = CGList(
(CGIfWrapper(
CGGeneric("return false;\n"),
'!InternJSString(aCx, %s, "%s")' %
'!AtomizeAndPinJSString(aCx, %s, "%s")' %
(descNameToId(desc.name), desc.interface.identifier.name))
for desc in descriptors),
"\n")
@@ -14576,7 +14576,7 @@ class GlobalGenRoots():
def memberToAtomCacheMember(binaryNameFor, m):
binaryMemberName = binaryNameFor(m.identifier.name)
return ClassMember(CGDictionary.makeIdName(binaryMemberName),
"InternedStringId", visibility="public")
"PinnedStringId", visibility="public")
def buildAtomCacheStructure(idlobj, binaryNameFor, members):
classMembers = [memberToAtomCacheMember(binaryNameFor, m)
for m in members]
@@ -14622,7 +14622,7 @@ class GlobalGenRoots():
CGWrapper(structs, pre='\n'))
curr = CGWrapper(curr, post='\n')
# Add include statement for InternedStringId.
# Add include statement for PinnedStringId.
declareIncludes = ['mozilla/dom/BindingUtils.h']
curr = CGHeaders([], [], [], [], declareIncludes, [], 'GeneratedAtomList',
curr)
@@ -14799,7 +14799,7 @@ class GlobalGenRoots():
skipGen=False)]
defineIncludes.append("nsThreadUtils.h") # For NS_IsMainThread
defineIncludes.append("js/Id.h") # For jsid
defineIncludes.append("mozilla/dom/BindingUtils.h") # InternJSString
defineIncludes.append("mozilla/dom/BindingUtils.h") # AtomizeAndPinJSString
curr = CGHeaders([], [], [], [], [], defineIncludes,
'ResolveSystemBinding', curr)
+1 -1
View File
@@ -25,7 +25,7 @@ jsid s_length_id = JSID_VOID;
bool
DefineStaticJSVals(JSContext* cx)
{
return InternJSString(cx, s_length_id, "length");
return AtomizeAndPinJSString(cx, s_length_id, "length");
}
const char DOMProxyHandler::family = 0;
+8 -2
View File
@@ -8,7 +8,7 @@
interface nsIDocument;
interface nsIContent;
[scriptable, uuid(c0716002-5602-4002-a0de-cc69b924b2c6)]
[scriptable, uuid(2487F9CA-D05F-4BD1-8F43-5964E746C482)]
/**
* The focus manager deals with all focus related behaviour. Only one element
* in the entire application may have the focus at a time; this element
@@ -97,7 +97,8 @@ interface nsIFocusManager : nsISupports
* MOVEFOCUS_CARET, then the focus is cleared. If aType is any other value,
* the focus is not changed.
*
* Returns the element that was focused.
* Returns the element that was focused. The return value may be null if focus
* was moved into a child process.
*/
nsIDOMElement moveFocus(in nsIDOMWindow aWindow, in nsIDOMElement aStartElement,
in unsigned long aType, in unsigned long aFlags);
@@ -215,6 +216,11 @@ interface nsIFocusManager : nsISupports
*/
const unsigned long MOVEFOCUS_CARET = 8;
/** move focus to the first focusable document */
const unsigned long MOVEFOCUS_FIRSTDOC = 9;
/** move focus to the last focusable document */
const unsigned long MOVEFOCUS_LASTDOC = 10;
/**
* Called when a window has been raised.
*/
+7 -1
View File
@@ -5,7 +5,7 @@
#include "domstubs.idl"
[scriptable, uuid(4d4576eb-ecfe-47b9-93b8-4121518621ad)]
[scriptable, uuid(A10D887D-7FC2-48A2-ABC6-A2027423860C)]
interface nsITabParent : nsISupports
{
void injectTouchEvent(in AString aType,
@@ -27,5 +27,11 @@ interface nsITabParent : nsISupports
readonly attribute uint64_t tabId;
/**
* If aForward is true, navigate to the first focusable document.
* If aForward is false, navigate to the last focusable document.
*/
void navigateDocument(in bool aForward);
readonly attribute boolean hasContentOpener;
};
+7 -2
View File
@@ -142,9 +142,9 @@ parent:
parent:
/**
* When child sends this message, parent should move focus to
* the next or previous focusable element.
* the next or previous focusable element or document.
*/
MoveFocus(bool forward);
MoveFocus(bool forward, bool forDocumentNavigation);
Event(RemoteDOMEvent aEvent);
@@ -696,6 +696,11 @@ child:
*/
SetIsDocShellActive(bool aIsActive);
/**
* Navigate by document.
*/
NavigateDocument(bool aForward);
/**
* The parent (chrome thread) requests that the child inform it when
* the graphics objects are ready to display.
+22 -4
View File
@@ -1371,16 +1371,16 @@ TabChild::Blur()
}
NS_IMETHODIMP
TabChild::FocusNextElement()
TabChild::FocusNextElement(bool aForDocumentNavigation)
{
SendMoveFocus(true);
SendMoveFocus(true, aForDocumentNavigation);
return NS_OK;
}
NS_IMETHODIMP
TabChild::FocusPrevElement()
TabChild::FocusPrevElement(bool aForDocumentNavigation)
{
SendMoveFocus(false);
SendMoveFocus(false, aForDocumentNavigation);
return NS_OK;
}
@@ -2826,6 +2826,24 @@ TabChild::RecvSetIsDocShellActive(const bool& aIsActive)
return true;
}
bool
TabChild::RecvNavigateDocument(const bool& aForward)
{
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
nsCOMPtr<nsIDOMElement> result;
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(WebNavigation());
// Move to the first or last document.
fm->MoveFocus(window, nullptr, aForward ? nsIFocusManager::MOVEFOCUS_FIRSTDOC :
nsIFocusManager::MOVEFOCUS_LASTDOC,
nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
SendRequestFocus(false);
}
return true;
}
PRenderFrameChild*
TabChild::AllocPRenderFrameChild()
{
+1
View File
@@ -524,6 +524,7 @@ protected:
virtual bool RecvDestroy() override;
virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
virtual bool RecvSetIsDocShellActive(const bool& aIsActive) override;
virtual bool RecvNavigateDocument(const bool& aForward) override;
virtual bool RecvRequestNotifyAfterRemotePaint() override;
+14 -3
View File
@@ -499,13 +499,17 @@ TabParent::ActorDestroy(ActorDestroyReason why)
}
bool
TabParent::RecvMoveFocus(const bool& aForward)
TabParent::RecvMoveFocus(const bool& aForward, const bool& aForDocumentNavigation)
{
nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
if (fm) {
nsCOMPtr<nsIDOMElement> dummy;
uint32_t type = aForward ? uint32_t(nsIFocusManager::MOVEFOCUS_FORWARD)
: uint32_t(nsIFocusManager::MOVEFOCUS_BACKWARD);
uint32_t type = aForward ?
(aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARDDOC) :
static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD)) :
(aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARDDOC) :
static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARD));
nsCOMPtr<nsIDOMElement> frame = do_QueryInterface(mFrameElement);
fm->MoveFocus(nullptr, frame, type, nsIFocusManager::FLAG_BYKEY,
getter_AddRefs(dummy));
@@ -3038,6 +3042,13 @@ TabParent::SetHasContentOpener(bool aHasContentOpener)
mHasContentOpener = aHasContentOpener;
}
NS_IMETHODIMP
TabParent::NavigateDocument(bool aForward)
{
unused << SendNavigateDocument(aForward);
return NS_OK;
}
class LayerTreeUpdateRunnable final
: public nsRunnable
{
+2 -1
View File
@@ -125,7 +125,8 @@ public:
void AddWindowListeners();
void DidRefresh() override;
virtual bool RecvMoveFocus(const bool& aForward) override;
virtual bool RecvMoveFocus(const bool& aForward,
const bool& aForDocumentNavigation) override;
virtual bool RecvEvent(const RemoteDOMEvent& aEvent) override;
virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) override;
virtual bool RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) override;
+2 -1
View File
@@ -1078,7 +1078,7 @@ void AudioCallbackDriver::CompleteAudioContextOperations(AsyncCubebOperation aOp
array.SwapElements(mPromisesForOperation);
}
for (int32_t i = array.Length() - 1; i >= 0; i--) {
for (uint32_t i = 0; i < array.Length(); i++) {
StreamAndPromiseForOperation& s = array[i];
if ((aOperation == AsyncCubebOperation::INIT &&
s.mOperation == dom::AudioContextOperation::Resume) ||
@@ -1089,6 +1089,7 @@ void AudioCallbackDriver::CompleteAudioContextOperations(AsyncCubebOperation aOp
s.mPromise,
s.mOperation);
array.RemoveElementAt(i);
i--;
}
}
+3
View File
@@ -0,0 +1,3 @@
<script>
(new AudioContext).close();
</script>
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<script>
function boom()
{
var ac = new AudioContext();
// first "suspended" -> "running" transition
ac.onstatechange = function() {
ac.onstatechange = null;
ac.suspend();
ac.close();
}
}
</script>
</head>
<body onload="boom();"></body>
</html>
@@ -75,7 +75,10 @@ load offline-buffer-source-ended-1.html
load oscillator-ended-1.html
load oscillator-ended-2.html
load 1080986.html
load 1158427.html
load 1157994.html
load 1122218.html
load 1127188.html
load 1158427.html
load audiocontext-double-suspend.html
include ../../mediasource/test/crashtests/crashtests.list
+12 -2
View File
@@ -761,13 +761,21 @@ private:
nsRefPtr<AudioContext> mAudioContext;
};
void
AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
{
MOZ_ASSERT(NS_IsMainThread());
// This can happen if close() was called right after creating the
// AudioContext, before the context has switched to "running".
if (mAudioContextState == AudioContextState::Closed &&
aNewState == AudioContextState::Running &&
!aPromise) {
return;
}
#ifndef WIN32 // Bug 1170547
MOZ_ASSERT((mAudioContextState == AudioContextState::Suspended &&
aNewState == AudioContextState::Running) ||
(mAudioContextState == AudioContextState::Running &&
@@ -779,6 +787,8 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
(mAudioContextState == aNewState),
"Invalid AudioContextState transition");
#endif // WIN32
MOZ_ASSERT(
mIsOffline || aPromise || aNewState == AudioContextState::Running,
"We should have a promise here if this is a real-time AudioContext."
+3 -3
View File
@@ -1055,7 +1055,7 @@ nsJSObjWrapper::NP_Enumerate(NPObject* npobj, NPIdentifier** idarray,
NPIdentifier id;
if (v.isString()) {
JS::Rooted<JSString*> str(cx, v.toString());
str = JS_InternJSString(cx, str);
str = JS_AtomizeAndPinJSString(cx, str);
if (!str) {
PR_Free(*idarray);
return false;
@@ -1556,9 +1556,9 @@ CallNPMethodInternal(JSContext* cx, JS::Handle<JSObject*> obj, unsigned argc,
// the function object.
if (npobj->_class->invoke) {
JSFunction* fun = ::JS_GetObjectFunction(funobj);
JSFunction *fun = ::JS_GetObjectFunction(funobj);
JS::Rooted<JSString*> funId(cx, ::JS_GetFunctionId(fun));
JSString* name = ::JS_InternJSString(cx, funId);
JSString *name = ::JS_AtomizeAndPinJSString(cx, funId);
NPIdentifier id = StringToNPIdentifier(cx, name);
ok = npobj->_class->invoke(npobj, id, npargs, argc, &v);
+1 -1
View File
@@ -680,7 +680,7 @@ doGetIdentifier(JSContext *cx, const NPUTF8* name)
{
NS_ConvertUTF8toUTF16 utf16name(name);
JSString *str = ::JS_InternUCStringN(cx, utf16name.get(), utf16name.Length());
JSString *str = ::JS_AtomizeAndPinUCStringN(cx, utf16name.get(), utf16name.Length());
if (!str)
return nullptr;
+1 -1
View File
@@ -132,7 +132,7 @@ inline bool
NPStringIdentifierIsPermanent(NPIdentifier id)
{
AutoSafeJSContext cx;
return JS_StringHasBeenInterned(cx, NPIdentifierToString(id));
return JS_StringHasBeenPinned(cx, NPIdentifierToString(id));
}
#define NPIdentifier_VOID (JSIdToNPIdentifier(JSID_VOID))
@@ -21,7 +21,7 @@ using namespace mozilla::plugins::parent;
/**
* NPIdentifiers in the chrome process are stored as jsids. The difficulty is in
* ensuring that string identifiers are rooted without interning them all. We
* ensuring that string identifiers are rooted without pinning them all. We
* assume that all NPIdentifiers passed into nsJSNPRuntime will not be used
* outside the scope of the NPAPI call (i.e., they won't be stored in the
* heap). Rooting is done using the StackIdentifier class, which roots the
@@ -31,7 +31,7 @@ using namespace mozilla::plugins::parent;
* generational or compacting GC. When Firefox implements a moving GC for
* strings, we will need to ensure that no movement happens while NPAPI code is
* on the stack: although StackIdentifier roots all identifiers used, the GC has
* no way to no that a jsid cast to an NPIdentifier needs to be fixed up if it
* no way to know that a jsid cast to an NPIdentifier needs to be fixed up if it
* is moved.
*/
@@ -39,7 +39,7 @@ class MOZ_STACK_CLASS StackIdentifier
{
public:
explicit StackIdentifier(const PluginIdentifier& aIdentifier,
bool aIntern = false);
bool aAtomizeAndPin = false);
bool Failed() const { return mFailed; }
NPIdentifier ToNPIdentifier() const { return mIdentifier; }
@@ -51,7 +51,7 @@ private:
JS::RootedId mId;
};
StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, bool aIntern)
StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, bool aAtomizeAndPin)
: mFailed(false),
mId(mCx)
{
@@ -64,8 +64,8 @@ StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, bool aInte
mFailed = true;
return;
}
if (aIntern) {
str = JS_InternJSString(mCx, str);
if (aAtomizeAndPin) {
str = JS_AtomizeAndPinJSString(mCx, str);
if (!str) {
NS_ERROR("Id can't be allocated");
mFailed = true;
@@ -500,9 +500,9 @@ PluginScriptableObjectParent::ScriptableEnumerate(NPObject* aObject,
}
for (uint32_t index = 0; index < *aCount; index++) {
// We intern the ID to avoid a GC hazard here. This could probably be fixed
// We pin the ID to avoid a GC hazard here. This could probably be fixed
// if the interface with nsJSNPRuntime were smarter.
StackIdentifier stackID(identifiers[index], true /* aIntern */);
StackIdentifier stackID(identifiers[index], true /* aAtomizeAndPin */);
if (stackID.Failed()) {
return false;
}
-1
View File
@@ -21,6 +21,5 @@ interface Location {
[Throws, UnsafeInPrerendering]
void reload(optional boolean forceget = false);
};
// No support for .searchParams on Location yet. See bug 1082734.
Location implements URLUtils;
+1 -1
View File
@@ -15,7 +15,7 @@ dictionary ProfileTimelineStackFrame {
DOMString functionDisplayName;
object? parent = null;
object? asyncParent = null;
object? asyncCause = null;
DOMString asyncCause;
};
dictionary ProfileTimelineLayerRect {
+3 -3
View File
@@ -12,9 +12,9 @@
* attributes.
*
* IMPORTANT: If you add any members here, you need to update the
* CreateSuffix, Serialize, and Deserialize implementations in BasePrincipal,
* and bump the CIDs of all the principal implementations that invoke those
* methods.
* methods on mozilla::OriginAttributes, and bump the CIDs of all
* the principal implementations that use OriginAttributes in their
* nsISerializable implementations.
*/
dictionary OriginAttributesDictionary {
unsigned long appId = 0;
@@ -12,19 +12,21 @@
* to the embedding chrome. See mozilla bug #70224 for gratuitous info.
*/
[scriptable, uuid(d2206418-1dd1-11b2-8e55-acddcd2bcfb8)]
[scriptable, uuid(947B2EE6-51ED-4C2B-9F45-426C27CA84C6)]
interface nsIWebBrowserChromeFocus : nsISupports
{
/**
* Set the focus at the next focusable element in the chrome.
* Set the focus at the next focusable element in the chrome. If
* aForDocumentNavigation is true, this was a document navigation, so
* focus the parent window.
*/
void focusNextElement();
void focusNextElement(in bool aForDocumentNavigation);
/**
* Set the focus at the previous focusable element in the chrome.
*/
void focusPrevElement();
void focusPrevElement(in bool aForDocumentNavigation);
};
+6 -2
View File
@@ -358,12 +358,16 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(AutoEntryMonitor) {
// We have begun executing |function|. Note that |function| may not be the
// actual closure we are running, but only the canonical function object to
// which the script refers.
virtual void Entry(JSContext* cx, JSFunction* function) = 0;
virtual void Entry(JSContext* cx, JSFunction* function,
HandleValue asyncStack,
HandleString asyncCause) = 0;
// Execution has begun at the entry point of |script|, which is not a
// function body. (This is probably being executed by 'eval' or some
// JSAPI equivalent.)
virtual void Entry(JSContext* cx, JSScript* script) = 0;
virtual void Entry(JSContext* cx, JSScript* script,
HandleValue asyncStack,
HandleString asyncCause) = 0;
// Execution of the function or script has ended.
virtual void Exit(JSContext* cx) { }
+1 -1
View File
@@ -784,7 +784,7 @@ HashableValue::setValue(JSContext* cx, HandleValue v)
{
if (v.isString()) {
// Atomize so that hash() and operator==() are fast and infallible.
JSString* str = AtomizeString(cx, v.toString(), DoNotInternAtom);
JSString* str = AtomizeString(cx, v.toString(), DoNotPinAtom);
if (!str)
return false;
value = StringValue(str);
+2 -2
View File
@@ -3,7 +3,7 @@
FRAGMENT(jsid, simple) {
JS::Rooted<JSString*> string(cx, JS_NewStringCopyZ(cx, "moon"));
JS::Rooted<JSString*> interned(cx, JS_InternJSString(cx, string));
JS::Rooted<JSString*> interned(cx, JS_AtomizeAndPinJSString(cx, string));
JS::Rooted<jsid> string_id(cx, INTERNED_STRING_TO_JSID(cx, interned));
jsid int_id = INT_TO_JSID(1729);
JS::Rooted<jsid> unique_symbol_id(
@@ -38,7 +38,7 @@ jsid_handles(JS::Handle<jsid> jsid_handle,
FRAGMENT(jsid, handles) {
JS::Rooted<JSString*> string(cx, JS_NewStringCopyZ(cx, "shovel"));
JS::Rooted<JSString*> interned(cx, JS_InternJSString(cx, string));
JS::Rooted<JSString*> interned(cx, JS_AtomizeAndPinJSString(cx, string));
JS::Rooted<jsid> string_id(cx, INTERNED_STRING_TO_JSID(cx, interned));
jsid_handles(string_id, &string_id);
}
@@ -0,0 +1,6 @@
// |jit-test| --no-baseline; --non-writable-jitcode
(function(stdlib, foreign, heap) {
"use asm";
function f() {}
return f;
})();
+22
View File
@@ -48,6 +48,28 @@ ExecutablePool::~ExecutablePool()
m_allocator->releasePoolPages(this);
}
/* static */ void
ExecutableAllocator::initStatic()
{
if (!pageSize) {
pageSize = determinePageSize();
// On Windows, VirtualAlloc effectively allocates in 64K chunks.
// (Technically, it allocates in page chunks, but the starting
// address is always a multiple of 64K, so each allocation uses up
// 64K of address space.) So a size less than that would be
// pointless. But it turns out that 64KB is a reasonable size for
// all platforms. (This assumes 4KB pages.) On 64-bit windows,
// AllocateExecutableMemory prepends an extra page for structured
// exception handling data (see comments in function) onto whatever
// is passed in, so subtract one page here.
#if defined(JS_CPU_X64) && defined(XP_WIN)
largeAllocSize = pageSize * 15;
#else
largeAllocSize = pageSize * 16;
#endif
}
}
void
ExecutableAllocator::addSizeOfCode(JS::CodeSizes* sizes) const
{
+2 -18
View File
@@ -182,24 +182,6 @@ class ExecutableAllocator
ExecutableAllocator()
: destroyCallback(nullptr)
{
if (!pageSize) {
pageSize = determinePageSize();
// On Windows, VirtualAlloc effectively allocates in 64K chunks.
// (Technically, it allocates in page chunks, but the starting
// address is always a multiple of 64K, so each allocation uses up
// 64K of address space.) So a size less than that would be
// pointless. But it turns out that 64KB is a reasonable size for
// all platforms. (This assumes 4KB pages.) On 64-bit windows,
// AllocateExecutableMemory prepends an extra page for structured
// exception handling data (see comments in function) onto whatever
// is passed in, so subtract one page here.
#if defined(JS_CPU_X64) && defined(XP_WIN)
largeAllocSize = pageSize * 15;
#else
largeAllocSize = pageSize * 16;
#endif
}
MOZ_ASSERT(m_smallPools.empty());
}
@@ -263,6 +245,8 @@ class ExecutableAllocator
this->destroyCallback = destroyCallback;
}
static void initStatic();
static bool nonWritableJitCode;
private:
-1
View File
@@ -12,7 +12,6 @@ UNIFIED_SOURCES += [
'testArgumentsObject.cpp',
'testArrayBuffer.cpp',
'testArrayBufferView.cpp',
'testBindCallable.cpp',
'testBug604087.cpp',
'testCallNonGenericMethodOnProxy.cpp',
'testChromeBuffer.cpp',
-32
View File
@@ -1,32 +0,0 @@
/* 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 "jsapi-tests/tests.h"
BEGIN_TEST(test_BindCallable)
{
JS::RootedValue v(cx);
EVAL("({ somename : 1717 })", &v);
CHECK(v.isObject());
JS::RootedValue func(cx);
EVAL("(function() { return this.somename; })", &func);
CHECK(func.isObject());
JS::RootedObject funcObj(cx, func.toObjectOrNull());
JS::RootedObject vObj(cx, v.toObjectOrNull());
JSObject* newCallable = JS_BindCallable(cx, funcObj, vObj);
CHECK(newCallable);
JS::RootedValue retval(cx);
JS::RootedValue fun(cx, JS::ObjectValue(*newCallable));
bool called = JS_CallFunctionValue(cx, nullptr, fun, JS::HandleValueArray::empty(), &retval);
CHECK(called);
CHECK(retval.isInt32());
CHECK(retval.toInt32() == 1717);
return true;
}
END_TEST(test_BindCallable)
+5 -5
View File
@@ -13,28 +13,28 @@ BEGIN_TEST(testIntString_bug515273)
EVAL("'1';", &v);
JSString* str = v.toString();
CHECK(JS_StringHasBeenInterned(cx, str));
CHECK(JS_StringHasBeenPinned(cx, str));
CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "1"));
EVAL("'42';", &v);
str = v.toString();
CHECK(JS_StringHasBeenInterned(cx, str));
CHECK(JS_StringHasBeenPinned(cx, str));
CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "42"));
EVAL("'111';", &v);
str = v.toString();
CHECK(JS_StringHasBeenInterned(cx, str));
CHECK(JS_StringHasBeenPinned(cx, str));
CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "111"));
/* Test other types of static strings. */
EVAL("'a';", &v);
str = v.toString();
CHECK(JS_StringHasBeenInterned(cx, str));
CHECK(JS_StringHasBeenPinned(cx, str));
CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "a"));
EVAL("'bc';", &v);
str = v.toString();
CHECK(JS_StringHasBeenInterned(cx, str));
CHECK(JS_StringHasBeenPinned(cx, str));
CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "bc"));
return true;
+8 -8
View File
@@ -10,17 +10,17 @@
using mozilla::ArrayLength;
BEGIN_TEST(testAtomizedIsNotInterned)
BEGIN_TEST(testAtomizedIsNotPinned)
{
/* Try to pick a string that won't be interned by other tests in this runtime. */
static const char someChars[] = "blah blah blah? blah blah blah";
JS::Rooted<JSAtom*> atom(cx, js::Atomize(cx, someChars, ArrayLength(someChars)));
CHECK(!JS_StringHasBeenInterned(cx, atom));
CHECK(JS_InternJSString(cx, atom));
CHECK(JS_StringHasBeenInterned(cx, atom));
CHECK(!JS_StringHasBeenPinned(cx, atom));
CHECK(JS_AtomizeAndPinJSString(cx, atom));
CHECK(JS_StringHasBeenPinned(cx, atom));
return true;
}
END_TEST(testAtomizedIsNotInterned)
END_TEST(testAtomizedIsNotPinned)
struct StringWrapperStruct
{
@@ -28,9 +28,9 @@ struct StringWrapperStruct
bool strOk;
} sw;
BEGIN_TEST(testInternAcrossGC)
BEGIN_TEST(testPinAcrossGC)
{
sw.str = JS_InternString(cx, "wrapped chars that another test shouldn't be using");
sw.str = JS_AtomizeAndPinString(cx, "wrapped chars that another test shouldn't be using");
sw.strOk = false;
CHECK(sw.str);
JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr);
@@ -45,4 +45,4 @@ FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status, bool isCompartmentGC, v
if (status == JSFINALIZE_GROUP_START)
sw.strOk = js::gc::IsMarkedUnbarriered(&sw.str);
}
END_TEST(testInternAcrossGC)
END_TEST(testPinAcrossGC)
+39 -27
View File
@@ -92,6 +92,7 @@
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/SavedStacks-inl.h"
#include "vm/String-inl.h"
using namespace js;
@@ -552,6 +553,8 @@ JS_Init(void)
if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
return false;
jit::ExecutableAllocator::initStatic();
if (!jit::InitializeIon())
return false;
@@ -988,7 +991,7 @@ JS_GetCompartmentPrivate(JSCompartment* compartment)
JS_PUBLIC_API(JSAddonId*)
JS::NewAddonId(JSContext* cx, HandleString str)
{
return static_cast<JSAddonId*>(JS_InternJSString(cx, str));
return static_cast<JSAddonId*>(JS_AtomizeAndPinJSString(cx, str));
}
JS_PUBLIC_API(JSString*)
@@ -2760,13 +2763,13 @@ PropertySpecNameToSymbolCode(const char* name)
static bool
PropertySpecNameToId(JSContext* cx, const char* name, MutableHandleId id,
js::InternBehavior ib = js::DoNotInternAtom)
js::PinningBehavior pin = js::DoNotPinAtom)
{
if (JS::PropertySpecNameIsSymbol(name)) {
JS::SymbolCode which = PropertySpecNameToSymbolCode(name);
id.set(SYMBOL_TO_JSID(cx->wellKnownSymbols().get(which)));
} else {
JSAtom* atom = Atomize(cx, name, strlen(name), ib);
JSAtom* atom = Atomize(cx, name, strlen(name), pin);
if (!atom)
return false;
id.set(AtomToId(atom));
@@ -2782,7 +2785,7 @@ JS::PropertySpecNameToPermanentId(JSContext* cx, const char* name, jsid* idp)
// of this API is to populate *idp with a jsid that does not need to be
// marked.
return PropertySpecNameToId(cx, name, MutableHandleId::fromMarkedLocation(idp),
js::InternAtom);
js::PinAtom);
}
JS_PUBLIC_API(bool)
@@ -3492,13 +3495,6 @@ JS_IsConstructor(JSFunction* fun)
return fun->isConstructor();
}
JS_PUBLIC_API(JSObject*)
JS_BindCallable(JSContext* cx, HandleObject target, HandleObject newThis)
{
RootedValue thisArg(cx, ObjectValue(*newThis));
return fun_bind(cx, target, thisArg, nullptr, 0);
}
static bool
GenericNativeMethodDispatcher(JSContext* cx, unsigned argc, Value* vp)
{
@@ -4770,7 +4766,7 @@ JS_NewStringCopyZ(JSContext* cx, const char* s)
}
JS_PUBLIC_API(bool)
JS_StringHasBeenInterned(JSContext* cx, JSString* str)
JS_StringHasBeenPinned(JSContext* cx, JSString* str)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
@@ -4778,7 +4774,7 @@ JS_StringHasBeenInterned(JSContext* cx, JSString* str)
if (!str->isAtom())
return false;
return AtomIsInterned(cx, &str->asAtom());
return AtomIsPinned(cx, &str->asAtom());
}
JS_PUBLIC_API(jsid)
@@ -4786,33 +4782,33 @@ INTERNED_STRING_TO_JSID(JSContext* cx, JSString* str)
{
MOZ_ASSERT(str);
MOZ_ASSERT(((size_t)str & JSID_TYPE_MASK) == 0);
MOZ_ASSERT_IF(cx, JS_StringHasBeenInterned(cx, str));
MOZ_ASSERT_IF(cx, JS_StringHasBeenPinned(cx, str));
return AtomToId(&str->asAtom());
}
JS_PUBLIC_API(JSString*)
JS_InternJSString(JSContext* cx, HandleString str)
JS_AtomizeAndPinJSString(JSContext* cx, HandleString str)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
JSAtom* atom = AtomizeString(cx, str, InternAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenInterned(cx, atom));
JSAtom* atom = AtomizeString(cx, str, PinAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenPinned(cx, atom));
return atom;
}
JS_PUBLIC_API(JSString*)
JS_InternString(JSContext* cx, const char* s)
JS_AtomizeAndPinString(JSContext* cx, const char* s)
{
return JS_InternStringN(cx, s, strlen(s));
return JS_AtomizeAndPinStringN(cx, s, strlen(s));
}
JS_PUBLIC_API(JSString*)
JS_InternStringN(JSContext* cx, const char* s, size_t length)
JS_AtomizeAndPinStringN(JSContext* cx, const char* s, size_t length)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
JSAtom* atom = Atomize(cx, s, length, InternAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenInterned(cx, atom));
JSAtom* atom = Atomize(cx, s, length, PinAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenPinned(cx, atom));
return atom;
}
@@ -4845,19 +4841,19 @@ JS_NewUCStringCopyZ(JSContext* cx, const char16_t* s)
}
JS_PUBLIC_API(JSString*)
JS_InternUCStringN(JSContext* cx, const char16_t* s, size_t length)
JS_AtomizeAndPinUCStringN(JSContext* cx, const char16_t* s, size_t length)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
JSAtom* atom = AtomizeChars(cx, s, length, InternAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenInterned(cx, atom));
JSAtom* atom = AtomizeChars(cx, s, length, PinAtom);
MOZ_ASSERT_IF(atom, JS_StringHasBeenPinned(cx, atom));
return atom;
}
JS_PUBLIC_API(JSString*)
JS_InternUCString(JSContext* cx, const char16_t* s)
JS_AtomizeAndPinUCString(JSContext* cx, const char16_t* s)
{
return JS_InternUCStringN(cx, s, js_strlen(s));
return JS_AtomizeAndPinUCStringN(cx, s, js_strlen(s));
}
JS_PUBLIC_API(size_t)
@@ -6273,6 +6269,22 @@ JS::CaptureCurrentStack(JSContext* cx, JS::MutableHandleObject stackp, unsigned
return true;
}
JS_PUBLIC_API(bool)
JS::CopyAsyncStack(JSContext* cx, JS::HandleObject asyncStack,
JS::HandleString asyncCause, JS::MutableHandleObject stackp,
unsigned maxFrameCount)
{
js::AssertObjectIsSavedFrameOrWrapper(cx, asyncStack);
JSCompartment* compartment = cx->compartment();
MOZ_ASSERT(compartment);
Rooted<SavedFrame*> frame(cx);
if (!compartment->savedStacks().copyAsyncStack(cx, asyncStack, asyncCause,
&frame, maxFrameCount))
return false;
stackp.set(frame.get());
return true;
}
JS_PUBLIC_API(Zone*)
JS::GetObjectZone(JSObject* obj)
{
+27 -23
View File
@@ -739,7 +739,7 @@ JS_NumberValue(double d)
/************************************************************************/
JS_PUBLIC_API(bool)
JS_StringHasBeenInterned(JSContext* cx, JSString* str);
JS_StringHasBeenPinned(JSContext* cx, JSString* str);
namespace JS {
@@ -3261,14 +3261,6 @@ JS_IsNativeFunction(JSObject* funobj, JSNative call);
extern JS_PUBLIC_API(bool)
JS_IsConstructor(JSFunction* fun);
/*
* Bind the given callable to use the given object as "this".
*
* If |callable| is not callable, will throw and return nullptr.
*/
extern JS_PUBLIC_API(JSObject*)
JS_BindCallable(JSContext* cx, JS::Handle<JSObject*> callable, JS::Handle<JSObject*> newThis);
// This enum is used to select if properties with JSPROP_DEFINE_LATE flag
// should be defined on the object.
// Normal JSAPI consumers probably always want DefineAllProperties here.
@@ -4049,13 +4041,13 @@ extern JS_PUBLIC_API(JSString*)
JS_NewStringCopyZ(JSContext* cx, const char* s);
extern JS_PUBLIC_API(JSString*)
JS_InternJSString(JSContext* cx, JS::HandleString str);
JS_AtomizeAndPinJSString(JSContext* cx, JS::HandleString str);
extern JS_PUBLIC_API(JSString*)
JS_InternStringN(JSContext* cx, const char* s, size_t length);
JS_AtomizeAndPinStringN(JSContext* cx, const char* s, size_t length);
extern JS_PUBLIC_API(JSString*)
JS_InternString(JSContext* cx, const char* s);
JS_AtomizeAndPinString(JSContext* cx, const char* s);
extern JS_PUBLIC_API(JSString*)
JS_NewUCString(JSContext* cx, char16_t* chars, size_t length);
@@ -4067,10 +4059,10 @@ extern JS_PUBLIC_API(JSString*)
JS_NewUCStringCopyZ(JSContext* cx, const char16_t* s);
extern JS_PUBLIC_API(JSString*)
JS_InternUCStringN(JSContext* cx, const char16_t* s, size_t length);
JS_AtomizeAndPinUCStringN(JSContext* cx, const char16_t* s, size_t length);
extern JS_PUBLIC_API(JSString*)
JS_InternUCString(JSContext* cx, const char16_t* s);
JS_AtomizeAndPinUCString(JSContext* cx, const char16_t* s);
extern JS_PUBLIC_API(bool)
JS_CompareStrings(JSContext* cx, JSString* str1, JSString* str2, int32_t* result);
@@ -4091,9 +4083,8 @@ JS_FileEscapedString(FILE* fp, JSString* str, char quote);
* fail. As indicated by the lack of a JSContext parameter, there are two
* special cases where getting the chars is infallible:
*
* The first case is interned strings, i.e., strings from JS_InternString or
* JSID_TO_STRING(id), using JS_GetLatin1InternedStringChars or
* JS_GetTwoByteInternedStringChars.
* The first case is for strings that have been atomized, e.g. directly by
* JS_AtomizeAndPinString or implicitly because it is stored in a jsid.
*
* The second case is "flat" strings that have been explicitly prepared in a
* fallible context by JS_FlattenString. To catch errors, a separate opaque
@@ -4154,12 +4145,6 @@ JS_GetTwoByteExternalStringChars(JSString* str);
extern JS_PUBLIC_API(bool)
JS_CopyStringChars(JSContext* cx, mozilla::Range<char16_t> dest, JSString* str);
extern JS_PUBLIC_API(const JS::Latin1Char*)
JS_GetLatin1InternedStringChars(const JS::AutoCheckCannotGC& nogc, JSString* str);
extern JS_PUBLIC_API(const char16_t*)
JS_GetTwoByteInternedStringChars(const JS::AutoCheckCannotGC& nogc, JSString* str);
extern JS_PUBLIC_API(JSFlatString*)
JS_FlattenString(JSContext* cx, JSString* str);
@@ -5310,6 +5295,25 @@ SetOutOfMemoryCallback(JSRuntime* rt, OutOfMemoryCallback cb, void* data);
extern JS_PUBLIC_API(bool)
CaptureCurrentStack(JSContext* cx, MutableHandleObject stackp, unsigned maxFrameCount = 0);
/*
* This is a utility function for preparing an async stack to be used
* by some other object. This may be used when you need to treat a
* given stack trace as an async parent. If you just need to capture
* the current stack, async parents and all, use CaptureCurrentStack
* instead.
*
* Here |asyncStack| is the async stack to prepare. It is copied into
* |cx|'s current compartment, and the newest frame is given
* |asyncCause| as its asynchronous cause. If |maxFrameCount| is
* non-zero, capture at most the youngest |maxFrameCount| frames. The
* new stack object is written to |stackp|. Returns true on success,
* or sets an exception and returns |false| on error.
*/
extern JS_PUBLIC_API(bool)
CopyAsyncStack(JSContext* cx, HandleObject asyncStack,
HandleString asyncCause, MutableHandleObject stackp,
unsigned maxFrameCount);
/*
* Accessors for working with SavedFrame JSObjects
*
+23 -23
View File
@@ -143,7 +143,7 @@ JSRuntime::initializeAtoms(JSContext* cx)
ImmutablePropertyNamePtr* names = reinterpret_cast<ImmutablePropertyNamePtr*>(commonNames);
for (size_t i = 0; i < ArrayLength(cachedNames); i++, names++) {
JSAtom* atom = Atomize(cx, cachedNames[i].str, cachedNames[i].length, InternAtom);
JSAtom* atom = Atomize(cx, cachedNames[i].str, cachedNames[i].length, PinAtom);
if (!atom)
return false;
names->init(atom->asPropertyName());
@@ -197,11 +197,11 @@ js::MarkAtoms(JSTracer* trc)
JSRuntime* rt = trc->runtime();
for (AtomSet::Enum e(rt->atoms()); !e.empty(); e.popFront()) {
const AtomStateEntry& entry = e.front();
if (!entry.isTagged())
if (!entry.isPinned())
continue;
JSAtom* atom = entry.asPtr();
bool tagged = entry.isTagged();
bool tagged = entry.isPinned();
TraceRoot(trc, &atom, "interned_atom");
if (entry.asPtr() != atom)
e.rekeyFront(AtomHasher::Lookup(atom), AtomStateEntry(atom, tagged));
@@ -257,7 +257,7 @@ JSRuntime::sweepAtoms()
bool isDying = IsAboutToBeFinalizedUnbarriered(&atom);
/* Pinned or interned key cannot be finalized. */
MOZ_ASSERT_IF(hasContexts() && entry.isTagged(), !isDying);
MOZ_ASSERT_IF(hasContexts() && entry.isPinned(), !isDying);
if (isDying)
e.removeFront();
@@ -289,7 +289,7 @@ JSRuntime::transformToPermanentAtoms(JSContext *cx)
}
bool
AtomIsInterned(JSContext* cx, JSAtom* atom)
AtomIsPinned(JSContext* cx, JSAtom* atom)
{
/* We treat static strings as interned because they're never collected. */
if (StaticStrings::isStatic(atom))
@@ -309,14 +309,14 @@ AtomIsInterned(JSContext* cx, JSAtom* atom)
if (!p)
return false;
return p->isTagged();
return p->isPinned();
}
/* |tbchars| must not point into an inline or short string. */
template <typename CharT>
MOZ_ALWAYS_INLINE
static JSAtom*
AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, InternBehavior ib)
AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin)
{
if (JSAtom* s = cx->staticStrings().lookup(tbchars, length))
return s;
@@ -340,7 +340,7 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, I
AtomSet::AddPtr p = atoms.lookupForAdd(lookup);
if (p) {
JSAtom* atom = p->asPtr();
p->setTagged(bool(ib));
p->setPinned(bool(pin));
return atom;
}
@@ -360,7 +360,7 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, I
// We have held the lock since looking up p, and the operations we've done
// since then can't GC; therefore the atoms table has not been modified and
// p is still valid.
if (!atoms.add(p, AtomStateEntry(atom, bool(ib)))) {
if (!atoms.add(p, AtomStateEntry(atom, bool(pin)))) {
ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
return nullptr;
}
@@ -369,19 +369,19 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, I
}
template JSAtom*
AtomizeAndCopyChars(ExclusiveContext* cx, const char16_t* tbchars, size_t length, InternBehavior ib);
AtomizeAndCopyChars(ExclusiveContext* cx, const char16_t* tbchars, size_t length, PinningBehavior pin);
template JSAtom*
AtomizeAndCopyChars(ExclusiveContext* cx, const Latin1Char* tbchars, size_t length, InternBehavior ib);
AtomizeAndCopyChars(ExclusiveContext* cx, const Latin1Char* tbchars, size_t length, PinningBehavior pin);
JSAtom*
js::AtomizeString(ExclusiveContext* cx, JSString* str,
js::InternBehavior ib /* = js::DoNotInternAtom */)
js::PinningBehavior pin /* = js::DoNotPinAtom */)
{
if (str->isAtom()) {
JSAtom& atom = str->asAtom();
/* N.B. static atoms are effectively always interned. */
if (ib != InternAtom || js::StaticStrings::isStatic(&atom))
if (pin != PinAtom || js::StaticStrings::isStatic(&atom))
return &atom;
AtomHasher::Lookup lookup(&atom);
@@ -397,8 +397,8 @@ js::AtomizeString(ExclusiveContext* cx, JSString* str,
p = cx->atoms().lookup(lookup);
MOZ_ASSERT(p); /* Non-static atom must exist in atom state set. */
MOZ_ASSERT(p->asPtr() == &atom);
MOZ_ASSERT(ib == InternAtom);
p->setTagged(bool(ib));
MOZ_ASSERT(pin == PinAtom);
p->setPinned(bool(pin));
return &atom;
}
@@ -408,12 +408,12 @@ js::AtomizeString(ExclusiveContext* cx, JSString* str,
JS::AutoCheckCannotGC nogc;
return linear->hasLatin1Chars()
? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc), linear->length(), ib)
: AtomizeAndCopyChars(cx, linear->twoByteChars(nogc), linear->length(), ib);
? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc), linear->length(), pin)
: AtomizeAndCopyChars(cx, linear->twoByteChars(nogc), linear->length(), pin);
}
JSAtom*
js::Atomize(ExclusiveContext* cx, const char* bytes, size_t length, InternBehavior ib)
js::Atomize(ExclusiveContext* cx, const char* bytes, size_t length, PinningBehavior pin)
{
CHECK_REQUEST(cx);
@@ -421,26 +421,26 @@ js::Atomize(ExclusiveContext* cx, const char* bytes, size_t length, InternBehavi
return nullptr;
const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(bytes);
return AtomizeAndCopyChars(cx, chars, length, ib);
return AtomizeAndCopyChars(cx, chars, length, pin);
}
template <typename CharT>
JSAtom*
js::AtomizeChars(ExclusiveContext* cx, const CharT* chars, size_t length, InternBehavior ib)
js::AtomizeChars(ExclusiveContext* cx, const CharT* chars, size_t length, PinningBehavior pin)
{
CHECK_REQUEST(cx);
if (!JSString::validateLength(cx, length))
return nullptr;
return AtomizeAndCopyChars(cx, chars, length, ib);
return AtomizeAndCopyChars(cx, chars, length, pin);
}
template JSAtom*
js::AtomizeChars(ExclusiveContext* cx, const Latin1Char* chars, size_t length, InternBehavior ib);
js::AtomizeChars(ExclusiveContext* cx, const Latin1Char* chars, size_t length, PinningBehavior pin);
template JSAtom*
js::AtomizeChars(ExclusiveContext* cx, const char16_t* chars, size_t length, InternBehavior ib);
js::AtomizeChars(ExclusiveContext* cx, const char16_t* chars, size_t length, PinningBehavior pin);
bool
js::IndexToIdSlow(ExclusiveContext* cx, uint32_t index, MutableHandleId idp)
+10 -10
View File
@@ -72,7 +72,7 @@ class AtomStateEntry
MOZ_ASSERT((uintptr_t(ptr) & 0x1) == 0);
}
bool isTagged() const {
bool isPinned() const {
return bits & 0x1;
}
@@ -80,8 +80,8 @@ class AtomStateEntry
* Non-branching code sequence. Note that the const_cast is safe because
* the hash function doesn't consider the tag to be a portion of the key.
*/
void setTagged(bool enabled) const {
const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(enabled);
void setPinned(bool pinned) const {
const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned);
}
JSAtom* asPtr() const;
@@ -151,7 +151,7 @@ class PropertyName;
} /* namespace js */
extern bool
AtomIsInterned(JSContext* cx, JSAtom* atom);
AtomIsPinned(JSContext* cx, JSAtom* atom);
/* Well-known predefined C strings. */
#define DECLARE_PROTO_STR(name,code,init,clasp) extern const char js_##name##_str[];
@@ -218,23 +218,23 @@ void
MarkWellKnownSymbols(JSTracer* trc);
/* N.B. must correspond to boolean tagging behavior. */
enum InternBehavior
enum PinningBehavior
{
DoNotInternAtom = false,
InternAtom = true
DoNotPinAtom = false,
PinAtom = true
};
extern JSAtom*
Atomize(ExclusiveContext* cx, const char* bytes, size_t length,
js::InternBehavior ib = js::DoNotInternAtom);
js::PinningBehavior pin = js::DoNotPinAtom);
template <typename CharT>
extern JSAtom*
AtomizeChars(ExclusiveContext* cx, const CharT* chars, size_t length,
js::InternBehavior ib = js::DoNotInternAtom);
js::PinningBehavior pin = js::DoNotPinAtom);
extern JSAtom*
AtomizeString(ExclusiveContext* cx, JSString* str, js::InternBehavior ib = js::DoNotInternAtom);
AtomizeString(ExclusiveContext* cx, JSString* str, js::PinningBehavior pin = js::DoNotPinAtom);
template <AllowGC allowGC>
extern JSAtom*
+14 -25
View File
@@ -1675,42 +1675,29 @@ js::fun_bind(JSContext* cx, unsigned argc, Value* vp)
argslen = args.length() - 1;
}
// Steps 4-14.
RootedValue thisArg(cx, args.length() >= 1 ? args[0] : UndefinedValue());
RootedObject target(cx, &thisv.toObject());
JSObject* boundFunction = fun_bind(cx, target, thisArg, boundArgs, argslen);
if (!boundFunction)
return false;
// Step 15.
args.rval().setObject(*boundFunction);
return true;
}
JSObject*
js::fun_bind(JSContext* cx, HandleObject target, HandleValue thisArg,
Value* boundArgs, unsigned argslen)
{
double length = 0.0;
// Try to avoid invoking the resolve hook.
if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedLength()) {
uint16_t len;
if (!target->as<JSFunction>().getLength(cx, &len))
return nullptr;
return false;
length = Max(0.0, double(len) - argslen);
} else {
// Steps 5-6.
RootedId id(cx, NameToId(cx->names().length));
bool hasLength;
if (!HasOwnProperty(cx, target, id, &hasLength))
return nullptr;
return false;
// Step 7-8.
if (hasLength) {
// a-b.
RootedValue targetLen(cx);
if (!GetProperty(cx, target, target, id, &targetLen))
return nullptr;
return false;
// d.
if (targetLen.isNumber())
length = Max(0.0, JS::ToInteger(targetLen.toNumber()) - argslen);
@@ -1725,7 +1712,7 @@ js::fun_bind(JSContext* cx, HandleObject target, HandleValue thisArg,
// Steps 11-12.
RootedValue targetName(cx);
if (!GetProperty(cx, target, target, cx->names().name, &targetName))
return nullptr;
return false;
// Step 13.
if (targetName.isString())
@@ -1736,23 +1723,23 @@ js::fun_bind(JSContext* cx, HandleObject target, HandleValue thisArg,
StringBuffer sb(cx);
// Disabled for B2G failures.
// if (!sb.append("bound ") || !sb.append(name))
// return nullptr;
// return false;
if (!sb.append(name))
return nullptr;
return false;
RootedAtom nameAtom(cx, sb.finishAtom());
if (!nameAtom)
return nullptr;
return false;
// Step 4.
// Step 4.
RootedFunction fun(cx, target->isConstructor() ?
NewNativeConstructor(cx, CallOrConstructBoundFunction, length, nameAtom) :
NewNativeFunction(cx, CallOrConstructBoundFunction, length, nameAtom));
if (!fun)
return nullptr;
return false;
if (!fun->initBoundFunction(cx, target, thisArg, boundArgs, argslen))
return nullptr;
return false;
// Steps 9-10. Set length again, because NewNativeFunction/NewNativeConstructor
// sometimes truncates.
@@ -1761,11 +1748,13 @@ js::fun_bind(JSContext* cx, HandleObject target, HandleValue thisArg,
if (!DefineProperty(cx, fun, cx->names().length, lengthVal, nullptr, nullptr,
JSPROP_READONLY))
{
return nullptr;
return false;
}
}
return fun;
// Step 15.
args.rval().setObject(*fun);
return true;
}
/*
-4
View File
@@ -759,10 +759,6 @@ fun_apply(JSContext* cx, unsigned argc, Value* vp);
extern bool
fun_call(JSContext* cx, unsigned argc, Value* vp);
extern JSObject*
fun_bind(JSContext* cx, HandleObject target, HandleValue thisArg,
Value* boundArgs, unsigned argslen);
} /* namespace js */
#ifdef DEBUG
+1 -1
View File
@@ -2937,7 +2937,7 @@ JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHan
/* Avoid recursive death when decompiling in ReportValueError. */
RootedString str(cx);
if (hint == JSTYPE_STRING) {
str = JS_InternString(cx, clasp->name);
str = JS_AtomizeAndPinString(cx, clasp->name);
if (!str)
return false;
} else {
+5 -3
View File
@@ -2249,7 +2249,7 @@ Intern(JSContext* cx, unsigned argc, jsval* vp)
mozilla::Range<const char16_t> chars = strChars.twoByteRange();
if (!JS_InternUCStringN(cx, chars.start().get(), chars.length()))
if (!JS_AtomizeAndPinUCStringN(cx, chars.start().get(), chars.length()))
return false;
args.rval().setUndefined();
@@ -4129,7 +4129,8 @@ class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
MOZ_ASSERT(!enteredWithoutExit);
}
void Entry(JSContext* cx, JSFunction* function) override {
void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
JS::HandleString asyncCause) override {
MOZ_ASSERT(!enteredWithoutExit);
enteredWithoutExit = true;
@@ -4143,7 +4144,8 @@ class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
oom = !log.append(make_string_copy("anonymous"));
}
void Entry(JSContext* cx, JSScript* script) override {
void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
JS::HandleString asyncCause) override {
MOZ_ASSERT(!enteredWithoutExit);
enteredWithoutExit = true;
+1 -1
View File
@@ -7553,7 +7553,7 @@ DebuggerEnv_getType(JSContext* cx, unsigned argc, Value* vp)
else
s = "object";
JSAtom* str = Atomize(cx, s, strlen(s), InternAtom);
JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
if (!str)
return false;
args.rval().setString(str);
+15
View File
@@ -807,6 +807,21 @@ SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsi
return insertFrames(cx, iter, frame, maxFrameCount);
}
bool
SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
MutableHandleSavedFrame adoptedStack, unsigned maxFrameCount)
{
MOZ_ASSERT(initialized());
assertSameCompartment(cx, this);
RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack));
MOZ_ASSERT(asyncStackObj);
MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj));
RootedSavedFrame frame(cx, &asyncStackObj->as<js::SavedFrame>());
return adoptAsyncStack(cx, frame, asyncCause, adoptedStack, maxFrameCount);
}
void
SavedStacks::sweep(JSRuntime* rt)
{
+2
View File
@@ -274,6 +274,8 @@ class SavedStacks {
bool init();
bool initialized() const { return frames.initialized(); }
bool saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsigned maxFrameCount = 0);
bool copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
MutableHandleSavedFrame adoptedStack, unsigned maxFrameCount = 0);
void sweep(JSRuntime* rt);
void trace(JSTracer* trc);
uint32_t count();
+6 -2
View File
@@ -923,10 +923,14 @@ InterpreterActivation::InterpreterActivation(RunState& state, JSContext* cx,
MOZ_ASSERT_IF(entryFrame_->isEvalFrame(), state.script()->isActiveEval());
if (entryMonitor_) {
RootedValue stack(cx_);
stack.setObjectOrNull(asyncStack_.get());
if (!cx_->compartment()->wrap(cx_, &stack))
stack.setUndefined();
if (entryFrame->isFunctionFrame())
entryMonitor_->Entry(cx_, entryFrame->fun());
entryMonitor_->Entry(cx_, entryFrame->fun(), stack, asyncCause_);
else
entryMonitor_->Entry(cx_, entryFrame->script());
entryMonitor_->Entry(cx_, entryFrame->script(), stack, asyncCause_);
}
}
+6 -2
View File
@@ -1404,10 +1404,14 @@ jit::JitActivation::JitActivation(JSContext* cx, CalleeToken entryPoint, bool ac
if (entryMonitor_) {
MOZ_ASSERT(entryPoint);
RootedValue stack(cx_);
stack.setObjectOrNull(asyncStack_.get());
if (!cx_->compartment()->wrap(cx_, &stack))
stack.setUndefined();
if (CalleeTokenIsFunction(entryPoint))
entryMonitor_->Entry(cx_, CalleeTokenToFunction(entryPoint));
entryMonitor_->Entry(cx_, CalleeTokenToFunction(entryPoint), stack, asyncCause_);
else
entryMonitor_->Entry(cx_, CalleeTokenToScript(entryPoint));
entryMonitor_->Entry(cx_, CalleeTokenToScript(entryPoint), stack, asyncCause_);
}
}
+11 -1
View File
@@ -20,7 +20,7 @@
* property, it should return a replacement property descriptor for it. If not,
* it should return null.
*/
[scriptable,uuid(215353cb-6e77-462f-a791-6891f42e593f)]
[scriptable,uuid(d05cc5fd-ad88-41a6-854c-36fd94d69ddb)]
interface nsIAddonInterposition : nsISupports
{
/**
@@ -54,4 +54,14 @@ interface nsIAddonInterposition : nsISupports
in jsval originalFunc,
in jsval originalThis,
in jsval args);
/**
* For the first time when the interposition is registered the engine
* calls getWhitelist and expects an array of strings. The strings are
* the name of properties the interposition wants interposeProperty
* to be called. It can be an empty array.
* Note: for CPOWs interposeProperty is always called regardless if
* the name of the property is on the whitelist or not.
*/
jsval getWhitelist();
};
+1 -1
View File
@@ -432,7 +432,7 @@ ExportFunction(JSContext* cx, HandleValue vfunction, HandleValue vscope, HandleV
JSFunction* fun = JS_GetObjectFunction(funObj);
RootedString funName(cx, JS_GetFunctionId(fun));
if (!funName)
funName = JS_InternString(cx, "");
funName = JS_AtomizeAndPinString(cx, "");
if (!JS_StringToId(cx, funName, &id))
return false;
+3 -1
View File
@@ -32,6 +32,7 @@
#include "nsWindowMemoryReporter.h"
#include "nsDOMClassInfo.h"
#include "ShimInterfaceInfo.h"
#include "nsIAddonInterposition.h"
using namespace mozilla;
using namespace JS;
@@ -3430,8 +3431,9 @@ nsXPCComponents_Utils::SetAddonInterposition(const nsACString& addonIdStr,
JSAddonId* addonId = xpc::NewAddonId(cx, addonIdStr);
if (!addonId)
return NS_ERROR_FAILURE;
if (!XPCWrappedNativeScope::SetAddonInterposition(addonId, interposition))
if (!XPCWrappedNativeScope::SetAddonInterposition(cx, addonId, interposition))
return NS_ERROR_FAILURE;
return NS_OK;
}
+2 -2
View File
@@ -3411,7 +3411,7 @@ XPCJSRuntime::OnJSContextNew(JSContext* cx)
if (JSID_IS_VOID(mStrIDs[0])) {
RootedString str(cx);
for (unsigned i = 0; i < IDX_TOTAL_COUNT; i++) {
str = JS_InternString(cx, mStrings[i]);
str = JS_AtomizeAndPinString(cx, mStrings[i]);
if (!str) {
mStrIDs[0] = JSID_VOID;
return false;
@@ -3445,7 +3445,7 @@ XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const js::Class* clasp,
XPCWrappedNativeProto* p =
static_cast<XPCWrappedNativeProto*>(xpc_GetJSPrivate(obj));
si = p->GetScriptableInfo();
if (!si) {
return false;
}
+3 -3
View File
@@ -293,7 +293,7 @@ XPCNativeInterface::NewInstance(nsIInterfaceInfo* aInfo)
if (!XPCConvert::IsMethodReflectable(*info))
continue;
str = JS_InternString(cx, info->GetName());
str = JS_AtomizeAndPinString(cx, info->GetName());
if (!str) {
NS_ERROR("bad method name");
failed = true;
@@ -338,7 +338,7 @@ XPCNativeInterface::NewInstance(nsIInterfaceInfo* aInfo)
break;
}
str = JS_InternString(cx, namestr);
str = JS_AtomizeAndPinString(cx, namestr);
if (!str) {
NS_ERROR("bad constant name");
failed = true;
@@ -364,7 +364,7 @@ XPCNativeInterface::NewInstance(nsIInterfaceInfo* aInfo)
if (!failed) {
const char* bytes;
if (NS_FAILED(aInfo->GetNameShared(&bytes)) || !bytes ||
nullptr == (str = JS_InternString(cx, bytes))) {
nullptr == (str = JS_AtomizeAndPinString(cx, bytes))) {
failed = true;
}
interfaceName = INTERNED_STRING_TO_JSID(cx, str);
+112 -3
View File
@@ -27,6 +27,7 @@ using namespace JS;
XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
XPCWrappedNativeScope::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr;
InterpositionWhitelistArray* XPCWrappedNativeScope::gInterpositionWhitelists = nullptr;
NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver, nsIObserver)
@@ -46,6 +47,11 @@ XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(nsISupports* subject
gInterpositionMap = nullptr;
}
if (gInterpositionWhitelists) {
delete gInterpositionWhitelists;
gInterpositionWhitelists = nullptr;
}
nsContentUtils::UnregisterShutdownObserver(this);
return NS_OK;
}
@@ -143,6 +149,8 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
"extensions.interposition.enabled", false);
if (interpositionEnabled) {
mInterposition = do_GetService("@mozilla.org/addons/default-addon-shims;1");
MOZ_ASSERT(mInterposition);
UpdateInterpositionWhitelist(cx, mInterposition);
}
}
}
@@ -693,7 +701,8 @@ XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target,
}
/* static */ bool
XPCWrappedNativeScope::SetAddonInterposition(JSAddonId* addonId,
XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx,
JSAddonId* addonId,
nsIAddonInterposition* interp)
{
if (!gInterpositionMap) {
@@ -701,14 +710,17 @@ XPCWrappedNativeScope::SetAddonInterposition(JSAddonId* addonId,
gInterpositionMap->init();
// Make sure to clear the map at shutdown.
// Note: this will take care of gInterpositionWhitelists too.
nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
}
if (interp) {
return gInterpositionMap->put(addonId, interp);
bool ok = gInterpositionMap->put(addonId, interp);
NS_ENSURE_TRUE(ok, false);
UpdateInterpositionWhitelist(cx, interp);
} else {
gInterpositionMap->remove(addonId);
return true;
}
return true;
}
nsCOMPtr<nsIAddonInterposition>
@@ -717,6 +729,103 @@ XPCWrappedNativeScope::GetInterposition()
return mInterposition;
}
/* static */ InterpositionWhitelist*
XPCWrappedNativeScope::GetInterpositionWhitelist(nsIAddonInterposition* interposition)
{
if (!gInterpositionWhitelists)
return nullptr;
InterpositionWhitelistArray& wls = *gInterpositionWhitelists;
for (size_t i = 0; i < wls.Length(); i++) {
if (wls[i].interposition == interposition)
return &wls[i].whitelist;
}
return nullptr;
}
/* static */ bool
XPCWrappedNativeScope::UpdateInterpositionWhitelist(JSContext* cx,
nsIAddonInterposition* interposition)
{
// We want to set the interpostion whitelist only once.
InterpositionWhitelist* whitelist = GetInterpositionWhitelist(interposition);
if (whitelist)
return true;
// The hashsets in gInterpositionWhitelists do not have a copy constructor so
// a reallocation for the array will lead to a memory corruption. If you
// need more interpositions, change the capacity of the array please.
static const size_t MAX_INTERPOSITION = 8;
if (!gInterpositionWhitelists)
gInterpositionWhitelists = new InterpositionWhitelistArray(MAX_INTERPOSITION);
MOZ_RELEASE_ASSERT(MAX_INTERPOSITION > gInterpositionWhitelists->Length() + 1);
InterpositionWhitelistPair* newPair = gInterpositionWhitelists->AppendElement();
newPair->interposition = interposition;
newPair->whitelist.init();
whitelist = &newPair->whitelist;
RootedValue whitelistVal(cx);
nsresult rv = interposition->GetWhitelist(&whitelistVal);
if (NS_FAILED(rv)) {
JS_ReportError(cx, "Could not get the whitelist from the interposition.");
return false;
}
if (!whitelistVal.isObject()) {
JS_ReportError(cx, "Whitelist must be an array.");
return false;
}
// We want to enter the whitelist's compartment to avoid any wrappers.
// To be on the safe side let's make sure that it's a system compartment
// and we don't accidentally trigger some content function here by parsing
// the whitelist object.
RootedObject whitelistObj(cx, &whitelistVal.toObject());
whitelistObj = js::UncheckedUnwrap(whitelistObj);
if (!AccessCheck::isChrome(whitelistObj)) {
JS_ReportError(cx, "Whitelist must be from system scope.");
return false;
}
{
JSAutoCompartment ac(cx, whitelistObj);
uint32_t length;
if (!JS_IsArrayObject(cx, whitelistObj) ||
!JS_GetArrayLength(cx, whitelistObj, &length)) {
JS_ReportError(cx, "Whitelist must be an array.");
return false;
}
for (uint32_t i = 0; i < length; i++) {
RootedValue idval(cx);
if (!JS_GetElement(cx, whitelistObj, i, &idval))
return false;
if (!idval.isString()) {
JS_ReportError(cx, "Whitelist must contain strings only.");
return false;
}
RootedString str(cx, idval.toString());
str = JS_AtomizeAndPinJSString(cx, str);
if (!str) {
JS_ReportError(cx, "String internization failed.");
return false;
}
// By internizing the id's we ensure that they won't get
// GCed so we can use them as hash keys.
jsid id = INTERNED_STRING_TO_JSID(cx, str);
whitelist->put(JSID_BITS(id));
}
}
return true;
}
/***************************************************************************/
// static
+8 -11
View File
@@ -1347,17 +1347,14 @@ bool
SetAddonInterposition(const nsACString& addonIdStr, nsIAddonInterposition* interposition)
{
JSAddonId* addonId;
{
// We enter the junk scope just to allocate a string, which actually will go
// in the system zone.
AutoJSAPI jsapi;
jsapi.Init(xpc::PrivilegedJunkScope());
addonId = NewAddonId(jsapi.cx(), addonIdStr);
if (!addonId)
return false;
}
return XPCWrappedNativeScope::SetAddonInterposition(addonId, interposition);
// We enter the junk scope just to allocate a string, which actually will go
// in the system zone.
AutoJSAPI jsapi;
jsapi.Init(xpc::PrivilegedJunkScope());
addonId = NewAddonId(jsapi.cx(), addonIdStr);
if (!addonId)
return false;
return XPCWrappedNativeScope::SetAddonInterposition(jsapi.cx(), addonId, interposition);
}
} // namespace xpc
+19 -1
View File
@@ -1014,6 +1014,17 @@ static inline bool IS_PROTO_CLASS(const js::Class* clazz)
clazz == &XPC_WN_ModsAllowed_NoCall_Proto_JSClass;
}
typedef js::HashSet<size_t,
js::DefaultHasher<size_t>,
js::SystemAllocPolicy> InterpositionWhitelist;
struct InterpositionWhitelistPair {
nsIAddonInterposition* interposition;
InterpositionWhitelist whitelist;
};
typedef nsTArray<InterpositionWhitelistPair> InterpositionWhitelistArray;
/***************************************************************************/
// XPCWrappedNativeScope is one-to-one with a JS global object.
@@ -1184,9 +1195,14 @@ public:
bool HasInterposition() { return mInterposition; }
nsCOMPtr<nsIAddonInterposition> GetInterposition();
static bool SetAddonInterposition(JSAddonId* addonId,
static bool SetAddonInterposition(JSContext* cx,
JSAddonId* addonId,
nsIAddonInterposition* interp);
static InterpositionWhitelist* GetInterpositionWhitelist(nsIAddonInterposition* interposition);
static bool UpdateInterpositionWhitelist(JSContext* cx,
nsIAddonInterposition* interposition);
void SetAddonCallInterposition() { mHasCallInterpositions = true; }
bool HasCallInterposition() { return mHasCallInterpositions; };
@@ -1209,6 +1225,8 @@ private:
static InterpositionMap* gInterpositionMap;
static InterpositionWhitelistArray* gInterpositionWhitelists;
XPCJSRuntime* mRuntime;
Native2WrappedNativeMap* mWrappedNativeMap;
ClassInfo2WrappedNativeProtoMap* mWrappedNativeProtoMap;
@@ -34,6 +34,11 @@ let TestInterposition = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition,
Ci.nsISupportsWeakReference]),
getWhitelist: function() {
return ["abcxyz", "utils", "dataprop", "getterprop", "setterprop",
"objprop", "defineprop", "configurableprop"];
},
interposeProperty: function(addonId, target, iid, prop) {
do_check_eq(addonId, ADDONID);
do_check_eq(gExpectedProp, prop);
+9 -1
View File
@@ -29,10 +29,12 @@ InterposeProperty(JSContext* cx, HandleObject target, const nsIID* iid, HandleId
// wrapped natives.
RootedObject unwrapped(cx, UncheckedUnwrap(target));
const js::Class* clasp = js::GetObjectClass(unwrapped);
bool isCPOW = jsipc::IsWrappedCPOW(unwrapped);
if (!mozilla::dom::IsDOMClass(clasp) &&
!IS_WN_CLASS(clasp) &&
!IS_PROTO_CLASS(clasp) &&
clasp != &OuterWindowProxyClass) {
clasp != &OuterWindowProxyClass &&
!isCPOW) {
return true;
}
@@ -40,6 +42,12 @@ InterposeProperty(JSContext* cx, HandleObject target, const nsIID* iid, HandleId
MOZ_ASSERT(scope->HasInterposition());
nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
InterpositionWhitelist* wl = XPCWrappedNativeScope::GetInterpositionWhitelist(interp);
// We do InterposeProperty only if the id is on the whitelist of the interpostion
// or if the target is a CPOW.
if ((!wl || !wl->has(JSID_BITS(id.get()))) && !isCPOW)
return true;
JSAddonId* addonId = AddonIdOfObject(target);
RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId)));
RootedValue prop(cx, IdToValue(id));
+21 -10
View File
@@ -27,7 +27,8 @@ public:
virtual void Prev() override;
nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart,
nsIteratorType aType, bool aLockScroll, bool aFollowOOFs);
nsIteratorType aType, bool aLockScroll, bool aFollowOOFs,
bool aSkipPopupChecks);
protected:
virtual ~nsFrameIterator() {}
@@ -87,6 +88,7 @@ protected:
nsPresContext* const mPresContext;
const bool mLockScroll;
const bool mFollowOOFs;
const bool mSkipPopupChecks;
const nsIteratorType mType;
private:
@@ -103,8 +105,9 @@ class nsVisualIterator: public nsFrameIterator
{
public:
nsVisualIterator(nsPresContext* aPresContext, nsIFrame *aStart,
nsIteratorType aType, bool aLockScroll, bool aFollowOOFs) :
nsFrameIterator(aPresContext, aStart, aType, aLockScroll, aFollowOOFs) {}
nsIteratorType aType, bool aLockScroll,
bool aFollowOOFs, bool aSkipPopupChecks) :
nsFrameIterator(aPresContext, aStart, aType, aLockScroll, aFollowOOFs, aSkipPopupChecks) {}
protected:
nsIFrame* GetFirstChildInner(nsIFrame* aFrame) override;
@@ -136,7 +139,8 @@ NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
nsIteratorType aType,
bool aVisual,
bool aLockInScrollView,
bool aFollowOOFs)
bool aFollowOOFs,
bool aSkipPopupChecks)
{
if (!aEnumerator || !aStart)
return NS_ERROR_NULL_POINTER;
@@ -148,10 +152,10 @@ NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
nsCOMPtr<nsIFrameEnumerator> trav;
if (aVisual) {
trav = new nsVisualIterator(aPresContext, aStart, aType,
aLockInScrollView, aFollowOOFs);
aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
} else {
trav = new nsFrameIterator(aPresContext, aStart, aType,
aLockInScrollView, aFollowOOFs);
aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
}
trav.forget(aEnumerator);
return NS_OK;
@@ -175,11 +179,12 @@ NS_IMETHODIMP
int32_t aType,
bool aVisual,
bool aLockInScrollView,
bool aFollowOOFs)
bool aFollowOOFs,
bool aSkipPopupChecks)
{
return NS_NewFrameTraversal(aEnumerator, aPresContext, aStart,
static_cast<nsIteratorType>(aType),
aVisual, aLockInScrollView, aFollowOOFs);
aVisual, aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
}
// nsFrameIterator implementation
@@ -188,10 +193,11 @@ NS_IMPL_ISUPPORTS(nsFrameIterator, nsIFrameEnumerator)
nsFrameIterator::nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart,
nsIteratorType aType, bool aLockInScrollView,
bool aFollowOOFs)
bool aFollowOOFs, bool aSkipPopupChecks)
: mPresContext(aPresContext),
mLockScroll(aLockInScrollView),
mFollowOOFs(aFollowOOFs),
mSkipPopupChecks(aSkipPopupChecks),
mType(aType),
mStart(aStart),
mCurrent(aStart),
@@ -242,7 +248,7 @@ nsFrameIterator::Last()
nsIFrame* parent = getCurrent();
// If the current frame is a popup, don't move farther up the tree.
// Otherwise, get the nearest root frame or popup.
if (parent->GetType() != nsGkAtoms::menuPopupFrame) {
if (mSkipPopupChecks || parent->GetType() != nsGkAtoms::menuPopupFrame) {
while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent)))
parent = result;
}
@@ -499,6 +505,11 @@ nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame)
bool
nsFrameIterator::IsPopupFrame(nsIFrame* aFrame)
{
// If skipping popup checks, pretend this isn't one.
if (mSkipPopupChecks) {
return false;
}
return (aFrame &&
aFrame->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_POPUP);
}
+4 -2
View File
@@ -16,7 +16,8 @@ nsresult NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
nsIteratorType aType,
bool aVisual,
bool aLockInScrollView,
bool aFollowOOFs);
bool aFollowOOFs,
bool aSkipPopupChecks);
nsresult NS_CreateFrameTraversal(nsIFrameTraversal** aResult);
@@ -33,7 +34,8 @@ public:
int32_t aType,
bool aVisual,
bool aLockInScrollView,
bool aFollowOOFs) override;
bool aFollowOOFs,
bool aSkipPopupChecks) override;
protected:
virtual ~nsFrameTraversal();
+6 -3
View File
@@ -34,9 +34,9 @@ enum nsIteratorType {
ePostOrder
};
// {9d469828-9bf2-4151-a385-05f30219221b}
// {d33fe76c-207c-4359-a315-8eb1eecf80e5}
#define NS_IFRAMETRAVERSAL_IID \
{ 0x9d469828, 0x9bf2, 0x4151, { 0xa3, 0x85, 0x05, 0xf3, 0x02, 0x19, 0x22, 0x1b } }
{ 0xd33fe76c, 0x207c, 0x4359, { 0xa3, 0x15, 0x8e, 0xb1, 0xee, 0xcf, 0x80, 0xe5 } }
class nsIFrameTraversal : public nsISupports
{
@@ -57,6 +57,8 @@ public:
* If true, when reaching a placeholder frame while going down will get
* the real frame. Going back up will go on past the placeholder,
* so the placeholders are logically part of the frame tree.
* @param aSkipPopupChecks [in] if false, then don't iterate into or out of a
* popup frame. If true, skip any popup related checks.
*/
NS_IMETHOD NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
nsPresContext* aPresContext,
@@ -64,7 +66,8 @@ public:
int32_t aType,
bool aVisual,
bool aLockInScrollView,
bool aFollowOOFs) = 0;
bool aFollowOOFs,
bool aSkipPopupChecks) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIFrameTraversal, NS_IFRAMETRAVERSAL_IID)
+6 -3
View File
@@ -6232,7 +6232,8 @@ nsFrame::GetNextPrevLineFromeBlockFrame(nsPresContext* aPresContext,
ePostOrder,
false, // aVisual
aPos->mScrollViewStop,
false // aFollowOOFs
false, // aFollowOOFs
false // aSkipPopupChecks
);
if (NS_FAILED(result))
return result;
@@ -6332,7 +6333,8 @@ nsFrame::GetNextPrevLineFromeBlockFrame(nsPresContext* aPresContext,
eLeaf,
false, // aVisual
aPos->mScrollViewStop,
false // aFollowOOFs
false, // aFollowOOFs
false // aSkipPopupChecks
);
}
while ( !found ){
@@ -7114,7 +7116,8 @@ nsIFrame::GetFrameFromDirection(nsDirection aDirection, bool aVisual,
eLeaf,
aVisual && presContext->BidiEnabled(),
aScrollViewStop,
true // aFollowOOFs
true, // aFollowOOFs
false // aSkipPopupChecks
);
if (NS_FAILED(result))
return result;
+2 -1
View File
@@ -1357,7 +1357,8 @@ nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn,
eLeaf,
false, // aVisual
false, // aLockInScrollView
false // aFollowOOFs
false, // aFollowOOFs
false // aSkipPopupChecks
);
if (NS_FAILED(result))
return result;
@@ -22,6 +22,10 @@ DefaultInterpositionService.prototype = {
classID: Components.ID("{50bc93ce-602a-4bef-bf3a-61fc749c4caf}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
getWhitelist: function() {
return [];
},
interposeProperty: function(addon, target, iid, prop) {
return null;
},
+1
View File
@@ -8,6 +8,7 @@ TEST_DIRS += ['tests']
EXTRA_COMPONENTS += [
'addoncompat.manifest',
'defaultShims.js',
'multiprocessShims.js',
'remoteTagService.js',
]
@@ -70,12 +70,42 @@ function AddonInterpositionService()
// kinds of objects.
this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions();
this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions();
let wl = [];
for (let v in this._interfaceInterpositions) {
let interp = this._interfaceInterpositions[v];
wl.push(...Object.getOwnPropertyNames(interp.methods));
wl.push(...Object.getOwnPropertyNames(interp.getters));
wl.push(...Object.getOwnPropertyNames(interp.setters));
}
for (let v in this._taggedInterpositions) {
let interp = this._taggedInterpositions[v];
wl.push(...Object.getOwnPropertyNames(interp.methods));
wl.push(...Object.getOwnPropertyNames(interp.getters));
wl.push(...Object.getOwnPropertyNames(interp.setters));
}
let nameSet = new Set();
wl = wl.filter(function(item) {
if (nameSet.has(item))
return true;
nameSet.add(item);
return true;
});
this._whitelist = wl;
}
AddonInterpositionService.prototype = {
classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
getWhitelist: function() {
return this._whitelist;
},
// When the interface is not known for a method call, this code
// determines the type of the target object.
getObjectTag: function(target) {
@@ -1251,7 +1251,8 @@ nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell,
eLeaf,
false, // aVisual
false, // aLockInScrollView
false // aFollowOOFs
false, // aFollowOOFs
false // aSkipPopupChecks
);
if (!frameTraversal)