{"id":2480,"date":"2014-04-30T15:14:49","date_gmt":"2014-04-30T20:14:49","guid":{"rendered":"http:\/\/mikeconley.ca\/blog\/?p=2480"},"modified":"2023-12-20T16:25:12","modified_gmt":"2023-12-20T21:25:12","slug":"electrolysis-code-spelunking-how-links-open-new-windows-in-firefox","status":"publish","type":"post","link":"https:\/\/mikeconley.ca\/blog\/2014\/04\/30\/electrolysis-code-spelunking-how-links-open-new-windows-in-firefox\/","title":{"rendered":"Electrolysis Code Spelunking: How links open new windows in Firefox"},"content":{"rendered":"<p>Hey. I&#8217;ve started hacking on Electrolysis bugs. I&#8217;m normally a front-end engineer working on Firefox desktop, but I&#8217;ve been temporarily loaned out to help get Electrolysis ready to be enabled by default on Nightly.<\/p>\n<p>I&#8217;m working on <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=989501\">bug 989501<\/a>. Basically, when you click on a link that targets &#8220;_blank&#8221; or uses window.open, we open a new tab instead. That&#8217;s no good &#8211; <a href=\"http:\/\/kb.mozillazine.org\/Browser.link.open_newwindow\">assuming the user&#8217;s profile is set to allow it<\/a>, we should open the link in a new window.<\/p>\n<p>In order to fix this, I need a clearer picture on what happens in the Firefox platform when we click on one of these links.<\/p>\n<p>This isn&#8217;t really a tutorial &#8211; I&#8217;m not going to go out of my way to explain much here. Think of this more as a public posting of my notes during my exploration.<\/p>\n<p>So, here goes.<\/p>\n<p>(Note that the code in this post was current as of revision 400a31da59a9 of mozilla-central, so if you&#8217;re reading this in the future, it&#8217;s possible that some stuff has greatly changed).<\/p>\n<p>I know for a fact that once the link is clicked, we eventually call mozilla::dom::TabChild::ProvideWindow. I know this because of conversations I&#8217;ve had with smaug, billm and jdm in and out of Bugzilla, IRC, and meatspace.<\/p>\n<p>Because I know this, I can hook up gdb to see how I get to that call. <a href=\"https:\/\/mikeconley.ca\/blog\/2014\/04\/25\/electrolysis-debugging-child-processes-of-content-for-make-benefit-glorious-browser-of-firefox\/\">I have some notes here<\/a> on how to hook up gdb to the content process of an e10s window.<\/p>\n<p>Once that&#8217;s hooked up, I set a breakpoint on mozilla::dom::TabChild::ProvideWindow, and click on a link somewhere with target=&#8221;_blank&#8221;.<\/p>\n<p>I hit my breakpoint, and I get a backtrace. Ready for it? Here we go:<\/p>\n<pre>#0\u00a0 mozilla::dom::TabChild::ProvideWindow (this=0x109afb400, aParent=0x10b098820, aChromeFlags=4094, aCalledFromJS=false, aPositionSpecified=false, aSizeSpecified=false, aURI=0xffe, aName=@0x0, aFeatures=@0x0, aWindowIsNew=0x10b098820, aReturn=0x7fff5fbfb648) at TabChild.cpp:1201\r\n#1\u00a0 0x00000001018682e4 in nsWindowWatcher::OpenWindowInternal (this=0x10b05b540, aParent=0x10b098820, aUrl=&lt;value temporarily unavailable, due to optimizations&gt;, aName=&lt;value temporarily unavailable, due to optimizations&gt;, aFeatures=&lt;value temporarily unavailable, due to optimizations&gt;, aCalledFromJS=false, aDialog=&lt;value temporarily unavailable, due to optimizations&gt;, aNavigate=&lt;value temporarily unavailable, due to optimizations&gt;, _retval=&lt;value temporarily unavailable, due to optimizations&gt;) at nsWindowWatcher.cpp:601\r\n#2\u00a0 0x0000000101869544 in non-virtual thunk to nsWindowWatcher::OpenWindow2(nsIDOMWindow*, char const*, char const*, char const*, bool, bool, bool, nsISupports*, nsIDOMWindow**) () at nsWindowWatcher.cpp:417\r\n#3\u00a0 0x0000000100e5dc63 in nsGlobalWindow::OpenInternal (this=0x10b098800, aUrl=@0x7fff5fbfbf90, aName=@0x7fff5fbfc038, aOptions=@0x103d77320, aDialog=false, aContentModal=false, aCalleePrincipal=&lt;value temporarily unavailable, due to optimizations&gt;, aJSCallerContext=&lt;value temporarily unavailable, due to optimizations&gt;, aReturn=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/dom\/base\/nsGlobalWindow.cpp:11498\r\n#4\u00a0 0x0000000100e5e3a4 in non-virtual thunk to nsGlobalWindow::OpenNoNavigate(nsAString_internal const&amp;, nsAString_internal const&amp;, nsAString_internal const&amp;, nsIDOMWindow**) () at \/Users\/mikeconley\/Projects\/mozilla-central\/dom\/base\/nsGlobalWindow.cpp:7463\r\n#5\u00a0 0x000000010184d99d in nsDocShell::InternalLoad (this=&lt;value temporarily unavailable, due to optimizations&gt;, aURI=0x113eed200, aReferrer=0x1134c0fe0, aOwner=0x114a69070, aFlags=0, aWindowTarget=0x10b098820, aLoadType=&lt;value temporarily unavailable, due to optimizations&gt;, aSHEntry=&lt;value temporarily unavailable, due to optimizations&gt;, aSourceDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aRequest=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:9079\r\n#6\u00a0 0x0000000101855758 in nsDocShell::OnLinkClickSync (this=0x10b075000, aContent=0x112865eb0, aURI=0x113eed3c0, aTargetSpec=&lt;value temporarily unavailable, due to optimizations&gt;, aFileName=@0x106f27f10, aPostDataStream=0x0, aDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aRequest=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:12699\r\n#7\u00a0 0x0000000101857f85 in mozilla::Maybe&lt;mozilla::AutoCxPusher&gt;::~Maybe () at \/Users\/mikeconley\/Projects\/mozilla-central\/obj-x86_64-apple-darwin12.5.0\/dist\/include\/nsCxPusher.h:12499\r\n#8\u00a0 0x0000000101857f85 in nsCxPusher::~nsCxPusher () at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:41\r\n#9\u00a0 0x0000000101857f85 in nsCxPusher::~nsCxPusher () at \/Users\/mikeconley\/Projects\/mozilla-central\/obj-x86_64-apple-darwin12.5.0\/dist\/include\/nsCxPusher.h:66\r\n#10 0x0000000101857f85 in OnLinkClickEvent::Run (this=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:12502\r\n#11 0x0000000100084f60 in nsThread::ProcessNextEvent (this=0x106f245e0, mayWait=false, result=0x7fff5fbfc947) at nsThread.cpp:715\r\n#12 0x0000000100023241 in NS_ProcessPendingEvents (thread=&lt;value temporarily unavailable, due to optimizations&gt;, timeout=20) at nsThreadUtils.cpp:210\r\n#13 0x0000000100d41c47 in nsBaseAppShell::NativeEventCallback (this=0x1096e8660) at nsBaseAppShell.cpp:98\r\n#14 0x0000000100cfdba1 in nsAppShell::ProcessGeckoEvents (aInfo=0x1096e8660) at nsAppShell.mm:388\r\n#15 0x00007fff86adeb31 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()\r\n#16 0x00007fff86ade455 in __CFRunLoopDoSources0 ()\r\n#17 0x00007fff86b017f5 in __CFRunLoopRun ()\r\n#18 0x00007fff86b010e2 in CFRunLoopRunSpecific ()\r\n#19 0x00007fff8ad65eb4 in RunCurrentEventLoopInMode ()\r\n#20 0x00007fff8ad65c52 in ReceiveNextEventCommon ()\r\n#21 0x00007fff8ad65ae3 in BlockUntilNextEventMatchingListInMode ()\r\n#22 0x00007fff8cce1533 in _DPSNextEvent ()\r\n#23 0x00007fff8cce0df2 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] ()\r\n#24 0x0000000100cfd266 in -[GeckoNSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] (self=0x106f801a0, _cmd=&lt;value temporarily unavailable, due to optimizations&gt;, mask=18446744073709551615, expiration=0x422d63c37f00000d, mode=0x7fff7205e1c0, flag=1 '\\001') at nsAppShell.mm:165\r\n#25 0x00007fff8ccd81a3 in -[NSApplication run] ()\r\n#26 0x0000000100cfe32b in nsAppShell::Run (this=&lt;value temporarily unavailable, due to optimizations&gt;) at nsAppShell.mm:746\r\n#27 0x000000010199b3dc in XRE_RunAppShell () at \/Users\/mikeconley\/Projects\/mozilla-central\/toolkit\/xre\/nsEmbedFunctions.cpp:679\r\n#28 0x00000001002a0dae in MessageLoop::AutoRunState::~AutoRunState () at message_loop.cc:229\r\n#29 0x00000001002a0dae in MessageLoop::AutoRunState::~AutoRunState () at \/Users\/mikeconley\/Projects\/mozilla-central\/ipc\/chromium\/src\/base\/message_loop.h:197\r\n#30 0x00000001002a0dae in MessageLoop::Run (this=0x0) at message_loop.cc:503\r\n#31 0x000000010199b0cd in XRE_InitChildProcess (aArgc=&lt;value temporarily unavailable, due to optimizations&gt;, aArgv=&lt;value temporarily unavailable, due to optimizations&gt;, aProcess=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/toolkit\/xre\/nsEmbedFunctions.cpp:516\r\n#32 0x0000000100000f1d in main (argc=&lt;value temporarily unavailable, due to optimizations&gt;, argv=0x7fff5fbff4d8) at \/Users\/mikeconley\/Projects\/mozilla-central\/ipc\/app\/MozillaRuntimeMain.cpp:149<\/pre>\n<p>Oh my. Well, the good news is, we can chop off a good chunk of the lower half because that&#8217;s all message \/ event loop stuff. That&#8217;s going to be in every single backtrace ever, pretty much, so I can just ignore it. Here&#8217;s the more important stuff:<\/p>\n<pre>#0\u00a0 mozilla::dom::TabChild::ProvideWindow (this=0x109afb400, aParent=0x10b098820, aChromeFlags=4094, aCalledFromJS=false, aPositionSpecified=false, aSizeSpecified=false, aURI=0xffe, aName=@0x0, aFeatures=@0x0, aWindowIsNew=0x10b098820, aReturn=0x7fff5fbfb648) at TabChild.cpp:1201\r\n#1\u00a0 0x00000001018682e4 in nsWindowWatcher::OpenWindowInternal (this=0x10b05b540, aParent=0x10b098820, aUrl=&lt;value temporarily unavailable, due to optimizations&gt;, aName=&lt;value temporarily unavailable, due to optimizations&gt;, aFeatures=&lt;value temporarily unavailable, due to optimizations&gt;, aCalledFromJS=false, aDialog=&lt;value temporarily unavailable, due to optimizations&gt;, aNavigate=&lt;value temporarily unavailable, due to optimizations&gt;, _retval=&lt;value temporarily unavailable, due to optimizations&gt;) at nsWindowWatcher.cpp:601\r\n#2\u00a0 0x0000000101869544 in non-virtual thunk to nsWindowWatcher::OpenWindow2(nsIDOMWindow*, char const*, char const*, char const*, bool, bool, bool, nsISupports*, nsIDOMWindow**) () at nsWindowWatcher.cpp:417\r\n#3\u00a0 0x0000000100e5dc63 in nsGlobalWindow::OpenInternal (this=0x10b098800, aUrl=@0x7fff5fbfbf90, aName=@0x7fff5fbfc038, aOptions=@0x103d77320, aDialog=false, aContentModal=false, aCalleePrincipal=&lt;value temporarily unavailable, due to optimizations&gt;, aJSCallerContext=&lt;value temporarily unavailable, due to optimizations&gt;, aReturn=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/dom\/base\/nsGlobalWindow.cpp:11498\r\n#4\u00a0 0x0000000100e5e3a4 in non-virtual thunk to nsGlobalWindow::OpenNoNavigate(nsAString_internal const&amp;, nsAString_internal const&amp;, nsAString_internal const&amp;, nsIDOMWindow**) () at \/Users\/mikeconley\/Projects\/mozilla-central\/dom\/base\/nsGlobalWindow.cpp:7463\r\n#5\u00a0 0x000000010184d99d in nsDocShell::InternalLoad (this=&lt;value temporarily unavailable, due to optimizations&gt;, aURI=0x113eed200, aReferrer=0x1134c0fe0, aOwner=0x114a69070, aFlags=0, aWindowTarget=0x10b098820, aLoadType=&lt;value temporarily unavailable, due to optimizations&gt;, aSHEntry=&lt;value temporarily unavailable, due to optimizations&gt;, aSourceDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aRequest=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:9079\r\n#6\u00a0 0x0000000101855758 in nsDocShell::OnLinkClickSync (this=0x10b075000, aContent=0x112865eb0, aURI=0x113eed3c0, aTargetSpec=&lt;value temporarily unavailable, due to optimizations&gt;, aFileName=@0x106f27f10, aPostDataStream=0x0, aDocShell=&lt;value temporarily unavailable, due to optimizations&gt;, aRequest=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:12699\r\n#7\u00a0 0x0000000101857f85 in mozilla::Maybe&lt;mozilla::AutoCxPusher&gt;::~Maybe () at \/Users\/mikeconley\/Projects\/mozilla-central\/obj-x86_64-apple-darwin12.5.0\/dist\/include\/nsCxPusher.h:12499\r\n#8\u00a0 0x0000000101857f85 in nsCxPusher::~nsCxPusher () at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:41\r\n#9\u00a0 0x0000000101857f85 in nsCxPusher::~nsCxPusher () at \/Users\/mikeconley\/Projects\/mozilla-central\/obj-x86_64-apple-darwin12.5.0\/dist\/include\/nsCxPusher.h:66\r\n#10 0x0000000101857f85 in OnLinkClickEvent::Run (this=&lt;value temporarily unavailable, due to optimizations&gt;) at \/Users\/mikeconley\/Projects\/mozilla-central\/docshell\/base\/nsDocShell.cpp:12502<\/pre>\n<p>That&#8217;s a bit more manageable.<\/p>\n<p>So we start inside something called a docshell. I&#8217;ve heard that term bandied about a lot, and I can&#8217;t say I&#8217;ve ever been too sure what it means, or what a docshell does, or why I should care.<\/p>\n<p>I found <a href=\"https:\/\/wiki.mozilla.org\/DocShell:Home_Page\">some<\/a> <a href=\"http:\/\/www-archive.mozilla.org\/projects\/embedding\/docshell.html\">documents<\/a> that make things a little bit clearer.<\/p>\n<p>Basically, my understanding is that a docshell is the thing that connects incoming stuff from some URI (this could be web content, or it might be a XUL document that&#8217;s loading the browser UI&#8230;), and connects it to the things that make stuff show up on your screen.<\/p>\n<p>So, pretty important.<\/p>\n<p>It seems to be a place where some utility methods and functions go as well, so it&#8217;s kind of this abstract thing that seems to have multiple purposes.<\/p>\n<p>But the most important thing for the purposes of this post is this: every time you load a document, you have a docshell taking care of it. All of these docshells are structured in a tree which is rooted with a docshell owner. This will come into play later.<\/p>\n<p>So one thing that a docshell does, is that it notices when a link was clicked inside of its content. That&#8217;s nsDocShell.cpp&#8217;d OnLinkClickEvent::Run, and that eventually makes its way over to nsDocShell::OnLinkClickSync.<\/p>\n<p>After some initial checks and balances to ensure that this thing really is a link we want to travel to, we get sent off to nsDocShell::InternalLoad.<\/p>\n<p>Inside there, there&#8217;s some more checking\u2026 there&#8217;s a policy check to make sure we&#8217;re allowed to open a link. Lots of security going on. Eventually I see this:<\/p>\n<pre>if (aWindowTarget &amp;&amp; *aWindowTarget)<\/pre>\n<p>That&#8217;s good. aWindowTarget maps to the target=&#8221;_blank&#8221; attribute in the anchor. So we&#8217;ll be entering this block.<\/p>\n<pre>\u00a0\u00a0\u00a0 if (aWindowTarget &amp;&amp; *aWindowTarget) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Locate the target DocShell.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIDocShellTreeItem&gt; targetItem;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rv = FindItemWithName(aWindowTarget, nullptr, this,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(targetItem));<\/pre>\n<p>So now we&#8217;re looking for the right docshell to load this new document in. That makes sense &#8211; if you have a link where target=&#8221;foo&#8221;, subsequent links from the same origin targeted at &#8220;foo&#8221; will open in the same window or tab or what have you. So we&#8217;re checking to see if we&#8217;ve opened something with the name inside aWindowTarget already.<\/p>\n<p>So now we&#8217;re in nsDocShell::FindItemWithName, and I see this:<\/p>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else if (name.LowerCaseEqualsLiteral(\"_blank\"))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Just return null.\u00a0 Caller must handle creating a new window with\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ a blank name himself.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return NS_OK;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/pre>\n<p>Ah hah, so target=&#8221;_blank&#8221;, as we already knew, is special-cased &#8211; and this is where it happens. There&#8217;s no existing docshell for _blank because we know we&#8217;re going to be opening a new window (or tab if the user has preffed it that way). So we don&#8217;t return a pre-existing docshell.<\/p>\n<p>So we&#8217;re back in nsDocShell::InternalLoad.<\/p>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rv = FindItemWithName(aWindowTarget, nullptr, this,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(targetItem));\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NS_ENSURE_SUCCESS(rv, rv);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 targetDocShell = do_QueryInterface(targetItem);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ If the targetDocShell doesn't exist, then this is a new docShell\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ and we should consider this a TYPE_DOCUMENT load\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 isNewDocShell = !targetDocShell;<\/pre>\n<p>Ok, so now targetItem is nullptr, targetDocShell is also nullptr, and so isNewDocShell is true.<\/p>\n<p>There seems to be more policy checking going on in InternalLoad after this&#8230; but eventually, I see this:<\/p>\n<pre>\u00a0\u00a0 if (aWindowTarget &amp;&amp; *aWindowTarget) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ We've already done our owner-inheriting.\u00a0 Mask out that bit, so we\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ don't try inheriting an owner from the target window if we came up\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ with a null owner above.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 aFlags = aFlags &amp; ~INTERNAL_LOAD_FLAGS_INHERIT_OWNER;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 bool isNewWindow = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (!targetDocShell) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ If the docshell's document is sandboxed, only open a new window\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ (i.e. if allow-popups is specified)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIDocument* doc = mContentViewer-&gt;GetDocument();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 uint32_t sandboxFlags = 0;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (doc) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sandboxFlags = doc-&gt;GetSandboxFlags();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (sandboxFlags &amp; SANDBOXED_AUXILIARY_NAVIGATION) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return NS_ERROR_DOM_INVALID_ACCESS_ERR;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsPIDOMWindow&gt; win =\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 do_GetInterface(GetAsSupports(this));\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsDependentString name(aWindowTarget);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIDOMWindow&gt; newWin;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsAutoCString spec;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (aURI)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 aURI-&gt;GetSpec(spec);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rv = win-&gt;OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 name,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ window name\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 EmptyString(), \/\/ Features\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(newWin));<\/pre>\n<p>So we check again to see if we&#8217;re targeted at something, and check if we&#8217;ve found a target docshell for it. We hadn&#8217;t, so we do some security checks, and then &#8230; what the hell is nsPIDOMWindow? I&#8217;m used to things being called nsIBlahBlah, but now nsPIBlahBlah&#8230; what does the P mean?<\/p>\n<p>It took some asking around, but I eventually found out that the P is supposed to be for Private &#8211; as in, this is a private XPIDL interface, and non-core embedders should stay away from it.<\/p>\n<p>Ok, and we also see do_GetInterface. This is <em>not the same<\/em> as <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Tech\/XPCOM\/Reference\/Interface\/nsISupports#QueryInterface%28%29\">QueryInterface<\/a>, believe it or not. The difference is subtle, but basically it&#8217;s this: QueryInterface says &#8220;you implement X, but I think you also implement Y. If you do, please return a pointer to yourself that makes you seem like a Y.&#8221; GetInterface is different &#8211; GetInterface says &#8220;I know you know about something that implements Y. It might be you, or more likely, it&#8217;s something you&#8217;re holding a reference to. Can I get a reference to that please?&#8221;. And if successful, it returns it. <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Tech\/XPCOM\/Reference\/Interface\/nsIInterfaceRequestor#getInterface%28%29\">Here&#8217;s more documentation about GetInterface.<\/a><\/p>\n<p>It&#8217;s a subtle but important difference.<\/p>\n<p>So this docshell knows about a window, and we&#8217;ve now got a handle on that window using the private interface nsPIDOMWindow. Neat.<\/p>\n<p>So eventually, we call OpenNoNavigate on that nsPIDOMWindow. That method is pretty much like nsIDOMWindow::Open, except that OpenNoNavigate doesn&#8217;t send the window anywhere &#8211; it just returns it so that the caller can send it to a URI.<\/p>\n<p>Through the magic of do_GetInterface, nsDocShell::GetInterface, EnsureScriptEnvironment, and NS_NewScriptGlobalObject, I know that the nsPIDOMWindow is being implemented by nsGlobalWindow, and that&#8217;s where I should go to to find the OpenNoNavigate implementation.<\/p>\n<p>So off we go!<\/p>\n<p>nsGlobalWindow::OpenNoNavigate just seems to forward the call, after some argument setting, to nsGlobalWindow::OpenInternal, like this:<\/p>\n<pre>\u00a0 return OpenInternal(aUrl, aName, aOptions,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 false,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aDialog\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 false,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aContentModal\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 true,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aCalledNoScript\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 false,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aDoJSFixups\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 false,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aNavigate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nullptr, nullptr,\u00a0 \/\/ No args\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GetPrincipal(),\u00a0\u00a0\u00a0 \/\/ aCalleePrincipal\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nullptr,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ aJSCallerContext\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _retval);<\/pre>\n<p>Having a glance around at the rest of the nsGlobalWindow::Open[foo] methods, it looks like they all call into OpenInternal. It&#8217;s the big-mamma opening method.<\/p>\n<p>This method does a few things, including making sure that we&#8217;re not being abused by web content that&#8217;s trying to spam the user with popups.<\/p>\n<p>Eventually, we get to this:<\/p>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0 rv = pwwatch-&gt;OpenWindow2(this, url.get(), name_ptr, options_ptr,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* aCalledFromScript = *\/ false,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 aDialog, aNavigate, aExtraArgument,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(domReturn));<\/pre>\n<p>and return the domReturn pointer back after a few more checks to our caller. Remember that the caller is going to take this new window, and navigate it to some URI.<\/p>\n<p>Ok, so, pwwatch. What is that? Well, that appears to be a private interface to nsWindowWatcher, which gives us access to the OpenWindow2 method.<\/p>\n<p>After prepping some arguments, much like nsGlobalWindow::OpenNoNavigate did, we forward the call over to nsWindowWatcher::OpenWindowInternal.<\/p>\n<p>And now we&#8217;re almost done &#8211; we&#8217;re almost at the point where we&#8217;re actually going to open a window!<\/p>\n<p>Some key things need to happen though. First, we do this:<\/p>\n<pre>nsCOMPtr&lt;nsIDocShellTreeOwner&gt;\u00a0 parentTreeOwner;\u00a0 \/\/ from the parent window, if any\r\n...\r\nGetWindowTreeOwner(aParent, getter_AddRefs(parentTreeOwner));<\/pre>\n<p>So what that does is it tries to get the docshell owner of the docshell that&#8217;s attempting to open the window (and that&#8217;d be the docshell that we clicked the link in).<\/p>\n<p>After a few more things, we check to see if there&#8217;s an existing window with that target name which we can re-use:<\/p>\n<pre>\u00a0 \/\/ try to find an extant window with the given name\r\n\u00a0 nsCOMPtr&lt;nsIDOMWindow&gt; foundWindow = SafeGetWindowByName(name, aParent);\r\n\u00a0 GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem));<\/pre>\n<p>And if so, we set it to newDocShellItem.<\/p>\n<p>After some more security stuff, we check to see if newDocShellItem exists. Because name is nullptr (since we had target=&#8221;_blank&#8221;, and nsDocShell::FindItemWithName returned nullptr), newDocShellItem is null.<\/p>\n<p>Because it doesn&#8217;t exist, we know we&#8217;re opening a brand new window!<\/p>\n<p>More security things seem to happen, and then we get to the part that I&#8217;m starting to focus on:<\/p>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIWindowProvider&gt; provider = do_GetInterface(parentTreeOwner);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (provider) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NS_ASSERTION(aParent, \"We've _got_ to have a parent here!\");\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIDOMWindow&gt; newWindow;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rv = provider-&gt;ProvideWindow(aParent, chromeFlags, aCalledFromJS,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sizeSpec.PositionSpecified(),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sizeSpec.SizeSpecified(),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 uriToLoad, name, features, &amp;windowIsNew,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(newWindow));<\/pre>\n<p>We ask the parentTreeOwner to get us something that it knows about that implements nsIWindowProvider. In the Electrolysis \/ content process case, that&#8217;d be TabChild. In the normal, non-Electrolysis case, that&#8217;s nsContentTreeOwner.<\/p>\n<p>The nsIWindowProvider is the thing that we&#8217;ll use to get a new window from! So we call ProvideWindow on it, to give us a pointer to new nsIDOMWindow window, assigned to newWindow.<\/p>\n<p>Here&#8217;s TabChild::ProvideWindow:<\/p>\n<pre>NS_IMETHODIMP\r\nTabChild::ProvideWindow(nsIDOMWindow* aParent, uint32_t aChromeFlags,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 bool aCalledFromJS,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 bool aPositionSpecified, bool aSizeSpecified,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIURI* aURI, const nsAString&amp; aName,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const nsACString&amp; aFeatures, bool* aWindowIsNew,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIDOMWindow** aReturn)\r\n{\r\n\u00a0\u00a0\u00a0 *aReturn = nullptr;\r\n\r\n\u00a0\u00a0\u00a0 \/\/ If aParent is inside an &lt;iframe mozbrowser&gt; or &lt;iframe mozapp&gt; and this\r\n\u00a0\u00a0\u00a0 \/\/ isn't a request to open a modal-type window, we're going to create a new\r\n\u00a0\u00a0\u00a0 \/\/ &lt;iframe mozbrowser\/mozapp&gt; and return its window here.\r\n\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIDocShell&gt; docshell = do_GetInterface(aParent);\r\n\u00a0\u00a0\u00a0 if (docshell &amp;&amp; docshell-&gt;GetIsInBrowserOrApp() &amp;&amp;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 !(aChromeFlags &amp; (nsIWebBrowserChrome::CHROME_MODAL |\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Note that BrowserFrameProvideWindow may return NS_ERROR_ABORT if the\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ open window call was canceled.\u00a0 It's important that we pass this error\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ code back to our caller.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return BrowserFrameProvideWindow(aParent, aURI, aName, aFeatures,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 aWindowIsNew, aReturn);\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 \/\/ Otherwise, create a new top-level window.\r\n\u00a0\u00a0\u00a0 PBrowserChild* newChild;\r\n\u00a0\u00a0\u00a0 if (!CallCreateWindow(&amp;newChild)) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return NS_ERROR_NOT_AVAILABLE;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 *aWindowIsNew = true;\r\n\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIDOMWindow&gt; win =\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 do_GetInterface(static_cast&lt;TabChild*&gt;(newChild)-&gt;WebNavigation());\r\n\u00a0\u00a0\u00a0 win.forget(aReturn);\r\n\u00a0\u00a0\u00a0 return NS_OK;\r\n}<\/pre>\n<p>The docshell-&gt;GetIsInBrowserOrApp() is basically asking &#8220;are we b2g?&#8221;, to which the answer is &#8220;no&#8221;, so we skip that block, and go right for CallCreateWindow.<\/p>\n<p>CallCreateWindow is using the IPC library to communicate with TabParent in the UI process, which has a corresponding function called AnswerCreateWindow. Here it is:<\/p>\n<pre>bool\r\nTabParent::AnswerCreateWindow(PBrowserParent** retval)\r\n{\r\n\u00a0\u00a0\u00a0 if (!mBrowserDOMWindow) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 \/\/ Only non-app, non-browser processes may call CreateWindow.\r\n\u00a0\u00a0\u00a0 if (IsBrowserOrApp()) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 \/\/ Get a new rendering area from the browserDOMWin.\u00a0 We don't want\r\n\u00a0\u00a0\u00a0 \/\/ to be starting any loads here, so get it with a null URI.\r\n\u00a0\u00a0\u00a0 nsCOMPtr&lt;nsIFrameLoaderOwner&gt; frameLoaderOwner;\r\n\u00a0\u00a0\u00a0 mBrowserDOMWindow-&gt;OpenURIInFrame(nullptr, nullptr,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIBrowserDOMWindow::OPEN_NEWTAB,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsIBrowserDOMWindow::OPEN_NEW,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getter_AddRefs(frameLoaderOwner));\r\n\u00a0\u00a0\u00a0 if (!frameLoaderOwner) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 nsRefPtr&lt;nsFrameLoader&gt; frameLoader = frameLoaderOwner-&gt;GetFrameLoader();\r\n\u00a0\u00a0\u00a0 if (!frameLoader) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 *retval = frameLoader-&gt;GetRemoteBrowser();\r\n\u00a0\u00a0\u00a0 return true;\r\n}<\/pre>\n<p>So after some checks, we call mBrowserDOMWindow&#8217;s OpenURIInFrame, with (among other things), nsIBrowserDOMWindow::OPEN_NEWTAB. So that&#8217;s why we&#8217;ve got a new tab opening instead of a new window.<\/p>\n<p>mBrowserDOMWindow is a reference to this thing implemented in browser.js:<\/p>\n<pre>function nsBrowserAccess() { }\r\n\r\nnsBrowserAccess.prototype = {\r\n\u00a0 QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),\r\n\r\n\u00a0 _openURIInNewTab: function(aURI, aOpener, aIsExternal) {\r\n\u00a0\u00a0\u00a0 let win, needToFocusWin;\r\n\r\n\u00a0\u00a0\u00a0 \/\/ try the current window.\u00a0 if we're in a popup, fall back on the most recent browser window\r\n\u00a0\u00a0\u00a0 if (window.toolbar.visible)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 win = window;\r\n\u00a0\u00a0\u00a0 else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 needToFocusWin = true;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 if (!win) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ we couldn't find a suitable window, a new one needs to be opened.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return null;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 if (aIsExternal &amp;&amp; (!aURI || aURI.spec == \"about:blank\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 win.BrowserOpenTab(); \/\/ this also focuses the location bar\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 win.focus();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return win.gBrowser.selectedBrowser;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 let loadInBackground = gPrefService.getBoolPref(\"browser.tabs.loadDivertedInBackground\");\r\n\u00a0\u00a0\u00a0 let referrer = aOpener ? makeURI(aOpener.location.href) : null;\r\n\r\n\u00a0\u00a0\u00a0 let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : \"about:blank\", {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 referrerURI: referrer,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 fromExternal: aIsExternal,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inBackground: loadInBackground});\r\n\u00a0\u00a0\u00a0 let browser = win.gBrowser.getBrowserForTab(tab);\r\n\r\n\u00a0\u00a0\u00a0 if (needToFocusWin || (!loadInBackground &amp;&amp; aIsExternal))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 win.focus();\r\n\r\n\u00a0\u00a0\u00a0 return browser;\r\n\u00a0 },\r\n\r\n\u00a0 openURI: function (aURI, aOpener, aWhere, aContext) {\r\n    ... (removed for brevity)\r\n\u00a0 },\r\n\r\n\u00a0 openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {\r\n\u00a0\u00a0\u00a0 if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 dump(\"Error: openURIInFrame can only open in new tabs\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return null;\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);\r\n\u00a0\u00a0\u00a0 let browser = this._openURIInNewTab(aURI, aOpener, isExternal);\r\n\u00a0\u00a0\u00a0 if (browser)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return browser.QueryInterface(Ci.nsIFrameLoaderOwner);\r\n\r\n\u00a0\u00a0\u00a0 return null;\r\n\u00a0 },\r\n\r\n\u00a0 isTabContentWindow: function (aWindow) {\r\n\u00a0\u00a0\u00a0 return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);\r\n\u00a0 },\r\n\r\n\u00a0 get contentWindow() {\r\n\u00a0\u00a0\u00a0 return gBrowser.contentWindow;\r\n\u00a0 }\r\n}<\/pre>\n<p>So nsBrowserAccess&#8217;s openURIInFrame <strong>only<\/strong> supports opening things in new tabs, and then it just calls _openURIInNewTab on itself, which does the job of returning the tab&#8217;s remote browser after the tab is opened.<\/p>\n<p>I might follow this up with a post about how nsContentTreeOwner opens a window in the non-Electrolysis case, and how we might abstract some of that out for re-use here. We&#8217;ll see.<\/p>\n<p>And that&#8217;s about it. Hopefully this is useful to future spelunkers.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey. I&#8217;ve started hacking on Electrolysis bugs. I&#8217;m normally a front-end engineer working on Firefox desktop, but I&#8217;ve been temporarily loaned out to help get Electrolysis ready to be enabled by default on Nightly. I&#8217;m working on bug 989501. Basically, when you click on a link that targets &#8220;_blank&#8221; or uses window.open, we open a [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[874,861],"tags":[1074,1072,1069,125,35,1042,1073],"class_list":["post-2480","post","type-post","status-publish","format-standard","hentry","category-firefox-mozilla-2","category-mozilla-2","tag-do_getinterface","tag-docshell","tag-e10s","tag-firefox","tag-mozilla","tag-tabs","tag-xpcom"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/prmTy-E0","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/posts\/2480","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/comments?post=2480"}],"version-history":[{"count":4,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/posts\/2480\/revisions"}],"predecessor-version":[{"id":2485,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/posts\/2480\/revisions\/2485"}],"wp:attachment":[{"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/media?parent=2480"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/categories?post=2480"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mikeconley.ca\/blog\/wp-json\/wp\/v2\/tags?post=2480"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}