Backed out 16 changesets (bug 1770944) as req by asuth.

Backed out changeset 61af32f40777 (bug 1770944)
Backed out changeset 4ff0c45db93b (bug 1770944)
Backed out changeset 8a217eff7bcd (bug 1770944)
Backed out changeset 6435f48c96bf (bug 1770944)
Backed out changeset 0d2432765ca0 (bug 1770944)
Backed out changeset 58e02566db85 (bug 1770944)
Backed out changeset 0a8c4c2460ee (bug 1770944)
Backed out changeset 9416bafd9982 (bug 1770944)
Backed out changeset 79de4f83fe2e (bug 1770944)
Backed out changeset 63ac518aceb0 (bug 1770944)
Backed out changeset 14952f872b77 (bug 1770944)
Backed out changeset f65e0967ad75 (bug 1770944)
Backed out changeset bd53c42038f7 (bug 1770944)
Backed out changeset 36c378ba8212 (bug 1770944)
Backed out changeset 9ba54ab06348 (bug 1770944)
Backed out changeset fb5a54b3cbe9 (bug 1770944)
This commit is contained in:
Narcis Beleuzu 2024-02-23 21:11:08 +02:00
parent 57c8f700d1
commit 7eae8c1064
99 changed files with 3020 additions and 273 deletions

View file

@ -471,6 +471,17 @@ export class ContextMenuChild extends JSWindowActorChild {
return this.contentWindow.HTMLTextAreaElement.isInstance(node);
}
/**
* Check if we are in the parent process and the current iframe is the RDM iframe.
*/
_isTargetRDMFrame(node) {
return (
Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT &&
node.tagName === "iframe" &&
node.hasAttribute("mozbrowser")
);
}
_isSpellCheckEnabled(aNode) {
// We can always force-enable spellchecking on textboxes
if (this._isTargetATextBox(aNode)) {
@ -534,6 +545,12 @@ export class ContextMenuChild extends JSWindowActorChild {
return;
}
if (this._isTargetRDMFrame(aEvent.composedTarget)) {
// The target is in the DevTools RDM iframe, a proper context menu event
// will be created from the RDM browser.
return;
}
let doc = aEvent.composedTarget.ownerDocument;
let {
mozDocumentURIIfNotForErrorPages: docLocation,

View file

@ -874,6 +874,9 @@ add_task(async function checkAllTheFiles() {
// Wait for all manifest to be parsed
await PerfTestHelpers.throttledMapPromises(manifestURIs, parseManifest);
for (let jsm of Components.manager.getComponentJSMs()) {
gReferencesFromCode.set(jsm, null);
}
for (let esModule of Components.manager.getComponentESModules()) {
gReferencesFromCode.set(esModule, null);
}

View file

@ -23,6 +23,7 @@ dom/base/
dom/battery/
dom/bindings/
dom/broadcastchannel/
dom/browser-element/
dom/cache/
dom/canvas/
dom/clients/

View file

@ -1190,6 +1190,13 @@ BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) {
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetIsInIsolatedMozBrowserElement(
bool* aIsInIsolatedMozBrowserElement) {
*aIsInIsolatedMozBrowserElement = IsInIsolatedMozBrowserElement();
return NS_OK;
}
nsresult BasePrincipal::GetAddonPolicy(
extensions::WebExtensionPolicy** aResult) {
AssertIsOnMainThread();

View file

@ -163,6 +163,8 @@ class BasePrincipal : public nsJSPrincipals {
NS_IMETHOD GetIsIpAddress(bool* aIsIpAddress) override;
NS_IMETHOD GetIsLocalIpAddress(bool* aIsIpAddress) override;
NS_IMETHOD GetIsOnion(bool* aIsOnion) override;
NS_IMETHOD GetIsInIsolatedMozBrowserElement(
bool* aIsInIsolatedMozBrowserElement) final;
NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
NS_IMETHOD GetSiteOrigin(nsACString& aSiteOrigin) final;
@ -248,6 +250,9 @@ class BasePrincipal : public nsJSPrincipals {
uint32_t PrivateBrowsingId() const {
return mOriginAttributes.mPrivateBrowsingId;
}
bool IsInIsolatedMozBrowserElement() const {
return mOriginAttributes.mInIsolatedMozBrowser;
}
PrincipalKind Kind() const { return mKind; }

View file

@ -229,6 +229,10 @@ void OriginAttributes::CreateSuffix(nsACString& aStr) const {
// naming.
//
if (mInIsolatedMozBrowser) {
params.Set(u"inBrowser"_ns, u"1"_ns);
}
if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
value.Truncate();
value.AppendInt(mUserContextId);
@ -331,6 +335,7 @@ bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) {
return false;
}
mInIsolatedMozBrowser = true;
return true;
}

View file

@ -17,6 +17,10 @@ class OriginAttributes : public dom::OriginAttributesDictionary {
public:
OriginAttributes() = default;
explicit OriginAttributes(bool aInIsolatedMozBrowser) {
mInIsolatedMozBrowser = aInIsolatedMozBrowser;
}
explicit OriginAttributes(const OriginAttributesDictionary& aOther)
: OriginAttributesDictionary(aOther) {}
@ -70,7 +74,8 @@ class OriginAttributes : public dom::OriginAttributesDictionary {
}
[[nodiscard]] bool EqualsIgnoringFPD(const OriginAttributes& aOther) const {
return mUserContextId == aOther.mUserContextId &&
return mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
mUserContextId == aOther.mUserContextId &&
mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId;
}
@ -154,6 +159,11 @@ class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary {
// Performs a match of |aAttrs| against this pattern.
bool Matches(const OriginAttributes& aAttrs) const {
if (mInIsolatedMozBrowser.WasPassed() &&
mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) {
return false;
}
if (mUserContextId.WasPassed() &&
mUserContextId.Value() != aAttrs.mUserContextId) {
return false;
@ -217,6 +227,12 @@ class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary {
}
bool Overlaps(const OriginAttributesPattern& aOther) const {
if (mInIsolatedMozBrowser.WasPassed() &&
aOther.mInIsolatedMozBrowser.WasPassed() &&
mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) {
return false;
}
if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
mUserContextId.Value() != aOther.mUserContextId.Value()) {
return false;

View file

@ -586,6 +586,17 @@ interface nsIPrincipal : nsISupports
*/
[infallible] readonly attribute unsigned long privateBrowsingId;
/**
* Returns true iff the principal is inside an isolated mozbrowser element.
* <xul:browser> is not considered to be a mozbrowser element.
* <iframe mozbrowser noisolation> does not count as isolated since
* isolation is disabled. Isolation can only be disabled if the
* containing document is chrome.
*
* May be called from any thread.
*/
[infallible] readonly attribute boolean isInIsolatedMozBrowserElement;
/**
* Returns true iff this is a null principal (corresponding to an
* unknown, hence assumed minimally privileged, security context).

View file

@ -45,6 +45,12 @@ TEST(OriginAttributes, Suffix_default)
TestSuffix(attrs);
}
TEST(OriginAttributes, Suffix_inIsolatedMozBrowser)
{
OriginAttributes attrs(true);
TestSuffix(attrs);
}
TEST(OriginAttributes, FirstPartyDomain_default)
{
bool oldFpiPref = Preferences::GetBool(FPI_PREF);

View file

@ -24,6 +24,10 @@ function checkCrossOrigin(a, b) {
function checkOriginAttributes(prin, attrs, suffix) {
attrs = attrs || {};
Assert.equal(
prin.originAttributes.inIsolatedMozBrowser,
attrs.inIsolatedMozBrowser || false
);
Assert.equal(prin.originSuffix, suffix || "");
Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), suffix || "");
Assert.ok(
@ -55,6 +59,9 @@ function printAttrs(name, attrs) {
"\tuserContextId: " +
attrs.userContextId +
",\n" +
"\tinIsolatedMozBrowser: " +
attrs.inIsolatedMozBrowser +
",\n" +
"\tprivateBrowsingId: '" +
attrs.privateBrowsingId +
"',\n" +
@ -69,6 +76,10 @@ function checkValues(attrs, values) {
// printAttrs("attrs", attrs);
// printAttrs("values", values);
Assert.equal(attrs.userContextId, values.userContextId || 0);
Assert.equal(
attrs.inIsolatedMozBrowser,
values.inIsolatedMozBrowser || false
);
Assert.equal(attrs.privateBrowsingId, values.privateBrowsingId || "");
Assert.equal(attrs.firstPartyDomain, values.firstPartyDomain || "");
}
@ -130,6 +141,26 @@ function run_test() {
// Test origin attributes.
//
// Just browser.
var exampleOrg_browser = ssm.createContentPrincipal(
makeURI("http://example.org"),
{ inIsolatedMozBrowser: true }
);
var nullPrin_browser = ssm.createNullPrincipal({
inIsolatedMozBrowser: true,
});
checkOriginAttributes(
exampleOrg_browser,
{ inIsolatedMozBrowser: true },
"^inBrowser=1"
);
checkOriginAttributes(
nullPrin_browser,
{ inIsolatedMozBrowser: true },
"^inBrowser=1"
);
Assert.equal(exampleOrg_browser.origin, "http://example.org^inBrowser=1");
// First party Uri
var exampleOrg_firstPartyDomain = ssm.createContentPrincipal(
makeURI("http://example.org"),
@ -175,6 +206,7 @@ function run_test() {
);
// Check that all of the above are cross-origin.
checkCrossOrigin(exampleOrg_browser, nullPrin_browser);
checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg);
checkCrossOrigin(exampleOrg_userContext, exampleOrg);
@ -213,6 +245,7 @@ function run_test() {
var tests = [
["", {}],
["^userContextId=3", { userContextId: 3 }],
["^inBrowser=1", { inIsolatedMozBrowser: true }],
["^firstPartyDomain=example.org", { firstPartyDomain: "example.org" }],
];

View file

@ -129,7 +129,8 @@ class ChromeUtils {
static bool IsOriginAttributesEqualIgnoringFPD(
const dom::OriginAttributesDictionary& aA,
const dom::OriginAttributesDictionary& aB) {
return aA.mUserContextId == aB.mUserContextId &&
return aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
aA.mUserContextId == aB.mUserContextId &&
aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
}

View file

@ -90,6 +90,7 @@ class nsIDOMXULSelectControlElement;
class nsIDOMXULSelectControlItemElement;
class nsIFrame;
class nsIHTMLCollection;
class nsIMozBrowserFrame;
class nsIPrincipal;
class nsIScreen;
class nsIScrollableFrame;
@ -457,6 +458,16 @@ class Element : public FragmentOrElement {
*/
virtual bool IsInteractiveHTMLContent() const;
/**
* Returns |this| as an nsIMozBrowserFrame* if the element is a frame or
* iframe element.
*
* We have this method, rather than using QI, so that we can use it during
* the servo traversal, where we can't QI DOM nodes because of non-thread-safe
* refcounts.
*/
virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() { return nullptr; }
/**
* Is the attribute named aAttribute a mapped attribute?
*/

View file

@ -13,6 +13,7 @@
#include "nsFrameLoaderOwner.h"
#include "nsQueryObject.h"
#include "xpcpublic.h"
#include "nsIMozBrowserFrame.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/dom/ChromeMessageSender.h"
#include "mozilla/dom/Document.h"
@ -99,6 +100,15 @@ InProcessBrowserChildMessageManager::InProcessBrowserChildMessageManager(
mOwner(aOwner),
mChromeMessageManager(aChrome) {
mozilla::HoldJSObjects(this);
// If owner corresponds to an <iframe mozbrowser>, we'll have to tweak our
// GetEventTargetParent implementation.
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
if (browserFrame) {
mIsBrowserFrame = browserFrame->GetReallyIsBrowser();
} else {
mIsBrowserFrame = false;
}
}
InProcessBrowserChildMessageManager::~InProcessBrowserChildMessageManager() {
@ -226,7 +236,19 @@ void InProcessBrowserChildMessageManager::GetEventTargetParent(
return;
}
aVisitor.SetParentTarget(mOwner, false);
if (mIsBrowserFrame &&
(!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
if (mOwner) {
if (nsPIDOMWindowInner* innerWindow =
mOwner->OwnerDoc()->GetInnerWindow()) {
// 'this' is already a "chrome handler", so we consider window's
// parent target to be part of that same part of the event path.
aVisitor.SetParentTarget(innerWindow->GetParentTarget(), false);
}
}
} else {
aVisitor.SetParentTarget(mOwner, false);
}
}
class nsAsyncScriptLoad : public Runnable {

View file

@ -108,6 +108,9 @@ class InProcessBrowserChildMessageManager final
RefPtr<nsDocShell> mDocShell;
bool mLoadingScript;
// Is this the message manager for an in-process <iframe mozbrowser>? This
// affects where events get sent, so it affects GetEventTargetParent.
bool mIsBrowserFrame;
bool mPreventEventsEscaping;
// We keep a strong reference to the frameloader after we've started

View file

@ -39,6 +39,7 @@
#include "nsSubDocumentFrame.h"
#include "nsError.h"
#include "nsIAppWindow.h"
#include "nsIMozBrowserFrame.h"
#include "nsIScriptError.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.h"
@ -261,6 +262,13 @@ static bool IsTopContent(BrowsingContext* aParent, Element* aOwner) {
return false;
}
// If we have a (deprecated) mozbrowser element, we want to start a new
// BrowsingContext tree regardless of whether the parent is chrome or content.
nsCOMPtr<nsIMozBrowserFrame> mozbrowser = aOwner->GetAsMozBrowserFrame();
if (mozbrowser && mozbrowser->GetReallyIsBrowser()) {
return true;
}
if (aParent->IsContent()) {
// If we're already in content, we may still want to create a new
// BrowsingContext tree if our element is a xul browser element with a
@ -357,8 +365,17 @@ static bool InitialLoadIsRemote(Element* aOwner) {
return false;
}
// Otherwise, we're remote if we have "remote=true" and we're a XUL element.
return (aOwner->GetNameSpaceID() == kNameSpaceID_XUL) &&
// If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
// fall back to the default.
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aOwner);
bool isMozBrowserFrame = browserFrame && browserFrame->GetReallyIsBrowser();
if (isMozBrowserFrame && !aOwner->HasAttr(nsGkAtoms::remote)) {
return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false);
}
// Otherwise, we're remote if we have "remote=true" and we're either a
// browser frame or a XUL element.
return (isMozBrowserFrame || aOwner->GetNameSpaceID() == kNameSpaceID_XUL) &&
aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
nsGkAtoms::_true, eCaseMatters);
}
@ -689,6 +706,12 @@ nsresult nsFrameLoader::ReallyStartLoadingInternal() {
// Default flags:
int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE;
// Flags for browser frame:
if (OwnerIsMozBrowserFrame()) {
flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
loadState->SetLoadFlags(flags);
loadState->SetFirstParty(false);
@ -852,6 +875,14 @@ static bool CheckDocShellType(mozilla::dom::Element* aOwnerContent,
bool isContent = aOwnerContent->AttrValueIs(kNameSpaceID_None, aAtom,
nsGkAtoms::content, eIgnoreCase);
if (!isContent) {
nsCOMPtr<nsIMozBrowserFrame> mozbrowser =
aOwnerContent->GetAsMozBrowserFrame();
if (mozbrowser) {
mozbrowser->GetMozbrowser(&isContent);
}
}
if (isContent) {
return aDocShell->ItemType() == nsIDocShellTreeItem::typeContent;
}
@ -1125,6 +1156,7 @@ bool nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size,
if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) {
os->NotifyObservers(ToSupports(this), "remote-browser-shown", nullptr);
}
ProcessPriorityManager::RemoteBrowserFrameShown(this);
}
} else {
nsIntRect dimensions;
@ -1297,6 +1329,14 @@ nsresult nsFrameLoader::SwapWithOtherRemoteLoader(
return NS_ERROR_NOT_IMPLEMENTED;
}
// Destroy browser frame scripts for content leaving a frame with browser API
if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
DestroyBrowserFrameScripts();
}
if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
aOther->DestroyBrowserFrameScripts();
}
otherBrowserParent->SetBrowserDOMWindow(browserDOMWindow);
browserParent->SetBrowserDOMWindow(otherBrowserDOMWindow);
@ -1365,6 +1405,10 @@ nsresult nsFrameLoader::SwapWithOtherRemoteLoader(
ourPresShell->BackingScaleFactorChanged();
otherPresShell->BackingScaleFactorChanged();
// Initialize browser API if needed now that owner content has changed.
InitializeBrowserAPI();
aOther->InitializeBrowserAPI();
mInSwap = aOther->mInSwap = false;
// Send an updated tab context since owner content type may have changed.
@ -1492,8 +1536,13 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
return NS_ERROR_NOT_IMPLEMENTED;
}
bool ourFullscreenAllowed = ourContent->IsXULElement();
bool otherFullscreenAllowed = otherContent->IsXULElement();
bool ourFullscreenAllowed = ourContent->IsXULElement() ||
(OwnerIsMozBrowserFrame() &&
ourContent->HasAttr(nsGkAtoms::allowfullscreen));
bool otherFullscreenAllowed =
otherContent->IsXULElement() ||
(aOther->OwnerIsMozBrowserFrame() &&
otherContent->HasAttr(nsGkAtoms::allowfullscreen));
if (ourFullscreenAllowed != otherFullscreenAllowed) {
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -1683,6 +1732,14 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
return rv;
}
// Destroy browser frame scripts for content leaving a frame with browser API
if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
DestroyBrowserFrameScripts();
}
if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
aOther->DestroyBrowserFrameScripts();
}
// Now move the docshells to the right docshell trees. Note that this
// resets their treeowners to null.
ourParentItem->RemoveChild(ourDocshell);
@ -1780,6 +1837,10 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
ourFrame->PresShell()->BackingScaleFactorChanged();
otherFrame->PresShell()->BackingScaleFactorChanged();
// Initialize browser API if needed now that owner content has changed
InitializeBrowserAPI();
aOther->InitializeBrowserAPI();
return NS_OK;
}
@ -2113,6 +2174,11 @@ void nsFrameLoader::SetOwnerContent(Element* aContent) {
}
}
bool nsFrameLoader::OwnerIsMozBrowserFrame() {
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
return browserFrame ? browserFrame->GetReallyIsBrowser() : false;
}
nsIContent* nsFrameLoader::GetParentObject() const { return mOwnerContent; }
void nsFrameLoader::AssertSafeToInit() {
@ -2272,7 +2338,16 @@ nsresult nsFrameLoader::MaybeCreateDocShell() {
MOZ_ALWAYS_SUCCEEDS(mPendingBrowsingContext->SetInitialSandboxFlags(
mPendingBrowsingContext->GetSandboxFlags()));
if (OwnerIsMozBrowserFrame()) {
// For inproc frames, set the docshell properties.
nsAutoString name;
if (mOwnerContent->GetAttr(nsGkAtoms::name, name)) {
docShell->SetName(name);
}
}
ReallyLoadFrameScripts();
InitializeBrowserAPI();
// Previously we would forcibly create the initial about:blank document for
// in-process content frames from a frame script which eagerly loaded in
@ -2513,8 +2588,11 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
// Graphics initialization code relies on having a frame for the
// remote browser case, as we can be inside a popup, which is a different
// widget.
if (!mOwnerContent->GetPrimaryFrame()) {
//
// FIXME: Ideally this should be unconditional, but we skip if for <iframe
// mozbrowser> because the old RDM ui depends on current behavior, and the
// mozbrowser frame code is scheduled for deletion, see bug 1574886.
if (!OwnerIsMozBrowserFrame() && !mOwnerContent->GetPrimaryFrame()) {
doc->FlushPendingNotifications(FlushType::Frames);
}
@ -2569,11 +2647,12 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
mPendingBrowsingContext->InitSessionHistory();
}
// <iframe mozbrowser> gets to skip these checks.
// iframes for JS plugins also get to skip these checks. We control the URL
// that gets loaded, but the load is triggered from the document containing
// the plugin.
// out of process iframes also get to skip this check.
if (!XRE_IsContentProcess()) {
if (!OwnerIsMozBrowserFrame() && !XRE_IsContentProcess()) {
if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
// Allow three exceptions to this rule :
// - about:addons so it can load remote extension options pages
@ -2738,6 +2817,7 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
}
ReallyLoadFrameScripts();
InitializeBrowserAPI();
return true;
}
@ -2973,7 +3053,7 @@ nsresult nsFrameLoader::EnsureMessageManager() {
return NS_OK;
}
if (!mIsTopLevelContent && !IsRemoteFrame() &&
if (!mIsTopLevelContent && !OwnerIsMozBrowserFrame() && !IsRemoteFrame() &&
!(mOwnerContent->IsXULElement() &&
mOwnerContent->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::forcemessagemanager,
@ -3458,6 +3538,36 @@ BrowsingContext* nsFrameLoader::GetExtantBrowsingContext() {
return mPendingBrowsingContext;
}
void nsFrameLoader::InitializeBrowserAPI() {
if (!OwnerIsMozBrowserFrame()) {
return;
}
nsresult rv = EnsureMessageManager();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
mMessageManager->LoadFrameScript(
u"chrome://global/content/BrowserElementChild.js"_ns,
/* allowDelayedLoad = */ true,
/* aRunInGlobalScope */ true, IgnoreErrors());
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
if (browserFrame) {
browserFrame->InitializeBrowserAPI();
}
}
void nsFrameLoader::DestroyBrowserFrameScripts() {
if (!OwnerIsMozBrowserFrame()) {
return;
}
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
if (browserFrame) {
browserFrame->DestroyBrowserFrameScripts();
}
}
void nsFrameLoader::StartPersistence(
BrowsingContext* aContext, nsIWebBrowserPersistDocumentReceiver* aRecv,
ErrorResult& aRv) {
@ -3563,9 +3673,9 @@ nsresult nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
nsresult nsFrameLoader::PopulateOriginContextIdsFromAttributes(
OriginAttributes& aAttr) {
// Only XUL are allowed to set context IDs
// Only XUL or mozbrowser frames are allowed to set context IDs
uint32_t namespaceID = mOwnerContent->GetNameSpaceID();
if (namespaceID != kNameSpaceID_XUL) {
if (namespaceID != kNameSpaceID_XUL && !OwnerIsMozBrowserFrame()) {
return NS_OK;
}
@ -3789,7 +3899,8 @@ bool nsFrameLoader::EnsureBrowsingContextAttached() {
// Inherit the `mFirstPartyDomain` flag from our parent document's result
// principal, if it was set.
if (parentContext->IsContent() &&
!parentDoc->NodePrincipal()->IsSystemPrincipal()) {
!parentDoc->NodePrincipal()->IsSystemPrincipal() &&
!OwnerIsMozBrowserFrame()) {
OriginAttributes docAttrs =
parentDoc->NodePrincipal()->OriginAttributesRef();
// We only want to inherit firstPartyDomain here, other attributes should
@ -3807,6 +3918,15 @@ bool nsFrameLoader::EnsureBrowsingContextAttached() {
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
// <iframe mozbrowser> is allowed to set `mozprivatebrowsing` to
// force-enable private browsing.
if (OwnerIsMozBrowserFrame()) {
if (mOwnerContent->HasAttr(nsGkAtoms::mozprivatebrowsing)) {
attrs.SyncAttributesWithPrivateBrowsing(true);
usePrivateBrowsing = true;
}
}
}
// If we've already been attached, return.

View file

@ -264,6 +264,12 @@ class nsFrameLoader final : public nsStubMutationObserver,
bool IsNetworkCreated() const { return mNetworkCreated; }
/**
* Is this a frame loader for a bona fide <iframe mozbrowser>?
* <xul:browser> is not a mozbrowser, so this is false for that case.
*/
bool OwnerIsMozBrowserFrame();
nsIContent* GetParentObject() const;
/**
@ -475,6 +481,9 @@ class nsFrameLoader final : public nsStubMutationObserver,
void AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
nsIDocShellTreeOwner* aOwner);
void InitializeBrowserAPI();
void DestroyBrowserFrameScripts();
nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
nsIURI* aURI = nullptr);

View file

@ -18,6 +18,7 @@ support-files = [
"custom_element_ep.js",
"window_nsITextInputProcessor.xhtml",
"title_window.xhtml",
"window_swapFrameLoaders.xhtml",
]
prefs = ["gfx.font_rendering.fallback.async=false"]
@ -125,6 +126,9 @@ support-files = ["../dummy.html"]
["test_range_getClientRectsAndTexts.html"]
["test_swapFrameLoaders.xhtml"]
skip-if = ["os == 'mac'"] # bug 1674413
["test_title.xhtml"]
support-files = ["file_title.xhtml"]

View file

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
Test swapFrameLoaders with different frame types and remoteness
-->
<window title="Mozilla Bug 1242644"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644"
target="_blank">Mozilla Bug 1242644</a>
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
SimpleTest.waitForExplicitFinish();
window.openDialog("window_swapFrameLoaders.xhtml", "bug1242644",
"chrome,width=600,height=600,noopener", window);
]]></script>
</window>

View file

@ -0,0 +1,223 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
Test swapFrameLoaders with different frame types and remoteness
-->
<window title="Mozilla Bug 1242644"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"><![CDATA[
["SimpleTest", "SpecialPowers", "info", "is", "ok", "add_task"].forEach(key => {
window[key] = window.arguments[0][key];
})
const NS = {
xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
html: "http://www.w3.org/1999/xhtml",
}
const TAG = {
xul: "browser",
html: "iframe", // mozbrowser
}
const SCENARIOS = [
["xul", "xul"],
["xul", "html"],
["html", "xul"],
["html", "html"],
["xul", "xul", { remote: true }],
["xul", "html", { remote: true }],
["html", "xul", { remote: true }],
["html", "html", { remote: true }],
["xul", "html", { userContextId: 2 }],
["xul", "html", { userContextId: 2, remote: true }],
];
const HEIGHTS = [
200,
400
];
function frameScript() {
/* eslint-env mozilla/frame-script */
addEventListener("load", function onLoad() {
sendAsyncMessage("test:load");
}, true);
}
// Watch for loads in new frames
window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true);
function once(target, eventName, useCapture = false) {
info("Waiting for event: '" + eventName + "' on " + target + ".");
return new Promise(resolve => {
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addMessageListener", "removeMessageListener"],
]) {
if ((add in target) && (remove in target)) {
target[add](eventName, function onEvent(...aArgs) {
info("Got event: '" + eventName + "' on " + target + ".");
target[remove](eventName, onEvent, useCapture);
resolve(aArgs);
}, useCapture);
break;
}
}
});
}
async function addFrame(type, options, height) {
let remote = options && options.remote;
let userContextId = options && options.userContextId;
let frame = document.createElementNS(NS[type], TAG[type]);
frame.setAttribute("remote", remote);
if (remote && type == "xul") {
frame.setAttribute("style", "-moz-binding: none;");
}
if (userContextId) {
frame.setAttribute("usercontextid", userContextId);
}
if (type == "html") {
frame.setAttribute("mozbrowser", "true");
frame.setAttribute("noisolation", "true");
frame.setAttribute("allowfullscreen", "true");
} else if (type == "xul") {
frame.setAttribute("type", "content");
}
let src = `data:text/html,<!doctype html>` +
`<body style="height:${height}px"/>`;
frame.setAttribute("src", src);
document.documentElement.appendChild(frame);
let mm = frame.frameLoader.messageManager;
await once(mm, "test:load");
return frame;
}
add_task(async function() {
for (let scenario of SCENARIOS) {
let [ typeA, typeB, options ] = scenario;
let heightA = HEIGHTS[0];
info(`Adding frame A, type ${typeA}, options ${JSON.stringify(options)}, height ${heightA}`);
let frameA = await addFrame(typeA, options, heightA);
let heightB = HEIGHTS[1];
info(`Adding frame B, type ${typeB}, options ${JSON.stringify(options)}, height ${heightB}`);
let frameB = await addFrame(typeB, options, heightB);
let frameScriptFactory = function(name) {
/* eslint-env mozilla/frame-script */
return `function() {
addMessageListener("ping", function() {
sendAsyncMessage("pong", "${name}");
});
addMessageListener("check-browser-api", function() {
let exists = "api" in this;
sendAsyncMessage("check-browser-api", {
exists,
running: exists && !this.api._shuttingDown,
});
});
addEventListener("pagehide", function({ inFrameSwap }) {
sendAsyncMessage("pagehide", inFrameSwap);
}, {mozSystemGroup: true});
}`;
}
// Load frame script into each frame
{
let mmA = frameA.frameLoader.messageManager;
let mmB = frameB.frameLoader.messageManager;
mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false);
mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false);
}
// Ping before swap
{
let mmA = frameA.frameLoader.messageManager;
let mmB = frameB.frameLoader.messageManager;
let inflightA = once(mmA, "pong");
let inflightB = once(mmB, "pong");
info("Ping message manager for frame A");
mmA.sendAsyncMessage("ping");
let [ { data: pongA } ] = await inflightA;
is(pongA, "A", "Frame A message manager gets reply A before swap");
info("Ping message manager for frame B");
mmB.sendAsyncMessage("ping");
let [ { data: pongB } ] = await inflightB;
is(pongB, "B", "Frame B message manager gets reply B before swap");
}
// Ping after swap using message managers acquired before
{
let mmA = frameA.frameLoader.messageManager;
let mmB = frameB.frameLoader.messageManager;
let pagehideA = once(mmA, "pagehide");
let pagehideB = once(mmB, "pagehide");
info("swapFrameLoaders");
frameA.swapFrameLoaders(frameB);
let [ { data: inFrameSwapA } ] = await pagehideA;
ok(inFrameSwapA, "Frame A got pagehide with inFrameSwap: true");
let [ { data: inFrameSwapB } ] = await pagehideB;
ok(inFrameSwapB, "Frame B got pagehide with inFrameSwap: true");
let inflightA = once(mmA, "pong");
let inflightB = once(mmB, "pong");
info("Ping message manager for frame A");
mmA.sendAsyncMessage("ping");
let [ { data: pongA } ] = await inflightA;
is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap");
info("Ping message manager for frame B");
mmB.sendAsyncMessage("ping");
let [ { data: pongB } ] = await inflightB;
is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap");
}
// Check height after swap
if (frameA.getContentDimensions) {
let { height } = await frameA.getContentDimensions();
is(height, heightB, "Frame A's content height is 400px after swap");
}
if (frameB.getContentDimensions) {
let { height } = await frameB.getContentDimensions();
is(height, heightA, "Frame B's content height is 200px after swap");
}
// Ping after swap using message managers acquired after
{
let mmA = frameA.frameLoader.messageManager;
let mmB = frameB.frameLoader.messageManager;
let inflightA = once(mmA, "pong");
let inflightB = once(mmB, "pong");
info("Ping message manager for frame A");
mmA.sendAsyncMessage("ping");
let [ { data: pongA } ] = await inflightA;
is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap");
info("Ping message manager for frame B");
mmB.sendAsyncMessage("ping");
let [ { data: pongB } ] = await inflightB;
is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
}
frameA.remove();
frameB.remove();
}
});
]]></script>
</window>

View file

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozBrowser iframe</title>
</head>
<body>
<div id="container"></div>
<script type="application/javascript">
var ifr = document.createElement("iframe");
ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser.html";
ifr.onload = function() { alert("DONE"); };
var domParent = document.getElementById("container");
domParent.appendChild(ifr);
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozBrowser iframe</title>
</head>
<body>
<div id="container"></div>
<script type="application/javascript">
var ifr = document.createElement("iframe");
ifr.setAttribute("mozbrowser", true);
ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser2.html";
ifr.onload = function() { alert("DONE"); };
var domParent = document.getElementById("container");
domParent.appendChild(ifr);
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozBrowser iframe</title>
</head>
<body>
<script type="application/javascript">
var bc = new BroadcastChannel("foobar");
bc.postMessage("This is wrong!");
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozBrowser iframe</title>
</head>
<body>
<script type="application/javascript">
var bc = new BroadcastChannel("foobar");
bc.postMessage("This is wrong!");
</script>
</body>
</html>

View file

@ -4,6 +4,10 @@ support-files = [
"broadcastchannel_sharedWorker.js",
"broadcastchannel_worker_alive.js",
"!/dom/events/test/event_leak_utils.js",
"file_mozbrowser.html",
"file_mozbrowser2.html",
"iframe_mozbrowser.html",
"iframe_mozbrowser2.html",
"testUrl1_bfcache.html",
"testUrl2_bfcache.html",
]

View file

@ -0,0 +1,42 @@
/* 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/. */
/* eslint-env mozilla/frame-script */
/* global api, CopyPasteAssistent */
"use strict";
function debug(msg) {
// dump("BrowserElementChild - " + msg + "\n");
}
var BrowserElementIsReady;
debug(`Might load BE scripts: BEIR: ${BrowserElementIsReady}`);
if (!BrowserElementIsReady) {
debug("Loading BE scripts");
if (!("BrowserElementIsPreloaded" in this)) {
Services.scriptloader.loadSubScript(
"chrome://global/content/BrowserElementChildPreload.js",
this
);
}
function onDestroy() {
removeMessageListener("browser-element-api:destroy", onDestroy);
if (api) {
api.destroy();
}
BrowserElementIsReady = false;
}
addMessageListener("browser-element-api:destroy", onDestroy);
BrowserElementIsReady = true;
} else {
debug("BE already loaded, abort");
}
sendAsyncMessage("browser-element-api:call", { msg_name: "hello" });

View file

@ -0,0 +1,290 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* eslint-env mozilla/frame-script */
function debug(msg) {
// dump("BrowserElementChildPreload - " + msg + "\n");
}
debug("loaded");
var BrowserElementIsReady;
var { BrowserElementPromptService } = ChromeUtils.import(
"resource://gre/modules/BrowserElementPromptService.jsm"
);
function sendAsyncMsg(msg, data) {
// Ensure that we don't send any messages before BrowserElementChild.js
// finishes loading.
if (!BrowserElementIsReady) {
return;
}
if (!data) {
data = {};
}
data.msg_name = msg;
sendAsyncMessage("browser-element-api:call", data);
}
var LISTENED_EVENTS = [
// This listens to unload events from our message manager, but /not/ from
// the |content| window. That's because the window's unload event doesn't
// bubble, and we're not using a capturing listener. If we'd used
// useCapture == true, we /would/ hear unload events from the window, which
// is not what we want!
{ type: "unload", useCapture: false, wantsUntrusted: false },
];
/**
* The BrowserElementChild implements one half of <iframe mozbrowser>.
* (The other half is, unsurprisingly, BrowserElementParent.)
*
* This script is injected into an <iframe mozbrowser> via
* nsIMessageManager::LoadFrameScript().
*
* Our job here is to listen for events within this frame and bubble them up to
* the parent process.
*/
var global = this;
function BrowserElementChild() {
// Maps outer window id --> weak ref to window. Used by modal dialog code.
this._windowIDDict = {};
this._init();
}
BrowserElementChild.prototype = {
_init() {
debug("Starting up.");
BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);
this._shuttingDown = false;
LISTENED_EVENTS.forEach(event => {
addEventListener(
event.type,
this,
event.useCapture,
event.wantsUntrusted
);
});
addMessageListener("browser-element-api:call", this);
},
/**
* Shut down the frame's side of the browser API. This is called when:
* - our BrowserChildGlobal starts to die
* - the content is moved to frame without the browser API
* This is not called when the page inside |content| unloads.
*/
destroy() {
debug("Destroying");
this._shuttingDown = true;
BrowserElementPromptService.unmapWindowToBrowserElementChild(content);
LISTENED_EVENTS.forEach(event => {
removeEventListener(
event.type,
this,
event.useCapture,
event.wantsUntrusted
);
});
removeMessageListener("browser-element-api:call", this);
},
handleEvent(event) {
switch (event.type) {
case "unload":
this.destroy(event);
break;
}
},
receiveMessage(message) {
let self = this;
let mmCalls = {
"unblock-modal-prompt": this._recvStopWaiting,
};
if (message.data.msg_name in mmCalls) {
return mmCalls[message.data.msg_name].apply(self, arguments);
}
return undefined;
},
get _windowUtils() {
return content.document.defaultView.windowUtils;
},
_tryGetInnerWindowID(win) {
try {
return win.windowGlobalChild.innerWindowId;
} catch (e) {
return null;
}
},
/**
* Show a modal prompt. Called by BrowserElementPromptService.
*/
showModalPrompt(win, args) {
args.windowID = {
outer: win.docShell.outerWindowID,
inner: this._tryGetInnerWindowID(win),
};
sendAsyncMsg("showmodalprompt", args);
let returnValue = this._waitForResult(win);
if (
args.promptType == "prompt" ||
args.promptType == "confirm" ||
args.promptType == "custom-prompt"
) {
return returnValue;
}
return undefined;
},
/**
* Spin in a nested event loop until we receive a unblock-modal-prompt message for
* this window.
*/
_waitForResult(win) {
debug("_waitForResult(" + win + ")");
let utils = win.windowUtils;
let outerWindowID = win.docShell.outerWindowID;
let innerWindowID = this._tryGetInnerWindowID(win);
if (innerWindowID === null) {
// I have no idea what waiting for a result means when there's no inner
// window, so let's just bail.
debug("_waitForResult: No inner window. Bailing.");
return undefined;
}
this._windowIDDict[outerWindowID] = Cu.getWeakReference(win);
debug(
"Entering modal state (outerWindowID=" +
outerWindowID +
", " +
"innerWindowID=" +
innerWindowID +
")"
);
utils.enterModalState();
// We'll decrement win.modalDepth when we receive a unblock-modal-prompt message
// for the window.
if (!win.modalDepth) {
win.modalDepth = 0;
}
win.modalDepth++;
let origModalDepth = win.modalDepth;
debug("Nested event loop - begin");
Services.tm.spinEventLoopUntil(
"BrowserElementChildPreload.js:_waitForResult",
() => {
// Bail out of the loop if the inner window changed; that means the
// window navigated. Bail out when we're shutting down because otherwise
// we'll leak our window.
if (this._tryGetInnerWindowID(win) !== innerWindowID) {
debug(
"_waitForResult: Inner window ID changed " +
"while in nested event loop."
);
return true;
}
return win.modalDepth !== origModalDepth || this._shuttingDown;
}
);
debug("Nested event loop - finish");
if (win.modalDepth == 0) {
delete this._windowIDDict[outerWindowID];
}
// If we exited the loop because the inner window changed, then bail on the
// modal prompt.
if (innerWindowID !== this._tryGetInnerWindowID(win)) {
throw Components.Exception(
"Modal state aborted by navigation",
Cr.NS_ERROR_NOT_AVAILABLE
);
}
let returnValue = win.modalReturnValue;
delete win.modalReturnValue;
if (!this._shuttingDown) {
utils.leaveModalState();
}
debug(
"Leaving modal state (outerID=" +
outerWindowID +
", " +
"innerID=" +
innerWindowID +
")"
);
return returnValue;
},
_recvStopWaiting(msg) {
let outerID = msg.json.windowID.outer;
let innerID = msg.json.windowID.inner;
let returnValue = msg.json.returnValue;
debug(
"recvStopWaiting(outer=" +
outerID +
", inner=" +
innerID +
", returnValue=" +
returnValue +
")"
);
if (!this._windowIDDict[outerID]) {
debug("recvStopWaiting: No record of outer window ID " + outerID);
return;
}
let win = this._windowIDDict[outerID].get();
if (!win) {
debug("recvStopWaiting, but window is gone\n");
return;
}
if (innerID !== this._tryGetInnerWindowID(win)) {
debug("recvStopWaiting, but inner ID has changed\n");
return;
}
debug("recvStopWaiting " + win);
win.modalReturnValue = returnValue;
win.modalDepth--;
},
};
var api = new BrowserElementChild();

View file

@ -0,0 +1,276 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* BrowserElementParent injects script to listen for certain events in the
* child. We then listen to messages from the child script and take
* appropriate action here in the parent.
*/
const { BrowserElementPromptService } = ChromeUtils.import(
"resource://gre/modules/BrowserElementPromptService.jsm"
);
function debug(msg) {
// dump("BrowserElementParent - " + msg + "\n");
}
function handleWindowEvent(e) {
if (this._browserElementParents) {
let beps = ChromeUtils.nondeterministicGetWeakMapKeys(
this._browserElementParents
);
beps.forEach(bep => bep._handleOwnerEvent(e));
}
}
function BrowserElementParent() {
debug("Creating new BrowserElementParent object");
}
BrowserElementParent.prototype = {
classDescription: "BrowserElementAPI implementation",
classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
contractID: "@mozilla.org/dom/browser-element-api;1",
QueryInterface: ChromeUtils.generateQI([
"nsIBrowserElementAPI",
"nsISupportsWeakReference",
]),
setFrameLoader(frameLoader) {
debug("Setting frameLoader");
this._frameLoader = frameLoader;
this._frameElement = frameLoader.ownerElement;
if (!this._frameElement) {
debug("No frame element?");
return;
}
// Listen to visibilitychange on the iframe's owner window, and forward
// changes down to the child. We want to do this while registering as few
// visibilitychange listeners on _window as possible, because such a listener
// may live longer than this BrowserElementParent object.
//
// To accomplish this, we register just one listener on the window, and have
// it reference a WeakMap whose keys are all the BrowserElementParent objects
// on the window. Then when the listener fires, we iterate over the
// WeakMap's keys (which we can do, because we're chrome) to notify the
// BrowserElementParents.
if (!this._window._browserElementParents) {
this._window._browserElementParents = new WeakMap();
let handler = handleWindowEvent.bind(this._window);
let windowEvents = ["visibilitychange"];
for (let event of windowEvents) {
Services.els.addSystemEventListener(
this._window,
event,
handler,
/* useCapture = */ true
);
}
}
this._window._browserElementParents.set(this, null);
// Insert ourself into the prompt service.
BrowserElementPromptService.mapFrameToBrowserElementParent(
this._frameElement,
this
);
this._setupMessageListener();
},
destroyFrameScripts() {
debug("Destroying frame scripts");
this._mm.sendAsyncMessage("browser-element-api:destroy");
},
_setupMessageListener() {
this._mm = this._frameLoader.messageManager;
this._mm.addMessageListener("browser-element-api:call", this);
},
receiveMessage(aMsg) {
if (!this._isAlive()) {
return undefined;
}
// Messages we receive are handed to functions which take a (data) argument,
// where |data| is the message manager's data object.
// We use a single message and dispatch to various function based
// on data.msg_name
let mmCalls = {
hello: this._recvHello,
};
let mmSecuritySensitiveCalls = {
showmodalprompt: this._handleShowModalPrompt,
};
if (aMsg.data.msg_name in mmCalls) {
return mmCalls[aMsg.data.msg_name].apply(this, arguments);
} else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) {
return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(
this,
arguments
);
}
return undefined;
},
_removeMessageListener() {
this._mm.removeMessageListener("browser-element-api:call", this);
},
/**
* You shouldn't touch this._frameElement or this._window if _isAlive is
* false. (You'll likely get an exception if you do.)
*/
_isAlive() {
return (
!Cu.isDeadWrapper(this._frameElement) &&
!Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
!Cu.isDeadWrapper(this._frameElement.ownerGlobal)
);
},
get _window() {
return this._frameElement.ownerGlobal;
},
_sendAsyncMsg(msg, data) {
try {
if (!data) {
data = {};
}
data.msg_name = msg;
this._mm.sendAsyncMessage("browser-element-api:call", data);
} catch (e) {
return false;
}
return true;
},
_recvHello() {
debug("recvHello");
// Inform our child if our owner element's document is invisible. Note
// that we must do so here, rather than in the BrowserElementParent
// constructor, because the BrowserElementChild may not be initialized when
// we run our constructor.
if (this._window.document.hidden) {
this._ownerVisibilityChange();
}
},
/**
* Fire either a vanilla or a custom event, depending on the contents of
* |data|.
*/
_fireEventFromMsg(data) {
let detail = data.json;
let name = detail.msg_name;
// For events that send a "_payload_" property, we just want to transmit
// this in the event.
if ("_payload_" in detail) {
detail = detail._payload_;
}
debug("fireEventFromMsg: " + name + ", " + JSON.stringify(detail));
let evt = this._createEvent(name, detail, /* cancelable = */ false);
this._frameElement.dispatchEvent(evt);
},
_handleShowModalPrompt(data) {
// Fire a showmodalprmopt event on the iframe. When this method is called,
// the child is spinning in a nested event loop waiting for an
// unblock-modal-prompt message.
//
// If the embedder calls preventDefault() on the showmodalprompt event,
// we'll block the child until event.detail.unblock() is called.
//
// Otherwise, if preventDefault() is not called, we'll send the
// unblock-modal-prompt message to the child as soon as the event is done
// dispatching.
let detail = data.json;
debug("handleShowPrompt " + JSON.stringify(detail));
// Strip off the windowID property from the object we send along in the
// event.
let windowID = detail.windowID;
delete detail.windowID;
debug("Event will have detail: " + JSON.stringify(detail));
let evt = this._createEvent(
"showmodalprompt",
detail,
/* cancelable = */ true
);
let self = this;
let unblockMsgSent = false;
function sendUnblockMsg() {
if (unblockMsgSent) {
return;
}
unblockMsgSent = true;
// We don't need to sanitize evt.detail.returnValue (e.g. converting the
// return value of confirm() to a boolean); Gecko does that for us.
let data = { windowID, returnValue: evt.detail.returnValue };
self._sendAsyncMsg("unblock-modal-prompt", data);
}
Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: "unblock" });
this._frameElement.dispatchEvent(evt);
if (!evt.defaultPrevented) {
// Unblock the inner frame immediately. Otherwise we'll unblock upon
// evt.detail.unblock().
sendUnblockMsg();
}
},
_createEvent(evtName, detail, cancelable) {
// This will have to change if we ever want to send a CustomEvent with null
// detail. For now, it's OK.
if (detail !== undefined && detail !== null) {
detail = Cu.cloneInto(detail, this._window);
return new this._window.CustomEvent("mozbrowser" + evtName, {
bubbles: true,
cancelable,
detail,
});
}
return new this._window.Event("mozbrowser" + evtName, {
bubbles: true,
cancelable,
});
},
_handleOwnerEvent(evt) {
switch (evt.type) {
case "visibilitychange":
this._ownerVisibilityChange();
break;
}
},
/**
* Called when the visibility of the window which owns this iframe changes.
*/
_ownerVisibilityChange() {
let bc = this._frameLoader?.browsingContext;
if (bc) {
bc.isActive = !this._window.document.hidden;
}
},
};
var EXPORTED_SYMBOLS = ["BrowserElementParent"];

View file

@ -0,0 +1,720 @@
/* 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/. */
/* vim: set ft=javascript : */
"use strict";
var Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
var EXPORTED_SYMBOLS = ["BrowserElementPromptService"];
function debug(msg) {
// dump("BrowserElementPromptService - " + msg + "\n");
}
function BrowserElementPrompt(win, browserElementChild) {
this._win = win;
this._browserElementChild = browserElementChild;
}
BrowserElementPrompt.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]),
alert(title, text) {
this._browserElementChild.showModalPrompt(this._win, {
promptType: "alert",
title,
message: text,
returnValue: undefined,
});
},
alertCheck(title, text, checkMsg, checkState) {
// Treat this like a normal alert() call, ignoring the checkState. The
// front-end can do its own suppression of the alert() if it wants.
this.alert(title, text);
},
confirm(title, text) {
return this._browserElementChild.showModalPrompt(this._win, {
promptType: "confirm",
title,
message: text,
returnValue: undefined,
});
},
confirmCheck(title, text, checkMsg, checkState) {
return this.confirm(title, text);
},
// Each button is described by an object with the following schema
// {
// string messageType, // 'builtin' or 'custom'
// string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave',
// // 'revert' or a string from caller if messageType was 'custom'.
// }
//
// Expected result from embedder:
// {
// int button, // Index of the button that user pressed.
// boolean checked, // True if the check box is checked.
// }
confirmEx(
title,
text,
buttonFlags,
button0Title,
button1Title,
button2Title,
checkMsg,
checkState
) {
let buttonProperties = this._buildConfirmExButtonProperties(
buttonFlags,
button0Title,
button1Title,
button2Title
);
let defaultReturnValue = { selectedButton: buttonProperties.defaultButton };
if (checkMsg) {
defaultReturnValue.checked = checkState.value;
}
let ret = this._browserElementChild.showModalPrompt(this._win, {
promptType: "custom-prompt",
title,
message: text,
defaultButton: buttonProperties.defaultButton,
buttons: buttonProperties.buttons,
showCheckbox: !!checkMsg,
checkboxMessage: checkMsg,
checkboxCheckedByDefault: !!checkState.value,
returnValue: defaultReturnValue,
});
if (checkMsg) {
checkState.value = ret.checked;
}
return buttonProperties.indexToButtonNumberMap[ret.selectedButton];
},
prompt(title, text, value, checkMsg, checkState) {
let rv = this._browserElementChild.showModalPrompt(this._win, {
promptType: "prompt",
title,
message: text,
initialValue: value.value,
returnValue: null,
});
value.value = rv;
// nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt,
// and false if the user pressed "Cancel".
//
// BrowserElementChild returns null for "Cancel" and returns the string the
// user entered otherwise.
return rv !== null;
},
promptUsernameAndPassword(title, text, username, password) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
promptPassword(title, text, password) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
select(title, text, aSelectList, aOutSelection) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
_buildConfirmExButtonProperties(
buttonFlags,
button0Title,
button1Title,
button2Title
) {
let r = {
defaultButton: -1,
buttons: [],
// This map is for translating array index to the button number that
// is recognized by Gecko. This shouldn't be exposed to embedder.
indexToButtonNumberMap: [],
};
let defaultButton = 0; // Default to Button 0.
if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) {
defaultButton = 1;
} else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) {
defaultButton = 2;
}
// Properties of each button.
let buttonPositions = [
Ci.nsIPrompt.BUTTON_POS_0,
Ci.nsIPrompt.BUTTON_POS_1,
Ci.nsIPrompt.BUTTON_POS_2,
];
function buildButton(buttonTitle, buttonNumber) {
let ret = {};
let buttonPosition = buttonPositions[buttonNumber];
let mask = 0xff * buttonPosition; // 8 bit mask
let titleType = (buttonFlags & mask) / buttonPosition;
ret.messageType = "builtin";
switch (titleType) {
case Ci.nsIPrompt.BUTTON_TITLE_OK:
ret.message = "ok";
break;
case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
ret.message = "cancel";
break;
case Ci.nsIPrompt.BUTTON_TITLE_YES:
ret.message = "yes";
break;
case Ci.nsIPrompt.BUTTON_TITLE_NO:
ret.message = "no";
break;
case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
ret.message = "save";
break;
case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
ret.message = "dontsave";
break;
case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
ret.message = "revert";
break;
case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
ret.message = buttonTitle;
ret.messageType = "custom";
break;
default:
// This button is not shown.
return;
}
// If this is the default button, set r.defaultButton to
// the index of this button in the array. This value is going to be
// exposed to the embedder.
if (defaultButton === buttonNumber) {
r.defaultButton = r.buttons.length;
}
r.buttons.push(ret);
r.indexToButtonNumberMap.push(buttonNumber);
}
buildButton(button0Title, 0);
buildButton(button1Title, 1);
buildButton(button2Title, 2);
// If defaultButton is still -1 here, it means the default button won't
// be shown.
if (r.defaultButton === -1) {
throw new Components.Exception(
"Default button won't be shown",
Cr.NS_ERROR_FAILURE
);
}
return r;
},
};
function BrowserElementAuthPrompt() {}
BrowserElementAuthPrompt.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
promptAuth: function promptAuth(channel, level, authInfo) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
asyncPromptAuth: function asyncPromptAuth(
channel,
callback,
context,
level,
authInfo
) {
debug("asyncPromptAuth");
// The cases that we don't support now.
if (
authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD
) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let frame = this._getFrameFromChannel(channel);
if (!frame) {
debug("Cannot get frame, asyncPromptAuth fail");
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let browserElementParent =
BrowserElementPromptService.getBrowserElementParentForFrame(frame);
if (!browserElementParent) {
debug("Failed to load browser element parent.");
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let consumer = {
QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
callback,
context,
cancel() {
this.callback.onAuthCancelled(this.context, false);
this.callback = null;
this.context = null;
},
};
let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
let hashKey = level + "|" + hostname + "|" + httpRealm;
let asyncPrompt = this._asyncPrompts[hashKey];
if (asyncPrompt) {
asyncPrompt.consumers.push(consumer);
return consumer;
}
asyncPrompt = {
consumers: [consumer],
channel,
authInfo,
level,
inProgress: false,
browserElementParent,
};
this._asyncPrompts[hashKey] = asyncPrompt;
this._doAsyncPrompt();
return consumer;
},
// Utilities for nsIAuthPrompt2 ----------------
_asyncPrompts: {},
_asyncPromptInProgress: new WeakMap(),
_doAsyncPrompt() {
// Find the key of a prompt whose browser element parent does not have
// async prompt in progress.
let hashKey = null;
for (let key in this._asyncPrompts) {
let prompt = this._asyncPrompts[key];
if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) {
hashKey = key;
break;
}
}
// Didn't find an available prompt, so just return.
if (!hashKey) {
return;
}
let prompt = this._asyncPrompts[hashKey];
this._asyncPromptInProgress.set(prompt.browserElementParent, true);
prompt.inProgress = true;
let self = this;
let callback = function (ok, username, password) {
debug(
"Async auth callback is called, ok = " + ok + ", username = " + username
);
// Here we got the username and password provided by embedder, or
// ok = false if the prompt was cancelled by embedder.
delete self._asyncPrompts[hashKey];
prompt.inProgress = false;
self._asyncPromptInProgress.delete(prompt.browserElementParent);
// Fill authentication information with username and password provided
// by user.
let flags = prompt.authInfo.flags;
if (username) {
if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
// Domain is separated from username by a backslash
let idx = username.indexOf("\\");
if (idx == -1) {
prompt.authInfo.username = username;
} else {
prompt.authInfo.domain = username.substring(0, idx);
prompt.authInfo.username = username.substring(idx + 1);
}
} else {
prompt.authInfo.username = username;
}
}
if (password) {
prompt.authInfo.password = password;
}
for (let consumer of prompt.consumers) {
if (!consumer.callback) {
// Not having a callback means that consumer didn't provide it
// or canceled the notification.
continue;
}
try {
if (ok) {
debug("Ok, calling onAuthAvailable to finish auth");
consumer.callback.onAuthAvailable(
consumer.context,
prompt.authInfo
);
} else {
debug("Cancelled, calling onAuthCancelled to finish auth.");
consumer.callback.onAuthCancelled(consumer.context, true);
}
} catch (e) {
/* Throw away exceptions caused by callback */
}
}
// Process the next prompt, if one is pending.
self._doAsyncPrompt();
};
let runnable = {
run() {
// Call promptAuth of browserElementParent, to show the prompt.
prompt.browserElementParent.promptAuth(
self._createAuthDetail(prompt.channel, prompt.authInfo),
callback
);
},
};
Services.tm.dispatchToMainThread(runnable);
},
_getFrameFromChannel(channel) {
let loadContext = channel.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
return loadContext.topFrameElement;
},
_createAuthDetail(channel, authInfo) {
let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
return {
host: hostname,
path: channel.URI.pathQueryRef,
realm: httpRealm,
username: authInfo.username,
isProxy: !!(authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY),
isOnlyPassword: !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD),
};
},
// The code is taken from nsLoginManagerPrompter.js, with slight
// modification for parameter name consistency here.
_getAuthTarget(channel, authInfo) {
let hostname, realm;
// If our proxy is demanding authentication, don't use the
// channel's actual destination.
if (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
if (!(channel instanceof Ci.nsIProxiedChannel)) {
throw new Error("proxy auth needs nsIProxiedChannel");
}
let info = channel.proxyInfo;
if (!info) {
throw new Error("proxy auth needs nsIProxyInfo");
}
// Proxies don't have a scheme, but we'll use "moz-proxy://"
// so that it's more obvious what the login is for.
var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
Ci.nsIIDNService
);
hostname =
"moz-proxy://" +
idnService.convertUTF8toACE(info.host) +
":" +
info.port;
realm = authInfo.realm;
if (!realm) {
realm = hostname;
}
return [hostname, realm];
}
hostname = this._getFormattedHostname(channel.URI);
// If a HTTP WWW-Authenticate header specified a realm, that value
// will be available here. If it wasn't set or wasn't HTTP, we'll use
// the formatted hostname instead.
realm = authInfo.realm;
if (!realm) {
realm = hostname;
}
return [hostname, realm];
},
/**
* Strip out things like userPass and path for display.
*/
_getFormattedHostname(uri) {
return uri.scheme + "://" + uri.hostPort;
},
};
function AuthPromptWrapper(oldImpl, browserElementImpl) {
this._oldImpl = oldImpl;
this._browserElementImpl = browserElementImpl;
}
AuthPromptWrapper.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
promptAuth(channel, level, authInfo) {
if (this._canGetParentElement(channel)) {
return this._browserElementImpl.promptAuth(channel, level, authInfo);
}
return this._oldImpl.promptAuth(channel, level, authInfo);
},
asyncPromptAuth(channel, callback, context, level, authInfo) {
if (this._canGetParentElement(channel)) {
return this._browserElementImpl.asyncPromptAuth(
channel,
callback,
context,
level,
authInfo
);
}
return this._oldImpl.asyncPromptAuth(
channel,
callback,
context,
level,
authInfo
);
},
_canGetParentElement(channel) {
try {
let context = channel.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
let frame = context.topFrameElement;
if (!frame) {
return false;
}
if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame)) {
return false;
}
return true;
} catch (e) {
return false;
}
},
};
function BrowserElementPromptFactory(toWrap) {
this._wrapped = toWrap;
}
BrowserElementPromptFactory.prototype = {
classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"),
QueryInterface: ChromeUtils.generateQI(["nsIPromptFactory"]),
_mayUseNativePrompt() {
try {
return Services.prefs.getBoolPref("browser.prompt.allowNative");
} catch (e) {
// This properity is default to true.
return true;
}
},
_getNativePromptIfAllowed(win, iid, err) {
if (this._mayUseNativePrompt()) {
return this._wrapped.getPrompt(win, iid);
}
// Not allowed, throw an exception.
throw err;
},
getPrompt(win, iid) {
// It is possible for some object to get a prompt without passing
// valid reference of window, like nsNSSComponent. In such case, we
// should just fall back to the native prompt service
if (!win) {
return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
}
if (
iid.number != Ci.nsIPrompt.number &&
iid.number != Ci.nsIAuthPrompt2.number
) {
debug(
"We don't recognize the requested IID (" +
iid +
", " +
"allowed IID: " +
"nsIPrompt=" +
Ci.nsIPrompt +
", " +
"nsIAuthPrompt2=" +
Ci.nsIAuthPrompt2 +
")"
);
return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
}
// Try to find a BrowserElementChild for the window.
let browserElementChild =
BrowserElementPromptService.getBrowserElementChildForWindow(win);
if (iid.number === Ci.nsIAuthPrompt2.number) {
debug("Caller requests an instance of nsIAuthPrompt2.");
if (browserElementChild) {
// If we are able to get a BrowserElementChild, it means that
// the auth prompt is for a mozbrowser. Therefore we don't need to
// fall back.
return new BrowserElementAuthPrompt().QueryInterface(iid);
}
// Because nsIAuthPrompt2 is called in parent process. If caller
// wants nsIAuthPrompt2 and we cannot get BrowserElementchild,
// it doesn't mean that we should fallback. It is possible that we can
// get the BrowserElementParent from nsIChannel that passed to
// functions of nsIAuthPrompt2.
if (this._mayUseNativePrompt()) {
return new AuthPromptWrapper(
this._wrapped.getPrompt(win, iid),
new BrowserElementAuthPrompt().QueryInterface(iid)
).QueryInterface(iid);
}
// Falling back is not allowed, so we don't need wrap the
// BrowserElementPrompt.
return new BrowserElementAuthPrompt().QueryInterface(iid);
}
if (!browserElementChild) {
debug(
"We can't find a browserElementChild for " + win + ", " + win.location
);
return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE);
}
debug("Returning wrapped getPrompt for " + win);
return new BrowserElementPrompt(win, browserElementChild).QueryInterface(
iid
);
},
};
var BrowserElementPromptService = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]),
_initialized: false,
_init() {
if (this._initialized) {
return;
}
this._initialized = true;
this._browserElementParentMap = new WeakMap();
Services.obs.addObserver(
this,
"outer-window-destroyed",
/* ownsWeak = */ true
);
// Wrap the existing @mozilla.org/prompter;1 implementation.
var contractID = "@mozilla.org/prompter;1";
var oldCID = Cm.contractIDToCID(contractID);
var newCID = BrowserElementPromptFactory.prototype.classID;
var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory);
if (oldCID == newCID) {
debug("WARNING: Wrapped prompt factory is already installed!");
return;
}
var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory);
var newInstance = new BrowserElementPromptFactory(oldInstance);
var newFactory = {
createInstance(iid) {
return newInstance.QueryInterface(iid);
},
};
Cm.registerFactory(
newCID,
"BrowserElementPromptService's prompter;1 wrapper",
contractID,
newFactory
);
debug("Done installing new prompt factory.");
},
_getOuterWindowID(win) {
return win.docShell.outerWindowID;
},
_browserElementChildMap: {},
mapWindowToBrowserElementChild(win, browserElementChild) {
this._browserElementChildMap[this._getOuterWindowID(win)] =
browserElementChild;
},
unmapWindowToBrowserElementChild(win) {
delete this._browserElementChildMap[this._getOuterWindowID(win)];
},
getBrowserElementChildForWindow(win) {
// We only have a mapping for <iframe mozbrowser>s, not their inner
// <iframes>, so we look up win.top below. window.top (when called from
// script) respects <iframe mozbrowser> boundaries.
return this._browserElementChildMap[this._getOuterWindowID(win.top)];
},
mapFrameToBrowserElementParent(frame, browserElementParent) {
this._browserElementParentMap.set(frame, browserElementParent);
},
getBrowserElementParentForFrame(frame) {
return this._browserElementParentMap.get(frame);
},
_observeOuterWindowDestroyed(outerWindowID) {
let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data;
debug("observeOuterWindowDestroyed " + id);
delete this._browserElementChildMap[outerWindowID.data];
},
observe(subject, topic, data) {
switch (topic) {
case "outer-window-destroyed":
this._observeOuterWindowDestroyed(subject);
break;
default:
debug("Observed unexpected topic " + topic);
}
},
};
BrowserElementPromptService._init();

View file

@ -0,0 +1,14 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [
{
'cid': '{9f171ac4-0939-4ef8-b360-3408aedc3060}',
'contract_ids': ['@mozilla.org/dom/browser-element-api;1'],
'jsm': 'resource://gre/modules/BrowserElementParent.jsm',
'constructor': 'BrowserElementParent',
},
]

View file

@ -0,0 +1,37 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
XPIDL_SOURCES += [
"nsIBrowserElementAPI.idl",
]
XPIDL_MODULE = "browser-element"
EXTRA_JS_MODULES += [
"BrowserElementParent.jsm",
"BrowserElementPromptService.jsm",
]
XPCOM_MANIFESTS += [
"components.conf",
]
LOCAL_INCLUDES += [
"/dom/html",
]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"
LOCAL_INCLUDES += [
"/dom/",
"/dom/base",
"/dom/ipc",
]

View file

@ -0,0 +1,44 @@
/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
webidl FrameLoader;
%{C++
#define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
#define BROWSER_ELEMENT_API_CID \
{ 0x651db7e3, 0x1734, 0x4536, \
{ 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
%}
/**
* Interface to the BrowserElementParent implementation. All methods
* but setFrameLoader throw when the remote process is dead.
*/
[scriptable, uuid(57758c10-6036-11e5-a837-0800200c9a66)]
interface nsIBrowserElementAPI : nsISupports
{
/**
* Notify frame scripts that support the API to destroy.
*/
void destroyFrameScripts();
void setFrameLoader(in FrameLoader frameLoader);
void sendMouseEvent(in AString type,
in uint32_t x,
in uint32_t y,
in uint32_t button,
in uint32_t clickCount,
in uint32_t mifiers);
void goBack();
void goForward();
void reload(in boolean hardReload);
void stop();
Promise getCanGoBack();
Promise getCanGoForward();
};

View file

@ -951,6 +951,7 @@ dictionary IOActivityDataDictionary {
[GenerateInitFromJSON]
dictionary OriginAttributesDictionary {
unsigned long userContextId = 0;
boolean inIsolatedMozBrowser = false;
unsigned long privateBrowsingId = 0;
DOMString firstPartyDomain = "";
DOMString geckoViewSessionContextId = "";
@ -960,6 +961,7 @@ dictionary OriginAttributesDictionary {
[GenerateInitFromJSON, GenerateToJSON]
dictionary OriginAttributesPatternDictionary {
unsigned long userContextId;
boolean inIsolatedMozBrowser;
unsigned long privateBrowsingId;
DOMString firstPartyDomain;
DOMString geckoViewSessionContextId;

View file

@ -122,6 +122,13 @@ interface FrameLoader {
[Pure]
readonly attribute unsigned long long childID;
/**
* Find out whether the owner content really is a mozbrowser. <xul:browser>
* is not considered to be a mozbrowser frame.
*/
[Pure]
readonly attribute boolean ownerIsMozBrowserFrame;
/**
* The last known width of the frame. Reading this property will not trigger
* a reflow, and therefore may not reflect the current state of things. It

View file

@ -142,6 +142,11 @@ class HTMLIFrameElement final : public nsGenericHTMLFrameElement {
Document* GetSVGDocument(nsIPrincipal& aSubjectPrincipal) {
return GetContentDocument(aSubjectPrincipal);
}
bool Mozbrowser() const { return GetBoolAttr(nsGkAtoms::mozbrowser); }
void SetMozbrowser(bool aAllow, ErrorResult& aError) {
SetHTMLBoolAttr(nsGkAtoms::mozbrowser, aAllow, aError);
}
using nsGenericHTMLFrameElement::SetMozbrowser;
// nsGenericHTMLFrameElement::GetFrameLoader is fine
// nsGenericHTMLFrameElement::GetAppManifestURL is fine

View file

@ -117,6 +117,7 @@ EXPORTS.mozilla.dom += [
"ImageDocument.h",
"MediaDocument.h",
"MediaError.h",
"nsBrowserElement.h",
"PlayPromise.h",
"RadioNodeList.h",
"TextTrackManager.h",
@ -202,6 +203,7 @@ UNIFIED_SOURCES += [
"ImageDocument.cpp",
"MediaDocument.cpp",
"MediaError.cpp",
"nsBrowserElement.cpp",
"nsDOMStringMap.cpp",
"nsGenericHTMLElement.cpp",
"nsGenericHTMLFrameElement.cpp",

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsBrowserElement.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsComponentManagerUtils.h"
#include "nsFrameLoader.h"
#include "nsINode.h"
#include "js/Wrapper.h"
using namespace mozilla::dom;
namespace mozilla {
bool nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv) {
if (mBrowserElementAPI) {
return true;
}
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
return false;
}
void nsBrowserElement::InitBrowserElementAPI() {
RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
NS_ENSURE_TRUE_VOID(frameLoader);
if (!frameLoader->OwnerIsMozBrowserFrame()) {
return;
}
if (!mBrowserElementAPI) {
mBrowserElementAPI =
do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
if (NS_WARN_IF(!mBrowserElementAPI)) {
return;
}
}
mBrowserElementAPI->SetFrameLoader(frameLoader);
}
void nsBrowserElement::DestroyBrowserElementFrameScripts() {
if (!mBrowserElementAPI) {
return;
}
mBrowserElementAPI->DestroyFrameScripts();
}
} // namespace mozilla

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsBrowserElement_h
#define nsBrowserElement_h
#include "mozilla/dom/BindingDeclarations.h"
#include "nsCOMPtr.h"
#include "nsIBrowserElementAPI.h"
class nsFrameLoader;
namespace mozilla {
namespace dom {
class Promise;
} // namespace dom
class ErrorResult;
/**
* A helper class for browser-element frames
*/
class nsBrowserElement {
public:
nsBrowserElement() = default;
virtual ~nsBrowserElement() = default;
void SendMouseEvent(const nsAString& aType, uint32_t aX, uint32_t aY,
uint32_t aButton, uint32_t aClickCount,
uint32_t aModifiers, ErrorResult& aRv);
void GoBack(ErrorResult& aRv);
void GoForward(ErrorResult& aRv);
void Reload(bool aHardReload, ErrorResult& aRv);
void Stop(ErrorResult& aRv);
already_AddRefed<dom::Promise> GetCanGoBack(ErrorResult& aRv);
already_AddRefed<dom::Promise> GetCanGoForward(ErrorResult& aRv);
protected:
virtual already_AddRefed<nsFrameLoader> GetFrameLoader() = 0;
void InitBrowserElementAPI();
void DestroyBrowserElementFrameScripts();
nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
private:
bool IsBrowserElementOrThrow(ErrorResult& aRv);
};
} // namespace mozilla
#endif // nsBrowserElement_h

View file

@ -35,6 +35,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
nsGenericHTMLElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement,
@ -44,12 +45,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement,
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAPI)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement,
nsGenericHTMLElement,
nsFrameLoaderOwner,
nsGenericHTMLFrameElement)
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
nsGenericHTMLFrameElement, nsGenericHTMLElement, nsFrameLoaderOwner,
nsIDOMMozBrowserFrame, nsIMozBrowserFrame, nsGenericHTMLFrameElement)
NS_IMETHODIMP
nsGenericHTMLFrameElement::GetMozbrowser(bool* aValue) {
*aValue = GetBoolAttr(nsGkAtoms::mozbrowser);
return NS_OK;
}
NS_IMETHODIMP
nsGenericHTMLFrameElement::SetMozbrowser(bool aValue) {
return SetBoolAttr(nsGkAtoms::mozbrowser, aValue);
}
int32_t nsGenericHTMLFrameElement::TabIndexDefault() { return 0; }
@ -249,6 +260,9 @@ void nsGenericHTMLFrameElement::AfterSetAttr(
child->SendScrollbarPreferenceChanged(pref);
}
}
} else if (aName == nsGkAtoms::mozbrowser) {
mReallyIsBrowser = !!aValue && XRE_IsParentProcess() &&
NodePrincipal()->IsSystemPrincipal();
}
}
@ -322,3 +336,28 @@ bool nsGenericHTMLFrameElement::IsHTMLFocusable(bool aWithMouse,
*aIsFocusable = true;
return false;
}
/**
* Return true if this frame element really is a mozbrowser. (It
* needs to have the right attributes, and its creator must have the right
* permissions.)
*/
/* [infallible] */
nsresult nsGenericHTMLFrameElement::GetReallyIsBrowser(bool* aOut) {
*aOut = mReallyIsBrowser;
return NS_OK;
}
NS_IMETHODIMP
nsGenericHTMLFrameElement::InitializeBrowserAPI() {
MOZ_ASSERT(mFrameLoader);
InitBrowserElementAPI();
return NS_OK;
}
NS_IMETHODIMP
nsGenericHTMLFrameElement::DestroyBrowserFrameScripts() {
MOZ_ASSERT(mFrameLoader);
DestroyBrowserElementFrameScripts();
return NS_OK;
}

View file

@ -8,10 +8,12 @@
#define nsGenericHTMLFrameElement_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/nsBrowserElement.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsGenericHTMLElement.h"
#include "nsIMozBrowserFrame.h"
namespace mozilla {
class ErrorResult;
@ -36,17 +38,24 @@ class XULFrameElement;
* A helper class for frame elements
*/
class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
public nsFrameLoaderOwner {
public nsFrameLoaderOwner,
public mozilla::nsBrowserElement,
public nsIMozBrowserFrame {
public:
nsGenericHTMLFrameElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
mozilla::dom::FromParser aFromParser)
: nsGenericHTMLElement(std::move(aNodeInfo)),
mSrcLoadHappened(false),
mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK) {}
mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK),
mBrowserFrameListenersRegistered(false),
mReallyIsBrowser(false) {}
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMMOZBROWSERFRAME
NS_DECL_NSIMOZBROWSERFRAME
NS_DECLARE_STATIC_IID_ACCESSOR(NS_GENERICHTMLFRAMEELEMENT_IID)
// nsIContent
@ -60,6 +69,8 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
virtual int32_t TabIndexDefault() override;
virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() override { return this; }
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
nsGenericHTMLElement)
@ -83,6 +94,11 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
return mSrcTriggeringPrincipal;
}
// Needed for nsBrowserElement
already_AddRefed<nsFrameLoader> GetFrameLoader() override {
return nsFrameLoaderOwner::GetFrameLoader();
}
protected:
virtual ~nsGenericHTMLFrameElement();
@ -116,6 +132,9 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
*/
bool mNetworkCreated;
bool mBrowserFrameListenersRegistered;
bool mReallyIsBrowser;
// This flag is only used by <iframe>. See HTMLIFrameElement::
// FullscreenFlag() for details. It is placed here so that we
// do not bloat any struct.

View file

@ -115,6 +115,7 @@ HTML_TAG("figure", "")
HTML_TAG("font", "Font");
HTML_TAG("footer", "")
HTML_TAG("form", "Form");
HTML_TAG("frame", "Frame", [ "nsIDOMMozBrowserFrame" ]);
HTML_TAG("frameset", "FrameSet");
HTML_TAG("h1", "Heading");
HTML_TAG("h2", "Heading");
@ -128,6 +129,7 @@ HTML_TAG("hgroup", "")
HTML_TAG("hr", "HR");
HTML_TAG("html", "Html");
HTML_TAG("i", "");
HTML_TAG("iframe", "IFrame", [ "nsIDOMMozBrowserFrame" ]);
HTML_TAG("image", "");
HTML_TAG("img", "Image", [ "nsIImageLoadingContent" ], []);
HTML_TAG("input", "Input", [], [ "imgINotificationObserver",

View file

@ -0,0 +1,15 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
XPIDL_SOURCES += [
"nsIDOMMozBrowserFrame.idl",
"nsIMozBrowserFrame.idl",
]
XPIDL_MODULE = "dom_html"

View file

@ -0,0 +1,27 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, builtinclass, uuid(4CAFE116-581B-4194-B0DE-7F02378FC51D)]
interface nsIDOMMozBrowserFrame : nsISupports
{
/**
* <iframe> element may have the mozbrowser attribute.
*
* The mozbrowser attribute has no effect unless the <iframe> element is
* contained in a document privileged to create browser frames.
*
* An <iframe> element in a privileged document with the mozbrowser attribute
* emits a variety of events when various things happen inside the frame.
*
* This will be documented eventually, but for more information at the moment,
* see dom/browser-element/BrowserElement{Child,Parent}.js.
*
*/
[infallible] attribute boolean mozbrowser;
};

View file

@ -0,0 +1,34 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDOMMozBrowserFrame.idl"
interface nsIRemoteTab;
[scriptable, builtinclass, uuid(0c0a862c-1a47-43c0-ae9e-d51835e3e1a6)]
interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
{
/**
* Gets whether this frame really is a browser frame.
*
* In order to really be a browser frame, this frame's
* nsIDOMMozBrowserFrame::mozbrowser attribute must be true, and the frame
* may have to pass various security checks.
*/
[infallible] readonly attribute boolean reallyIsBrowser;
/**
* Initialize the API, and add frame message listener that supports API
* invocations.
*/
[noscript] void initializeBrowserAPI();
/**
* Notify frame scripts that support the API to destroy.
*/
[noscript] void destroyBrowserFrameScripts();
};

View file

@ -213,6 +213,7 @@
#include "nsILocalStorageManager.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"
#include "nsINetworkLinkService.h"
#include "nsIObserverService.h"
#include "nsIParentChannel.h"

View file

@ -203,6 +203,8 @@ class ProcessPriorityManagerImpl final : public nsIObserver,
void BrowserPriorityChanged(CanonicalBrowsingContext* aBC, bool aPriority);
void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority);
void ResetPriority(ContentParent* aContentParent);
private:
static bool sPrefListenersRegistered;
static bool sInitialized;
@ -557,6 +559,12 @@ void ProcessPriorityManagerImpl::BrowserPriorityChanged(
}
}
void ProcessPriorityManagerImpl::ResetPriority(ContentParent* aContentParent) {
if (RefPtr pppm = GetParticularProcessPriorityManager(aContentParent)) {
pppm->ResetPriority();
}
}
NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsITimerCallback,
nsISupportsWeakReference, nsINamed);
@ -1034,4 +1042,26 @@ void ProcessPriorityManager::BrowserPriorityChanged(
singleton->BrowserPriorityChanged(aBrowserParent, aPriority);
}
/* static */
void ProcessPriorityManager::RemoteBrowserFrameShown(
nsFrameLoader* aFrameLoader) {
ProcessPriorityManagerImpl* singleton =
ProcessPriorityManagerImpl::GetSingleton();
if (!singleton) {
return;
}
BrowserParent* bp = BrowserParent::GetFrom(aFrameLoader);
NS_ENSURE_TRUE_VOID(bp);
MOZ_ASSERT(XRE_IsParentProcess());
// Ignore calls that aren't from a Browser.
if (!aFrameLoader->OwnerIsMozBrowserFrame()) {
return;
}
singleton->ResetPriority(bp->Manager());
}
} // namespace mozilla

View file

@ -80,6 +80,8 @@ class ProcessPriorityManager final {
static void BrowserPriorityChanged(dom::BrowserParent* aBrowserParent,
bool aPriority);
static void RemoteBrowserFrameShown(nsFrameLoader* aFrameLoader);
private:
ProcessPriorityManager();
ProcessPriorityManager(const ProcessPriorityManager&) = delete;

7
dom/ipc/jar.mn Normal file
View file

@ -0,0 +1,7 @@
# 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/.
toolkit.jar:
content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js)

View file

@ -257,6 +257,8 @@ DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
DEFINES["MOZ_ENABLE_FREETYPE"] = True
JAR_MANIFESTS += ["jar.mn"]
BROWSER_CHROME_MANIFESTS += [
"tests/browser.toml",
"tests/JSProcessActor/browser.toml",

View file

@ -12,6 +12,7 @@ JAR_MANIFESTS += ["jar.mn"]
interfaces = [
"base",
"html",
"events",
"sidebar",
"xul",
@ -31,6 +32,7 @@ DIRS += [
"base",
"bindings",
"battery",
"browser-element",
"cache",
"canvas",
"webgpu",

View file

@ -59,6 +59,11 @@ add_task(async function test_webapps_cleardata() {
originAttributes: {},
clearIf: { inIsolatedMozBrowser: false },
},
{
scope: "https://example.org/1",
originAttributes: { inIsolatedMozBrowser: true },
clearIf: {},
},
];
let unregisterDone;

View file

@ -108,7 +108,6 @@
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/ExtensionProtocolHandler.h"
#include "mozilla/StorageOriginAttributes.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
@ -3204,7 +3203,7 @@ nsresult QuotaManager::CreateDirectoryMetadata(
const OriginMetadata& aOriginMetadata) {
AssertIsOnIOThread();
StorageOriginAttributes groupAttributes;
OriginAttributes groupAttributes;
nsCString groupNoSuffix;
QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
@ -3212,11 +3211,11 @@ nsresult QuotaManager::CreateDirectoryMetadata(
NS_ERROR_FAILURE);
nsCString groupPrefix;
GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix);
GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
nsCString group = groupPrefix + groupNoSuffix;
StorageOriginAttributes originAttributes;
OriginAttributes originAttributes;
nsCString originNoSuffix;
QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
@ -3224,7 +3223,7 @@ nsresult QuotaManager::CreateDirectoryMetadata(
NS_ERROR_FAILURE);
nsCString originPrefix;
GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix);
GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
nsCString origin = originPrefix + originNoSuffix;

View file

@ -216,7 +216,11 @@ void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
return;
}
if ((aToken.First() != 't') && (aToken.First() != 'f')) {
if (aToken.First() == 't') {
mInIsolatedMozBrowser = true;
} else if (aToken.First() == 'f') {
mInIsolatedMozBrowser = false;
} else {
QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
nsCString(aToken).get());

View file

@ -65,6 +65,7 @@ class MOZ_STACK_CLASS OriginParser final {
SchemeType mSchemeType;
State mState;
bool mInIsolatedMozBrowser;
bool mUniversalFileOrigin;
bool mMaybeDriveLetter;
bool mError;
@ -79,6 +80,7 @@ class MOZ_STACK_CLASS OriginParser final {
mTokenizer(aOrigin, '+'),
mSchemeType(eNone),
mState(eExpectingAppIdOrScheme),
mInIsolatedMozBrowser(false),
mUniversalFileOrigin(false),
mMaybeDriveLetter(false),
mError(false),

View file

@ -64,6 +64,7 @@ struct ParamTraits<mozilla::OriginAttributesPattern> {
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.mFirstPartyDomain);
WriteParam(aWriter, aParam.mInIsolatedMozBrowser);
WriteParam(aWriter, aParam.mPrivateBrowsingId);
WriteParam(aWriter, aParam.mUserContextId);
WriteParam(aWriter, aParam.mGeckoViewSessionContextId);
@ -71,6 +72,7 @@ struct ParamTraits<mozilla::OriginAttributesPattern> {
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mFirstPartyDomain) &&
ReadParam(aReader, &aResult->mInIsolatedMozBrowser) &&
ReadParam(aReader, &aResult->mPrivateBrowsingId) &&
ReadParam(aReader, &aResult->mUserContextId) &&
ReadParam(aReader, &aResult->mGeckoViewSessionContextId);

View file

@ -6,58 +6,10 @@
#include "StorageOriginAttributes.h"
#include "nsString.h"
#include "nsURLHelper.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/quota/QuotaManager.h"
namespace mozilla {
void StorageOriginAttributes::CreateSuffix(nsACString& aStr) const {
nsCString str1;
URLParams params;
nsAutoString value;
if (mInIsolatedMozBrowser) {
params.Set(u"inBrowser"_ns, u"1"_ns);
}
str1.Truncate();
params.Serialize(value, true);
if (!value.IsEmpty()) {
str1.AppendLiteral("^");
str1.Append(NS_ConvertUTF16toUTF8(value));
}
// Make sure that the string don't contain characters that would get replaced
// with the plus character by quota manager, potentially causing ambiguity.
MOZ_ASSERT(str1.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) ==
kNotFound);
// Let OriginAttributes::CreateSuffix serialize other origin attributes.
nsCString str2;
mOriginAttributes.CreateSuffix(str2);
aStr.Truncate();
if (str1.IsEmpty()) {
aStr.Append(str2);
return;
}
if (str2.IsEmpty()) {
aStr.Append(str1);
return;
}
// If both strings are not empty, we need to combine them.
aStr.Append(str1);
aStr.Append('&');
aStr.Append(Substring(str2, 1, str2.Length() - 1));
}
bool StorageOriginAttributes::PopulateFromSuffix(const nsACString& aStr) {
if (aStr.IsEmpty()) {
return true;

View file

@ -15,32 +15,12 @@ namespace mozilla {
// in OriginAttributes class anymore.
class StorageOriginAttributes {
public:
StorageOriginAttributes() : mInIsolatedMozBrowser(false) {}
explicit StorageOriginAttributes(bool aInIsolatedMozBrowser)
: mInIsolatedMozBrowser(aInIsolatedMozBrowser) {}
bool InIsolatedMozBrowser() const { return mInIsolatedMozBrowser; }
uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
// New getters can be added here incrementally.
void SetInIsolatedMozBrowser(bool aInIsolatedMozBrowser) {
mInIsolatedMozBrowser = aInIsolatedMozBrowser;
}
void SetUserContextId(uint32_t aUserContextId) {
mOriginAttributes.mUserContextId = aUserContextId;
}
// New setters can be added here incrementally.
// Serializes/Deserializes 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) const;
[[nodiscard]] bool PopulateFromSuffix(const nsACString& aStr);
// Populates the attributes from a string like
@ -51,7 +31,7 @@ class StorageOriginAttributes {
private:
OriginAttributes mOriginAttributes;
bool mInIsolatedMozBrowser;
bool mInIsolatedMozBrowser = false;
};
} // namespace mozilla

View file

@ -9,33 +9,6 @@
namespace mozilla::dom::quota::test {
TEST(DOM_Quota_StorageOriginAttributes, Constructor_Default)
{
{
StorageOriginAttributes originAttributes;
ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
ASSERT_EQ(originAttributes.UserContextId(), 0u);
}
}
TEST(DOM_Quota_StorageOriginAttributes, Constructor_InIsolatedMozbrowser)
{
{
StorageOriginAttributes originAttributes(/* aInIsolatedMozBrowser */ false);
ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
ASSERT_EQ(originAttributes.UserContextId(), 0u);
}
{
StorageOriginAttributes originAttributes(/* aInIsolatedMozBrowser */ true);
ASSERT_TRUE(originAttributes.InIsolatedMozBrowser());
ASSERT_EQ(originAttributes.UserContextId(), 0u);
}
}
TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_NoOriginAttributes)
{
{
@ -189,52 +162,4 @@ TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_Mixed_Invalid)
}
}
TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_NoOriginAttributes)
{
{
StorageOriginAttributes originAttributes;
nsCString suffix;
originAttributes.CreateSuffix(suffix);
ASSERT_TRUE(suffix.IsEmpty());
}
}
TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_InIsolatedMozbrowser)
{
{
StorageOriginAttributes originAttributes;
originAttributes.SetInIsolatedMozBrowser(true);
nsCString suffix;
originAttributes.CreateSuffix(suffix);
ASSERT_TRUE(suffix.Equals("^inBrowser=1"_ns));
}
}
TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_UserContextId)
{
{
StorageOriginAttributes originAttributes;
originAttributes.SetUserContextId(42);
nsCString suffix;
originAttributes.CreateSuffix(suffix);
ASSERT_TRUE(suffix.Equals("^userContextId=42"_ns));
}
}
TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_Mixed)
{
{
StorageOriginAttributes originAttributes;
originAttributes.SetInIsolatedMozBrowser(true);
originAttributes.SetUserContextId(42);
nsCString suffix;
originAttributes.CreateSuffix(suffix);
ASSERT_TRUE(suffix.Equals("^inBrowser=1&userContextId=42"_ns));
}
}
} // namespace mozilla::dom::quota::test

View file

@ -219,7 +219,7 @@ TEST(ServiceWorkerRegistrar, TestReadData)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -280,24 +280,30 @@ TEST(ServiceWorkerRegistrar, TestWriteData)
{
RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
ServiceWorkerRegistrationData reg;
for (int i = 0; i < 2; ++i) {
ServiceWorkerRegistrationData reg;
reg.scope() = "https://scope_write_0.org"_ns;
reg.currentWorkerURL() = "currentWorkerURL write 0"_ns;
reg.currentWorkerHandlesFetch() = true;
reg.cacheName() = u"cacheName write 0"_ns;
reg.updateViaCache() =
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
reg.scope() = nsPrintfCString("https://scope_write_%d.org", i);
reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
reg.currentWorkerHandlesFetch() = true;
reg.cacheName() =
NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
reg.updateViaCache() =
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
reg.currentWorkerInstalledTime() = PR_Now();
reg.currentWorkerActivatedTime() = PR_Now();
reg.lastUpdateTime() = PR_Now();
reg.currentWorkerInstalledTime() = PR_Now();
reg.currentWorkerActivatedTime() = PR_Now();
reg.lastUpdateTime() = PR_Now();
const auto spec = "spec write 0"_ns;
reg.principal() = mozilla::ipc::ContentPrincipalInfo(
mozilla::OriginAttributes(), spec, spec, mozilla::Nothing(), spec);
nsAutoCString spec;
spec.AppendPrintf("spec write %d", i);
swr->TestRegisterServiceWorker(reg);
reg.principal() = mozilla::ipc::ContentPrincipalInfo(
mozilla::OriginAttributes(i % 2), spec, spec, mozilla::Nothing(),
spec);
swr->TestRegisterServiceWorker(reg);
}
nsresult rv = swr->TestWriteData();
ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
@ -308,37 +314,47 @@ TEST(ServiceWorkerRegistrar, TestWriteData)
nsresult rv = swr->TestReadData();
ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
const nsTArray<ServiceWorkerRegistrationData>& dataArr = swr->TestGetData();
ASSERT_EQ((uint32_t)1, dataArr.Length()) << "1 entries should be found";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
const auto& data = dataArr[0];
for (int i = 0; i < 2; ++i) {
nsAutoCString test;
ASSERT_EQ(data.principal().type(),
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
const mozilla::ipc::ContentPrincipalInfo& cInfo = data.principal();
ASSERT_EQ(data[i].principal().type(),
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();
mozilla::OriginAttributes attrs;
nsAutoCString suffix, expectSuffix;
attrs.CreateSuffix(expectSuffix);
cInfo.attrs().CreateSuffix(suffix);
mozilla::OriginAttributes attrs(i % 2);
nsAutoCString suffix, expectSuffix;
attrs.CreateSuffix(expectSuffix);
cInfo.attrs().CreateSuffix(suffix);
ASSERT_STREQ(expectSuffix.get(), suffix.get());
ASSERT_STREQ(expectSuffix.get(), suffix.get());
ASSERT_STREQ("https://scope_write_0.org", cInfo.spec().get());
ASSERT_STREQ("https://scope_write_0.org", data.scope().get());
ASSERT_STREQ("currentWorkerURL write 0", data.currentWorkerURL().get());
test.AppendPrintf("https://scope_write_%d.org", i);
ASSERT_STREQ(test.get(), cInfo.spec().get());
ASSERT_EQ(true, data.currentWorkerHandlesFetch());
test.Truncate();
test.AppendPrintf("https://scope_write_%d.org", i);
ASSERT_STREQ(test.get(), data[i].scope().get());
ASSERT_STREQ("cacheName write 0",
NS_ConvertUTF16toUTF8(data.cacheName()).get());
test.Truncate();
test.AppendPrintf("currentWorkerURL write %d", i);
ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get());
ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
data.updateViaCache());
ASSERT_EQ(true, data[i].currentWorkerHandlesFetch());
ASSERT_NE((int64_t)0, data.currentWorkerInstalledTime());
ASSERT_NE((int64_t)0, data.currentWorkerActivatedTime());
ASSERT_NE((int64_t)0, data.lastUpdateTime());
test.Truncate();
test.AppendPrintf("cacheName write %d", i);
ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
data[i].updateViaCache());
ASSERT_NE((int64_t)0, data[i].currentWorkerInstalledTime());
ASSERT_NE((int64_t)0, data[i].currentWorkerActivatedTime());
ASSERT_NE((int64_t)0, data[i].lastUpdateTime());
}
}
TEST(ServiceWorkerRegistrar, TestVersion2Migration)
@ -378,7 +394,7 @@ TEST(ServiceWorkerRegistrar, TestVersion2Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -448,7 +464,7 @@ TEST(ServiceWorkerRegistrar, TestVersion3Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -516,7 +532,7 @@ TEST(ServiceWorkerRegistrar, TestVersion4Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -588,7 +604,7 @@ TEST(ServiceWorkerRegistrar, TestVersion5Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -662,7 +678,7 @@ TEST(ServiceWorkerRegistrar, TestVersion6Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -749,7 +765,7 @@ TEST(ServiceWorkerRegistrar, TestVersion7Migration)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -834,7 +850,7 @@ TEST(ServiceWorkerRegistrar, TestDedupeRead)
nsAutoCString suffix0;
cInfo0.attrs().CreateSuffix(suffix0);
ASSERT_STREQ("", suffix0.get());
ASSERT_STREQ("^inBrowser=1", suffix0.get());
ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
@ -887,7 +903,8 @@ TEST(ServiceWorkerRegistrar, TestDedupeWrite)
spec.AppendPrintf("spec write dedupe/%d", i);
reg.principal() = mozilla::ipc::ContentPrincipalInfo(
mozilla::OriginAttributes(), spec, spec, mozilla::Nothing(), spec);
mozilla::OriginAttributes(false), spec, spec, mozilla::Nothing(),
spec);
swr->TestRegisterServiceWorker(reg);
}
@ -909,7 +926,7 @@ TEST(ServiceWorkerRegistrar, TestDedupeWrite)
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
const mozilla::ipc::ContentPrincipalInfo& cInfo = data[0].principal();
mozilla::OriginAttributes attrs;
mozilla::OriginAttributes attrs(false);
nsAutoCString suffix, expectSuffix;
attrs.CreateSuffix(expectSuffix);
cInfo.attrs().CreateSuffix(suffix);

View file

@ -15,7 +15,6 @@
#include "mozilla/Tokenizer.h"
#include "mozIStorageConnection.h"
#include "mozStorageHelper.h"
#include "mozilla/StorageOriginAttributes.h"
// Current version of the database schema
#define CURRENT_SCHEMA_VERSION 2
@ -132,8 +131,8 @@ class ExtractOriginData : protected mozilla::Tokenizer {
}
}
} else {
StorageOriginAttributes originAttributes(inIsolatedMozBrowser);
originAttributes.CreateSuffix(suffix);
OriginAttributes attrs(inIsolatedMozBrowser);
attrs.CreateSuffix(suffix);
}
// Consume the rest of the input as "origin".

View file

@ -13,7 +13,6 @@
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "mozilla/StorageOriginAttributes.h"
namespace mozilla::dom::StorageUtils {
@ -62,16 +61,16 @@ nsCString Scheme0Scope(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix) {
nsCString result;
StorageOriginAttributes oa;
OriginAttributes oa;
if (!aOriginSuffix.IsEmpty()) {
DebugOnly<bool> success = oa.PopulateFromSuffix(aOriginSuffix);
MOZ_ASSERT(success);
}
if (oa.InIsolatedMozBrowser()) {
if (oa.mInIsolatedMozBrowser) {
result.AppendInt(0); // This is the appId to be removed.
result.Append(':');
result.Append(oa.InIsolatedMozBrowser() ? 't' : 'f');
result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
result.Append(':');
}
@ -81,7 +80,7 @@ nsCString Scheme0Scope(const nsACString& aOriginSuffix,
// with originAttributes and originKey columns) so that switch between
// schema 1 and 0 always works in both ways.
nsAutoCString remaining;
oa.SetInIsolatedMozBrowser(false);
oa.mInIsolatedMozBrowser = false;
oa.CreateSuffix(remaining);
if (!remaining.IsEmpty()) {
MOZ_ASSERT(!aOriginSuffix.IsEmpty());

View file

@ -30,7 +30,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=800817
var b1 = document.getElementById("b1");
var b2 = document.getElementById("b2");
window.arguments[0].info("Testing...");
var testMozBrowser = window.arguments[0].testMozBrowser;
if (testMozBrowser) {
b1.setAttribute("mozbrowser", "true");
b2.setAttribute("mozbrowser", "true");
}
if (testMozBrowser)
window.arguments[0].info("Testing with mozbrowser=true");
else
window.arguments[0].info("Testing without mozbrowser");
b1.contentWindow.focus();
window.arguments[0].is(document.activeElement, b1,

View file

@ -20,12 +20,20 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=800817
<![CDATA[
/** Test for Bug 800817 **/
var testMozBrowser = false;
function runTests() {
// Run a first round of tests for non-mozbrowser iframes.
window.openDialog("file_bug800817.xhtml", "_blank", "chrome,width=600,height=550,noopener", window);
}
function finishedTests() {
SimpleTest.finish();
if (!testMozBrowser) {
testMozBrowser = true;
// Run a second round of tests for mozbrowser iframes.
window.openDialog("file_bug800817.xhtml", "_blank", "chrome,width=600,height=550,noopener", window);
} else {
SimpleTest.finish();
}
}
SimpleTest.waitForExplicitFinish();

View file

@ -308,6 +308,17 @@ nsresult GetPrincipalFromOrigin(const nsACString& aOrigin, bool aForceStripOA,
return NS_OK;
}
nsresult GetPrincipal(nsIURI* aURI, bool aIsInIsolatedMozBrowserElement,
nsIPrincipal** aPrincipal) {
OriginAttributes attrs(aIsInIsolatedMozBrowserElement);
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(aURI, attrs);
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
principal.forget(aPrincipal);
return NS_OK;
}
nsresult GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) {
OriginAttributes attrs;
nsCOMPtr<nsIPrincipal> principal =
@ -358,6 +369,7 @@ already_AddRefed<nsIURI> GetNextSubDomainURI(nsIURI* aURI) {
nsresult UpgradeHostToOriginAndInsert(
const nsACString& aHost, const nsCString& aType, uint32_t aPermission,
uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
bool aIsInIsolatedMozBrowserElement,
std::function<nsresult(const nsACString& aOrigin, const nsCString& aType,
uint32_t aPermission, uint32_t aExpireType,
int64_t aExpireTime, int64_t aModificationTime)>&&
@ -384,7 +396,8 @@ nsresult UpgradeHostToOriginAndInsert(
}
nsCOMPtr<nsIPrincipal> principal;
rv = GetPrincipal(uri, getter_AddRefs(principal));
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString origin;
@ -499,7 +512,8 @@ nsresult UpgradeHostToOriginAndInsert(
// We now have a URI which we can make a nsIPrincipal out of
nsCOMPtr<nsIPrincipal> principal;
rv = GetPrincipal(uri, getter_AddRefs(principal));
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
if (NS_WARN_IF(NS_FAILED(rv))) continue;
nsAutoCString origin;
@ -546,7 +560,8 @@ nsresult UpgradeHostToOriginAndInsert(
rv = NS_NewURI(getter_AddRefs(uri), "http://"_ns + hostSegment);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPrincipal(uri, getter_AddRefs(principal));
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
@ -560,7 +575,8 @@ nsresult UpgradeHostToOriginAndInsert(
rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + hostSegment);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPrincipal(uri, getter_AddRefs(principal));
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
@ -1208,6 +1224,7 @@ nsresult PermissionManager::TryInitDB(bool aRemoveFile,
entry.mExpireType = stmt->AsInt32(3);
entry.mExpireTime = stmt->AsInt64(4);
entry.mModificationTime = stmt->AsInt64(5);
entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
mMigrationEntries.AppendElement(entry);
}
@ -1362,6 +1379,7 @@ nsresult PermissionManager::TryInitDB(bool aRemoveFile,
entry.mExpireType = stmt->AsInt32(3);
entry.mExpireTime = stmt->AsInt64(4);
entry.mModificationTime = stmt->AsInt64(5);
entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
mMigrationEntries.AppendElement(entry);
}
@ -3050,7 +3068,7 @@ void PermissionManager::CompleteMigrations() {
for (const MigrationEntry& entry : entries) {
rv = UpgradeHostToOriginAndInsert(
entry.mHost, entry.mType, entry.mPermission, entry.mExpireType,
entry.mExpireTime, entry.mModificationTime,
entry.mExpireTime, entry.mModificationTime, entry.mIsInBrowserElement,
[&](const nsACString& aOrigin, const nsCString& aType,
uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
int64_t aModificationTime) {
@ -3671,7 +3689,7 @@ nsresult PermissionManager::ImportLatestDefaults() {
rv = UpgradeHostToOriginAndInsert(
entry.mHostOrOrigin, entry.mType, entry.mPermission,
nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime,
nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime, false,
[&](const nsACString& aOrigin, const nsCString& aType,
uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
int64_t aModificationTime) {

View file

@ -596,7 +596,8 @@ class PermissionManager final : public nsIPermissionManager,
mPermission(0),
mExpireType(0),
mExpireTime(0),
mModificationTime(0) {}
mModificationTime(0),
mIsInBrowserElement(false) {}
nsCString mHost;
nsCString mType;
@ -605,6 +606,9 @@ class PermissionManager final : public nsIPermissionManager,
uint32_t mExpireType;
int64_t mExpireTime;
int64_t mModificationTime;
// Legacy, for migration.
bool mIsInBrowserElement;
};
// List of entries read from the database. It will be populated OMT and

View file

@ -66,7 +66,13 @@ function run_test() {
pm = Services.perms;
let entries = [{ origin: "http://example.com", originAttributes: {} }];
let entries = [
{ origin: "http://example.com", originAttributes: {} },
{
origin: "http://example.com",
originAttributes: { inIsolatedMozBrowser: true },
},
];
// In that case, all permissions should be removed.
test(entries, getData({}), [
@ -75,4 +81,13 @@ function run_test() {
pm.ALLOW_ACTION,
pm.ALLOW_ACTION,
]);
// In that case, only the permissions related to a browserElement should be removed.
// All the other permissions should stay.
test(entries, getData({ inIsolatedMozBrowser: true }), [
pm.ALLOW_ACTION,
pm.UNKNOWN_ACTION,
pm.ALLOW_ACTION,
pm.ALLOW_ACTION,
]);
}

View file

@ -97,7 +97,17 @@ add_task(async function do_test() {
{}
);
let attrs = { userContextId: 1 };
let attrs = { inIsolatedMozBrowser: true };
let principal4 = Services.scriptSecurityManager.createContentPrincipal(
TEST_ORIGIN,
attrs
);
let principal5 = Services.scriptSecurityManager.createContentPrincipal(
TEST_ORIGIN_3,
attrs
);
attrs = { userContextId: 1 };
let principal1UserContext =
Services.scriptSecurityManager.createContentPrincipal(TEST_ORIGIN, attrs);
attrs = { privateBrowsingId: 1 };
@ -130,6 +140,10 @@ add_task(async function do_test() {
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION)
);
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION)
);
// Depending on the prefs there are two scenarios here:
// 1. We isolate by private browsing: The permission mgr should
// add default permissions for these principals too.
@ -141,6 +155,12 @@ add_task(async function do_test() {
pm.testPermissionFromPrincipal(principal1PrivateBrowsing, TEST_PERMISSION)
);
// Didn't add
Assert.equal(
Ci.nsIPermissionManager.UNKNOWN_ACTION,
pm.testPermissionFromPrincipal(principal5, TEST_PERMISSION)
);
// the permission should exist in the enumerator.
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
@ -169,6 +189,10 @@ add_task(async function do_test() {
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION)
);
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION)
);
// remove all should not throw and the default should remain
pm.removeAll();
@ -185,6 +209,10 @@ add_task(async function do_test() {
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION)
);
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION)
);
// Default permission should have also been added for private browsing.
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,

View file

@ -63,6 +63,14 @@ function run_test() {
let uri4_n = secMan.createContentPrincipal(uri4, {});
let uri5_n = secMan.createContentPrincipal(uri5, {});
attrs = { inIsolatedMozBrowser: true };
let uri0_y_ = secMan.createContentPrincipal(uri0, attrs);
let uri1_y_ = secMan.createContentPrincipal(uri1, attrs);
let uri2_y_ = secMan.createContentPrincipal(uri2, attrs);
let uri3_y_ = secMan.createContentPrincipal(uri3, attrs);
let uri4_y_ = secMan.createContentPrincipal(uri4, attrs);
let uri5_y_ = secMan.createContentPrincipal(uri5, attrs);
attrs = { userContextId: 1 };
let uri0_1 = secMan.createContentPrincipal(uri0, attrs);
let uri1_1 = secMan.createContentPrincipal(uri1, attrs);
@ -81,6 +89,8 @@ function run_test() {
pm.addFromPrincipal(uri0_n, "test/matches", pm.ALLOW_ACTION);
let perm_n = pm.getPermissionObject(uri0_n, "test/matches", true);
pm.addFromPrincipal(uri0_y_, "test/matches", pm.ALLOW_ACTION);
let perm_y_ = pm.getPermissionObject(uri0_y_, "test/matches", true);
pm.addFromPrincipal(uri0_1, "test/matches", pm.ALLOW_ACTION);
let perm_1 = pm.getPermissionObject(uri0_n, "test/matches", true);
pm.addFromPrincipal(uri0_cnn, "test/matches", pm.ALLOW_ACTION);
@ -93,6 +103,39 @@ function run_test() {
uri3_n,
uri4_n,
uri5_n,
uri0_y_,
uri1_y_,
uri2_y_,
uri3_y_,
uri4_y_,
uri5_y_,
uri2_1,
uri3_1,
uri4_1,
uri5_1,
uri0_cnn,
uri1_cnn,
uri2_cnn,
uri3_cnn,
uri4_cnn,
uri5_cnn,
]);
matches_always(perm_y_, [uri0_y_]);
matches_weak(perm_y_, [uri1_y_]);
matches_never(perm_y_, [
uri2_y_,
uri3_y_,
uri4_y_,
uri5_y_,
uri0_n,
uri1_n,
uri2_n,
uri3_n,
uri4_n,
uri5_n,
uri0_1,
uri1_1,
uri2_1,
uri3_1,
uri4_1,
@ -112,6 +155,12 @@ function run_test() {
uri3_n,
uri4_n,
uri5_n,
uri0_y_,
uri1_y_,
uri2_y_,
uri3_y_,
uri4_y_,
uri5_y_,
uri2_1,
uri3_1,
uri4_1,
@ -131,6 +180,12 @@ function run_test() {
uri3_n,
uri4_n,
uri5_n,
uri0_y_,
uri1_y_,
uri2_y_,
uri3_y_,
uri4_y_,
uri5_y_,
uri2_1,
uri3_1,
uri4_1,

View file

@ -106,7 +106,7 @@ add_task(async function test() {
["https://foo.com", "storageAccessAPI^https://foo.com", 2, 0, 0, 0],
["http://foo.com", "storageAccessAPI^https://bar.com", 2, 0, 0, 0],
["http://foo.com", "storageAccessAPI^https://bar.com", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
];
let found = expected.map(it => 0);

View file

@ -144,6 +144,7 @@ add_task(async function test() {
// The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
// and http://foo.com or a subdomain are never visited.
// ["http://foo.com", "A", 1, 0, 0],
// ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
//
// Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
// for subdomains of foo.com either
@ -152,14 +153,15 @@ add_task(async function test() {
["https://foo.com", "A", 1, 0, 0],
["https://foo.com", "C", 1, 0, 0],
["https://foo.com^inBrowser=1", "A", 1, 0, 0],
["https://sub.foo.com", "B", 1, 0, 0],
["https://subber.sub.foo.com", "B", 1, 0, 0],
// bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
["http://bar.ca", "B", 1, 0, 0],
["https://bar.ca", "B", 1, 0, 0],
["http://bar.ca", "A", 1, 0, 0],
["https://bar.ca", "A", 1, 0, 0],
["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
["file:///some/path/to/file.html", "A", 1, 0, 0],
["file:///another/file.html", "A", 1, 0, 0],
@ -167,6 +169,7 @@ add_task(async function test() {
// also have these entries
["ftp://foo.com:8000", "A", 1, 0, 0],
["ftp://foo.com:8000", "C", 1, 0, 0],
["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
// In addition, because we search for port/scheme combinations under eTLD+1, we should have the
// following entries

View file

@ -178,19 +178,21 @@ add_task(function test() {
let expected = [
["http://foo.com", "A", 1, 0, 0],
["http://foo.com", "C", 1, 0, 0],
["http://foo.com^inBrowser=1", "A", 1, 0, 0],
["http://sub.foo.com", "B", 1, 0, 0],
["http://subber.sub.foo.com", "B", 1, 0, 0],
["https://foo.com", "A", 1, 0, 0],
["https://foo.com", "C", 1, 0, 0],
["https://foo.com^inBrowser=1", "A", 1, 0, 0],
["https://sub.foo.com", "B", 1, 0, 0],
["https://subber.sub.foo.com", "B", 1, 0, 0],
// bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
["http://bar.ca", "B", 1, 0, 0],
["https://bar.ca", "B", 1, 0, 0],
["http://bar.ca", "A", 1, 0, 0],
["https://bar.ca", "A", 1, 0, 0],
["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
["file:///some/path/to/file.html", "A", 1, 0, 0],
["file:///another/file.html", "A", 1, 0, 0],

View file

@ -211,6 +211,7 @@ add_task(async function test() {
// The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
// and http://foo.com or a subdomain are never visited.
// ["http://foo.com", "A", 1, 0, 0],
// ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
//
// Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
// for subdomains of foo.com either
@ -219,14 +220,15 @@ add_task(async function test() {
["https://foo.com", "A", 1, 0, 0],
["https://foo.com", "C", 1, 0, 0],
["https://foo.com^inBrowser=1", "A", 1, 0, 0],
["https://sub.foo.com", "B", 1, 0, 0],
["https://subber.sub.foo.com", "B", 1, 0, 0],
// bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
["http://bar.ca", "B", 1, 0, 0],
["https://bar.ca", "B", 1, 0, 0],
["http://bar.ca", "A", 1, 0, 0],
["https://bar.ca", "A", 1, 0, 0],
["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
["file:///some/path/to/file.html", "A", 1, 0, 0],
["file:///another/file.html", "A", 1, 0, 0],
@ -234,6 +236,7 @@ add_task(async function test() {
// also have these entries
["ftp://foo.com:8000", "A", 1, 0, 0],
["ftp://foo.com:8000", "C", 1, 0, 0],
["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
// In addition, because we search for port/scheme combinations under eTLD+1, we should have the
// following entries

View file

@ -102,6 +102,7 @@ add_task(function test() {
let expected = [
["https://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
["http://127.0.0.1", "B", 2, 0, 0, 0],
["http://localhost", "B", 2, 0, 0, 0],

View file

@ -212,6 +212,7 @@ add_task(async function test() {
// The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
// and http://foo.com or a subdomain are never visited.
// ["http://foo.com", "A", 1, 0, 0],
// ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
//
// Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
// for subdomains of foo.com either
@ -220,14 +221,15 @@ add_task(async function test() {
["https://foo.com", "A", 1, 0, 0],
["https://foo.com", "C", 1, 0, 0],
["https://foo.com^inBrowser=1", "A", 1, 0, 0],
["https://sub.foo.com", "B", 1, 0, 0],
["https://subber.sub.foo.com", "B", 1, 0, 0],
// bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
["http://bar.ca", "B", 1, 0, 0],
["https://bar.ca", "B", 1, 0, 0],
["http://bar.ca", "A", 1, 0, 0],
["https://bar.ca", "A", 1, 0, 0],
["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
["file:///some/path/to/file.html", "A", 1, 0, 0],
["file:///another/file.html", "A", 1, 0, 0],
@ -235,6 +237,7 @@ add_task(async function test() {
// also have these entries
["ftp://foo.com:8000", "A", 1, 0, 0],
["ftp://foo.com:8000", "C", 1, 0, 0],
["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
// In addition, because we search for port/scheme combinations under eTLD+1, we should have the
// following entries

View file

@ -101,6 +101,7 @@ add_task(function test() {
let expected = [
["https://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
];
let found = expected.map(it => 0);

View file

@ -164,7 +164,7 @@ add_task(async function test() {
let created7 = [
insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
insertOrigin("http://foo.com^inBrowser=1", "C", 2, 0, 0, 0),
insertOrigin("http://foo.com^inBrowser=1", "A", 2, 0, 0, 0),
insertOrigin("https://192.0.2.235", "A", 2, 0, 0),
];
@ -174,7 +174,7 @@ add_task(async function test() {
insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
insertHost("foo.com", "C", 1, 0, 0, 0, 2000, true),
insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
@ -224,7 +224,7 @@ add_task(async function test() {
// We should have kept the previously migrated entries
["https://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "C", 2, 0, 0, 0],
["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
// Make sure that we also support localhost, and IP addresses
["https://localhost:8080", "A", 1, 0, 0],

View file

@ -172,7 +172,7 @@ add_task(async function test() {
let expected = [
["https://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com", "A", 2, 0, 0, 0],
["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
];
let found = expected.map(it => 0);

View file

@ -249,6 +249,18 @@ static nsIContent* GetClickableAncestor(
return content;
}
// Bug 921928: we don't have access to the content of remote iframe.
// So fluffing won't go there. We do an optimistic assumption here:
// that the content of the remote iframe needs to be a target.
if (content->IsHTMLElement(nsGkAtoms::iframe) &&
content->AsElement()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::mozbrowser,
nsGkAtoms::_true, eIgnoreCase) &&
content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
nsGkAtoms::_true, eIgnoreCase)) {
return content;
}
// See nsCSSFrameConstructor::FindXULTagData. This code is not
// really intended to be used with XUL, though.
if (content->IsAnyOfXULElements(

View file

@ -3450,8 +3450,11 @@ void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
static inline bool AttributeInfluencesOtherPseudoClassState(
const Element& aElement, const nsAtom* aAttribute) {
// We must record some state for :-moz-table-border-nonzero and
// :-moz-select-list-box.
// We must record some state for :-moz-browser-frame,
// :-moz-table-border-nonzero, and :-moz-select-list-box.
if (aAttribute == nsGkAtoms::mozbrowser) {
return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame);
}
if (aAttribute == nsGkAtoms::border) {
return aElement.IsHTMLElement(nsGkAtoms::table);

View file

@ -0,0 +1,177 @@
<!DOCTYPE HTML>
<html id="html" style="height:100%">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=921928
-->
<head>
<title>Test for bug 921928</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
#dialer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 50px;
background: green;
}
#apps {
position: absolute;
left: 0;
top: 51px;
width: 100%;
height: 100px;
background: blue;
}
.hit {
position: absolute;
width: 3px;
height: 3px;
z-index: 20;
background: red;
border: 1px solid red;
}
</style>
</head>
<body id="body" style="margin:0; width:100%; height:100%">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var prefs = [
["ui.mouse.radius.enabled", true],
["ui.mouse.radius.inputSource.touchOnly", false],
["ui.mouse.radius.leftmm", 12],
["ui.mouse.radius.topmm", 8],
["ui.mouse.radius.rightmm", 4],
["ui.mouse.radius.bottommm", 4],
["ui.mouse.radius.visitedweight", 50],
];
var eventTarget;
var debugHit = [];
function endTest() {
SimpleTest.finish();
SpecialPowers.removePermission("browser", location.href);
for (var pref in prefs) {
SpecialPowers.pushPrefEnv({"clear": pref[0]}, function() {});
}
}
function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
eventTarget = null;
synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
try {
is(eventTarget.id, idTarget,
"checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
} catch (ex) {
ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
}
}
function showDebug() {
for (var i = 0; i < debugHit.length; i++) {
document.body.appendChild(debugHit[i]);
}
var screenshot = SpecialPowers.snapshotWindow(window, true);
dump('IMAGE:' + screenshot.toDataURL() + '\n');
}
/*
Setup the test environment: enabling event fluffing (all ui.* preferences),
and enabling remote process.
*/
function setupTest(cont) {
SpecialPowers.addPermission("browser", true, document);
SpecialPowers.pushPrefEnv({"set": prefs}, cont);
}
function execTest() {
/*
Creating two iframes that mimics the attention screen behavior on the
device:
- the 'dialer' iframe is the attention screen you have when a call is
in place. it is a green bar, so we copy it as green here too
- the 'apps' iframe mimics another application that is being run, be it
dialer, sms, ..., anything that the user might want to trigger during
a call
The bug we intent to reproduce here is that in this case, if the user taps
onto the top of the 'apps', the event fluffing code will in fact redirect
the event to the 'dialer' iframe. In practice, this is bug 921928 where
during a call the user wants to place a second call, and while typing the
phone number, wants to tap onto the 'delete' key to erase a digit, but ends
tapping and activating the dialer.
*/
var dialer = document.createElement('iframe');
dialer.id = 'dialer';
dialer.src = '';
// Force OOP
dialer.setAttribute('mozbrowser', 'true');
dialer.setAttribute('remote', 'true');
document.body.appendChild(dialer);
var apps = document.createElement('iframe');
apps.id = 'apps';
apps.src = 'bug921928_event_target_iframe_apps_oop.html';
// Force OOP
apps.setAttribute('mozbrowser', 'true');
apps.setAttribute('remote', 'true');
document.body.appendChild(apps);
var handleEvent = function(event) {
eventTarget = event.target;
// We draw a small red div to show where the event has tapped
var hit = document.createElement('div');
hit.style.left = (event.clientX - 1.5) + 'px';
hit.style.top = (event.clientY - 1.5) + 'px';
hit.classList.add('hit');
debugHit.push(hit);
};
// In real life, the 'dialer' has a 'mousedown', so we mimic one too,
// to reproduce the same behavior
dialer.addEventListener('mousedown', function(e) {});
// This event listener is just here to record what iframe has been hit,
// and sets the 'eventTarget' to the iframe's id value so that the
// testMouseClick() code can correctly check. We cannot add it on the
// 'apps' otherwise it will alter the behavior of the test.
document.addEventListener('mousedown', handleEvent);
// In the following, the coordinates are relative to the iframe
// First, we check that tapping onto the 'dialer' correctly triggers the
// dialer.
testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with mouse input");
testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with touch input", {
inputSource: MouseEvent.MOZ_SOURCE_TOUCH
});
// Now this is it: we tap inside 'apps', but very close to the border between
// 'apps' and 'dialer'. Without the fix from this bug, this test will fail.
testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for mouse input");
testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for touch input", {
inputSource: MouseEvent.MOZ_SOURCE_TOUCH
});
// Show small red spots of where the click happened
// showDebug();
endTest();
}
function runTest() {
setupTest(execTest);
}
addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
</script>
</body>
</html>

View file

@ -21,6 +21,7 @@
#include "nsCanvasFrame.h"
#include "nsLayoutUtils.h"
#include "nsSubDocumentFrame.h"
#include "nsIMozBrowserFrame.h"
#include "nsPlaceholderFrame.h"
#include "MobileViewportManager.h"

View file

@ -27,6 +27,7 @@
#include "mozilla/dom/DocumentInlines.h"
#include "nsILoadContext.h"
#include "nsIFrame.h"
#include "nsIMozBrowserFrame.h"
#include "nsINode.h"
#include "nsIURI.h"
#include "nsFontMetrics.h"

View file

@ -8,7 +8,7 @@
# documentation and how to modify this file.
repo: mozilla-central
created_at: '2021-10-14T12:50:40.073465'
updated_at: '2024-02-13T20:45:29.452204'
updated_at: '2024-02-12T11:38:43.189353'
export:
path: ./docs/mots/index.rst
format: rst
@ -1153,6 +1153,22 @@ modules:
- *mstange
machine_name: core_apz_graphics_submodule
- name: 'Core: Browser WebAPI'
description: Web API for rendering apps, browser windows and widgets.
includes:
- dom/browser-element/**/*
meta:
owners_emeritus:
- Kan-Ru Chen
peers_emeritus:
- Fabrice Desré
group: dev-webapi
components:
- Core::DOM
owners:
- *smaug
machine_name: core_browser_webapi
- name: 'Core: Build and Release Tools'
description: Tools related to build and release automation and configuration of
release builds.
@ -4207,5 +4223,5 @@ modules:
- Ryan Tilder
group: dev-platform
hashes:
config: 9080f32f58cb9b83b823208e0a6f31b9e48cb0b9
export: f76b4dffdb520c5cd0045f89775b565af422d933
config: 308df3f31d4c18ad2b33ff42089ec56cca1566a1
export: de95c24371652728c9a29fb28c859cc845d9e04a

View file

@ -187,6 +187,7 @@ extern "C" const char* __lsan_default_suppressions() {
"leak:js::frontend::Parse\n"
"leak:xpc::CIGSHelper\n"
"leak:mozJSModuleLoader\n"
"leak:mozilla::xpcom::ConstructJSMComponent\n"
"leak:XPCWrappedNativeJSOps\n"
// End of suppressions.

View file

@ -90,6 +90,7 @@ class KeyParser : protected Tokenizer {
break;
case 'b':
// Leaving to be able to read and understand oldformatted entries
originAttribs.mInIsolatedMozBrowser = true;
break;
case 'a':
isAnonymous = true;

View file

@ -132,7 +132,14 @@ NS_IMETHODIMP
ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
nsresult rv;
OriginAttributes attrs;
int32_t inIsolatedMozBrowser;
rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
NS_ENSURE_SUCCESS(rv, rv);
// Create an originAttributes object by inIsolatedMozBrowser.
// Then create the originSuffix string from this object.
OriginAttributes attrs(inIsolatedMozBrowser != 0);
nsAutoCString suffix;
attrs.CreateSuffix(suffix);
@ -198,7 +205,7 @@ SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
RefPtr<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsInt32(false);
rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);

View file

@ -123,7 +123,7 @@ const char* NeckoParent::GetValidatedOriginAttributes(
if (!aSerialized.IsNotNull()) {
// If serialized is null, we cannot validate anything. We have to assume
// that this requests comes from a SystemPrincipal.
aAttrs = OriginAttributes();
aAttrs = OriginAttributes(false);
} else {
aAttrs = aSerialized.mOriginAttributes;
}

View file

@ -274,10 +274,12 @@ ChannelEventSink.prototype = {
/**
* A helper class to construct origin attributes.
*/
function OriginAttributes(privateId) {
function OriginAttributes(inIsolatedMozBrowser, privateId) {
this.inIsolatedMozBrowser = inIsolatedMozBrowser;
this.privateBrowsingId = privateId;
}
OriginAttributes.prototype = {
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
};

View file

@ -0,0 +1,92 @@
"use strict";
function createURI(s) {
return Services.io.newURI(s);
}
function run_test() {
// Set up a profile.
do_get_profile();
var secMan = Services.scriptSecurityManager;
const kURI1 = "http://example.com";
var app = secMan.createContentPrincipal(createURI(kURI1), {});
var appbrowser = secMan.createContentPrincipal(createURI(kURI1), {
inIsolatedMozBrowser: true,
});
var am = Cc["@mozilla.org/network/http-auth-manager;1"].getService(
Ci.nsIHttpAuthManager
);
am.setAuthIdentity(
"http",
"a.example.com",
-1,
"basic",
"realm",
"",
"example.com",
"user",
"pass",
false,
app
);
am.setAuthIdentity(
"http",
"a.example.com",
-1,
"basic",
"realm",
"",
"example.com",
"user3",
"pass3",
false,
appbrowser
);
Services.clearData.deleteDataFromOriginAttributesPattern({
inIsolatedMozBrowser: true,
});
var domain = { value: "" },
user = { value: "" },
pass = { value: "" };
try {
am.getAuthIdentity(
"http",
"a.example.com",
-1,
"basic",
"realm",
"",
domain,
user,
pass,
false,
appbrowser
);
Assert.equal(false, true); // no identity should be present
} catch (x) {
Assert.equal(domain.value, "");
Assert.equal(user.value, "");
Assert.equal(pass.value, "");
}
am.getAuthIdentity(
"http",
"a.example.com",
-1,
"basic",
"realm",
"",
domain,
user,
pass,
false,
app
);
Assert.equal(domain.value, "example.com");
Assert.equal(user.value, "user");
Assert.equal(pass.value, "pass");
}

View file

@ -20,24 +20,27 @@ function cached_handler(metadata, response) {
handlers_called++;
}
function makeChan(url, userContextId) {
function makeChan(url, inIsolatedMozBrowser, userContextId) {
var chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadInfo.originAttributes = { userContextId };
chan.loadInfo.originAttributes = { inIsolatedMozBrowser, userContextId };
return chan;
}
// [userContextId, expected_handlers_called]
// [inIsolatedMozBrowser, userContextId, expected_handlers_called]
var firstTests = [
[0, 1],
[1, 1],
[false, 0, 1],
[true, 0, 1],
[false, 1, 1],
[true, 1, 1],
];
var secondTests = [
[0, 0],
[1, 1],
[1, 0],
[false, 0, 0],
[true, 0, 0],
[false, 1, 1],
[true, 1, 0],
];
async function run_all_tests() {
@ -78,9 +81,9 @@ function run_test() {
});
}
function test_channel(userContextId, expected) {
function test_channel(inIsolatedMozBrowser, userContextId, expected) {
return new Promise(resolve => {
var chan = makeChan(URL, userContextId);
var chan = makeChan(URL, inIsolatedMozBrowser, userContextId);
chan.asyncOpen(
new ChannelListener(doneFirstLoad.bind(null, resolve), expected)
);
@ -90,7 +93,7 @@ function test_channel(userContextId, expected) {
function doneFirstLoad(resolve, req, buffer, expected) {
// Load it again, make sure it hits the cache
var oa = req.loadInfo.originAttributes;
var chan = makeChan(URL, oa.userContextId);
var chan = makeChan(URL, oa.isInIsolatedMozBrowserElement, oa.userContextId);
chan.asyncOpen(
new ChannelListener(doneSecondLoad.bind(null, resolve), expected)
);

View file

@ -36,11 +36,19 @@ function inChildProcess() {
var tests = [
{
cookieName: "LCC_App0_BrowF_PrivF",
originAttributes: new OriginAttributes(0),
originAttributes: new OriginAttributes(0, false, 0),
},
{
cookieName: "LCC_App0_BrowT_PrivF",
originAttributes: new OriginAttributes(0, true, 0),
},
{
cookieName: "LCC_App1_BrowF_PrivF",
originAttributes: new OriginAttributes(1),
originAttributes: new OriginAttributes(1, false, 0),
},
{
cookieName: "LCC_App1_BrowT_PrivF",
originAttributes: new OriginAttributes(1, true, 0),
},
];

View file

@ -106,6 +106,8 @@ skip-if = [
["test_auth_dialog_permission.js"]
["test_auth_jar.js"]
["test_auth_multiple.js"]
["test_auth_proxy.js"]

View file

@ -141,8 +141,11 @@ export class FindBarChild extends JSWindowActorChild {
return false;
}
if (win.XULFrameElement.isInstance(elt)) {
// If we're targeting an embedded XULFrameElement
if (
(win.HTMLIFrameElement.isInstance(elt) && elt.mozbrowser) ||
win.XULFrameElement.isInstance(elt)
) {
// If we're targeting a mozbrowser iframe or an embedded XULFrameElement
// (e.g. about:addons extensions inline options page), do not activate
// fast find.
return false;

View file

@ -470,6 +470,8 @@
"resource://gre/modules/BookmarkHTMLUtils.jsm": "toolkit/components/places/BookmarkHTMLUtils.jsm",
"resource://gre/modules/BookmarkJSONUtils.jsm": "toolkit/components/places/BookmarkJSONUtils.jsm",
"resource://gre/modules/Bookmarks.jsm": "toolkit/components/places/Bookmarks.jsm",
"resource://gre/modules/BrowserElementParent.jsm": "dom/browser-element/BrowserElementParent.jsm",
"resource://gre/modules/BrowserElementPromptService.jsm": "dom/browser-element/BrowserElementPromptService.jsm",
"resource://gre/modules/BrowserTelemetryUtils.jsm": "toolkit/modules/BrowserTelemetryUtils.jsm",
"resource://gre/modules/BrowserUtils.jsm": "toolkit/modules/BrowserUtils.jsm",
"resource://gre/modules/CSV.js": "toolkit/components/passwordmgr/CSV.js",

View file

@ -83,6 +83,10 @@ const nsXPTInterface gInterfaces[] = {
//# @interfaces@
};
const StringOffset gComponentJSMs[] = {
//# @component_jsms@
};
const StringOffset gComponentESModules[] = {
//# @component_esmodules@
};
@ -110,7 +114,7 @@ bool ContractEntry::Matches(const nsACString& aContractID) const {
enum class ComponentType { JSM, ESM };
template <ComponentType type>
[[maybe_unused]] static nsresult ConstructJSMOrESMComponent(const nsACString& aURI,
static nsresult ConstructJSMOrESMComponent(const nsACString& aURI,
const char* aConstructor,
nsISupports** aResult) {
if (!nsComponentManagerImpl::JSLoaderReady()) {
@ -144,6 +148,13 @@ template <ComponentType type>
(void**)aResult);
}
static nsresult ConstructJSMComponent(const nsACString& aURI,
const char* aConstructor,
nsISupports** aResult) {
return ConstructJSMOrESMComponent<ComponentType::JSM>(
aURI, aConstructor, aResult);
}
static nsresult ConstructESModuleComponent(const nsACString& aURI,
const char* aConstructor,
nsISupports** aResult) {
@ -338,6 +349,20 @@ const StaticProtocolHandler* StaticProtocolHandler::Lookup(const nsACString& aSc
return false;
}
/* static */ already_AddRefed<nsIUTF8StringEnumerator>
StaticComponents::GetComponentJSMs() {
auto jsms = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentJSMs));
for (const auto& entry : gComponentJSMs) {
jsms->AppendElement(GetString(entry));
}
nsCOMPtr<nsIUTF8StringEnumerator> result;
MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result),
jsms.release()));
return result.forget();
}
/* static */ already_AddRefed<nsIUTF8StringEnumerator>
StaticComponents::GetComponentESModules() {
auto esModules = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentESModules));

View file

@ -267,6 +267,7 @@ class StaticComponents final {
static bool InvalidateContractID(const nsACString& aContractID,
bool aInvalid = true);
static already_AddRefed<nsIUTF8StringEnumerator> GetComponentJSMs();
static already_AddRefed<nsIUTF8StringEnumerator> GetComponentESModules();
static Span<const JSServiceEntry> GetJSServices();

View file

@ -428,6 +428,14 @@ class ModuleEntry(object):
)
return res
if self.jsm:
res += (
" nsCOMPtr<nsISupports> inst;\n"
" MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n"
" %s,\n"
" getter_AddRefs(inst)));"
"\n" % (json.dumps(self.jsm), json.dumps(self.constructor))
)
elif self.esModule:
res += (
" nsCOMPtr<nsISupports> inst;\n"
@ -943,6 +951,9 @@ def gen_substs(manifests):
gen_includes(substs, headers)
substs["component_jsms"] = (
"\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n"
)
substs["component_esmodules"] = (
"\n".join(
" %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules)

View file

@ -1472,6 +1472,14 @@ nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) {
return rv;
}
NS_IMETHODIMP
nsComponentManagerImpl::GetComponentJSMs(nsIUTF8StringEnumerator** aJSMs) {
nsCOMPtr<nsIUTF8StringEnumerator> result =
StaticComponents::GetComponentJSMs();
result.forget(aJSMs);
return NS_OK;
}
NS_IMETHODIMP
nsComponentManagerImpl::GetComponentESModules(
nsIUTF8StringEnumerator** aESModules) {

View file

@ -96,6 +96,12 @@ interface nsIComponentManager : nsISupports
*/
nsIArray getManifestLocations();
/**
* Returns a list of JSM URLs which are used to create components. This
* should only be used in automation.
*/
nsIUTF8StringEnumerator getComponentJSMs();
/**
* Returns a list of ESM URLs which are used to create components. This
* should only be used in automation.

View file

@ -208,6 +208,7 @@ STATIC_ATOMS = [
Atom("box", "box"),
Atom("br", "br"),
Atom("browser", "browser"),
Atom("mozbrowser", "mozbrowser"),
Atom("button", "button"),
Atom("callTemplate", "call-template"),
Atom("canvas", "canvas"),