diff --git a/browser/base/content/test/general/browser_parsable_script.js b/browser/base/content/test/general/browser_parsable_script.js new file mode 100644 index 0000000000..65f221799f --- /dev/null +++ b/browser/base/content/test/general/browser_parsable_script.js @@ -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=" 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=" + + " 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"); +}); + diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index a680dfa6ef..8c5b7e0348 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -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 diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 6518546929..0647d09d94 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -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::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 principal; + if (NS_FAILED(rv) || inheritsPrincipal) { + return nsNullPrincipal::Create(); + } + + // Check whether the URI knows what its principal is supposed to be. + nsCOMPtr uriPrinc = do_QueryInterface(aURI); + if (uriPrinc) { + nsCOMPtr principal; + uriPrinc->GetPrincipal(getter_AddRefs(principal)); + if (!principal) { + return nsNullPrincipal::Create(); + } + nsRefPtr concrete = Cast(principal); + return concrete.forget(); + } + + // Mint a codebase principal. + nsRefPtr codebase = new nsPrincipal(); + rv = codebase->Init(aURI, aAttrs); + NS_ENSURE_SUCCESS(rv, nullptr); + return codebase.forget(); +} + } // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index 4a9866b9f7..a4329d930b 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -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(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 CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs); const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; } uint32_t AppId() const { return mOriginAttributes.mAppId; } diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl index 76465abc70..099f5d5dc0 100644 --- a/caps/nsIScriptSecurityManager.idl +++ b/caps/nsIScriptSecurityManager.idl @@ -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). diff --git a/caps/nsNullPrincipal.h b/caps/nsNullPrincipal.h index 4ad6296da9..9566843219 100644 --- a/caps/nsNullPrincipal.h +++ b/caps/nsNullPrincipal.h @@ -54,9 +54,9 @@ public: // Returns null on failure. static already_AddRefed - 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; diff --git a/caps/nsPrincipal.h b/caps/nsPrincipal.h index be491ffa2d..31897204fa 100644 --- a/caps/nsPrincipal.h +++ b/caps/nsPrincipal.h @@ -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); diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 54c08f32d1..d2d4fe9edf 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -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 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 uriPrinc = do_QueryInterface(aURI); - if (uriPrinc) { - nsCOMPtr 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 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 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 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 aOriginAttributes, + JSContext* aCx, nsIPrincipal** aPrincipal) +{ + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateNullPrincipal(JS::Handle aOriginAttributes, + JSContext* aCx, nsIPrincipal** aPrincipal) +{ + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr 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 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 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 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 prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } // static diff --git a/caps/nsScriptSecurityManager.h b/caps/nsScriptSecurityManager.h index edd5e1dd84..342e9b84ac 100644 --- a/caps/nsScriptSecurityManager.h +++ b/caps/nsScriptSecurityManager.h @@ -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(); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index b1da483802..2864696887 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -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 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 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 aAsyncStack, + JS::Handle 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 asyncStack(aCx, mAsyncStack.toObjectOrNull()); + JS::Rooted asyncCause(aCx, mAsyncCause.toString()); + JS::Rooted parentFrame(aCx); + if (!JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) { + JS_ClearPendingException(aCx); + } else { + stackFrame.mAsyncParent = parentFrame; + } + } JS::Rooted newStack(aCx); if (ToJSValue(aCx, stackFrame, &newStack)) { @@ -14164,13 +14152,17 @@ private: nsString mFunctionName; nsString mFileName; uint32_t mLineNumber; + JS::PersistentRooted mAsyncStack; + JS::PersistentRooted 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 aAsyncStack, + JS::Handle aAsyncCause) { bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers(); @@ -14179,7 +14171,8 @@ nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, mozilla::UniquePtr marker = MakeUnique(this, "Javascript", aReason, aFunctionName, aFilename, - aLineNumber); + aLineNumber, aAsyncStack, + aAsyncCause); AddProfileTimelineMarker(Move(marker)); } mJSRunToCompletionDepth++; diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index ef79677cfd..1883db944a 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -59,7 +59,6 @@ namespace mozilla { namespace dom { class EventTarget; -class URLSearchParams; } } @@ -813,9 +812,6 @@ protected: nsCOMPtr mFailedChannel; uint32_t mFailedLoadType; - // window.location.searchParams is updated in sync with this object. - nsRefPtr 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. diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 835f47f359..964dd41580 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -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(); /** diff --git a/docshell/test/browser/browser_timelineMarkers-frame-04.js b/docshell/test/browser/browser_timelineMarkers-frame-04.js index 7a6278a1da..aadec05f35 100644 --- a/docshell/test/browser/browser_timelineMarkers-frame-04.js +++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js @@ -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); diff --git a/docshell/test/browser/timelineMarkers-04.html b/docshell/test/browser/timelineMarkers-04.html index ecb25cadb1..8299983874 100644 --- a/docshell/test/browser/timelineMarkers-04.html +++ b/docshell/test/browser/timelineMarkers-04.html @@ -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); + diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index 182b2903ec..75b9a71a8d 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -600,7 +600,8 @@ AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx, void AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction, - JSScript* aScript) + JSScript* aScript, JS::Handle aAsyncStack, + JS::Handle aAsyncCause) { JS::Rooted 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 asyncCauseValue(aCx, aAsyncCause ? StringValue(aAsyncCause) : + JS::NullValue()); docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason, functionNameChars, filename.BeginReading(), - lineNumber); + lineNumber, aAsyncStack, + asyncCauseValue); } } diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index 80ef01fa15..caf833eb2b 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -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 aAsyncStack, + JS::Handle 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 aAsyncStack, + JS::Handle 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 aAsyncStack, + JS::Handle aAsyncCause); const char* mReason; }; diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index ded1e68e85..388527586b 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -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; diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index 40310892ef..26f66ac719 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -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 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 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 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 docShell = aWindow->GetDocShell(); + if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { nsCOMPtr 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 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 piWindow = doc->GetWindow(); + NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE); - // first, get the frame element this window is inside. - nsCOMPtr piWindow = docShell->GetWindow(); - NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE); + nsCOMPtr 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 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 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 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(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 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 window = GetCurrentWindow(aRootContent); NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); - NS_IF_ADDREF(*aResultContent = - GetRootForFocus(window, aRootContent->GetComposedDoc(), - false, true)); + + nsCOMPtr 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 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 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 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 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 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 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 curItem = aItem; - while (curItem) { - nsCOMPtr 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 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 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 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 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 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 doc; - nsCOMPtr 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 curItem = startDocShell.get(); - nsCOMPtr 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 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 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 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 window = subdoc->GetWindow(); + return GetRootForFocus(window, subdoc, true, true); } void diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h index 3886cd1fdd..e0e9fb1f00 100644 --- a/dom/base/nsFocusManager.h +++ b/dom/base/nsFocusManager.h @@ -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