Tear off tab with SDK

Soooooooooooooooooooooo I introspected gBrowser and discovered replaceTabWithWindow() which is precisely the “tear off tab” API. It moves a given XUL tab (i.e. something you can get like viewFor(tab)) off its owning window and onto a new one, preserving state—it even preserves typed in passwords, which usually get erased as soon as a page is reloaded, so it must be legitimately moving the existing tab object.

Introspection is great especially since a lot of the low level API is apparently written in pure javascript, so we can even read its source:

function replaceTabWithWindow(aTab, aOptions) {

          
            if (this.tabs.length == 1)
              return null;

            var options = "chrome,dialog=no,all";
            for (var name in aOptions)
              options += "," + name + "=" + aOptions[name];

            // tell a new window to take the "dropped" tab
            return window.openDialog(getBrowserURL(), "_blank", options, aTab);
          
        
}

Here’s an example. Run jpm init, drop this into the index.js, do jpm run and make some multi-tab windows, do stuff to them (like typing in passwords or other form fields) and then click this button:

const { getMostRecentBrowserWindow } = require("sdk/window/utils");                                   
const { ActionButton } = require("sdk/ui/button/action");

var button = ActionButton({
  id: "tearofftabtab-button",
  label: "Tear Off Current Tab",
  icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACUElEQVQ4jaWTTWtTURCGjzc33CCpbVKN4kexC9EUY1Hov+iqPyDrbgtuCrViKUERqsWVguBGQaW4UiKiaEVxoShFGgnuBMUqNW3zce49Z+ZxUWtwoRR8YXbzPswM7xj+JgVEiXGsYVknxgII4Ltt5p8AB8RArOAUVQfqQJNtAFA8QgvF6i9PR1Dt0KbVBTjncM4hIni/OZv3HsRB+wvefiP2LcQnJIkQe49FEJFNQLPZZHh4mEwmQyqVoqenh3K5TGvlK1dOlageH+HG4DFar1/S0A6Lr99xdN8QxWKRXC6HGR0dJZvNMjk5Sb1ep1gskk6nuTo/D+/ec7dvkBdhP9cKeX7UXxEZQ2/YRxRFLC8vY+bm5qhUKnjvsdYyPj5OFEWcnTnHujiS5TfcPDbAw50h9w7u5f7UadLZFLVaDRHBiGzuY61lbGyMXC5HoVBgrbGGWAW/TvvxHR7s7udFKs/1oyfZ+PSRTqeDqm7eoFqtEoYhmUyG2dlZVJU4iREfI/WP3Nt9iMUdu7jdf5Anly5i0oaVlRWazSZmYWGBIAiIoohyucz09DQTExPMnJli9dlT5vcM8Kh3gFsHDuNqb9mb7yXMRBhjWFpawpRKJVKpFMYYgiAgDEOCIOD81BkunBjh8pEhKqUhGkvP6bQ/U//wgUP5/YRhSDabxbTbbVQV5xyq2q0kgR8NdOM7JKuo/Y5qggqIdPvMlnkrQCKCquJFsOrxeHAJxA48eFU6Xv4EqOpv41YqnQirqliv4MEmQtN7RBSs7wL+/gvb038DfgJnyUabbHzUbQAAAABJRU5ErkJggg==",
  onClick: function() {
    getMostRecentBrowserWindow().gBrowser.replaceTabWithWindow(getMostRecentBrowserWindow().gBrowser.selectedTab);
  }
});

There’s a comment in the swapBrowsersAndCloseOther() function that @noitidart used that mentions replaceTabWithWindow(), despite not being called by it. Maybe window.openDialog() in some roundabout way does end up relying on swapBrowsersAndCloseOther(), in which case @DUzun’s and @noitidart’s codes are valiant efforts at reimplementing replaceTabWithWindow().

The big advantage of this for me over @DUzun’s is that whatever black magic is working in the low-low levels doesn’t spawn a hidden temporary tab, so it doesn’t trigger tabs.on('open'), so I don’t get a recursive explosion.

Here a listing of all the methods available on gBrowser. Are these documented anywhere? They don’t seem to be in the Jetpack docs. Maybe it will help someone else:

addEventListener 
addProgressListener 
addTab 
addTabsProgressListener 
_adjustFocusAfterTabSwitch 
appendChild 
_appendStatusPanel 
attachFormFill 
_beginRemoveTab 
blur 
_blurTab 
_callProgressListeners 
click 
cloneNode 
closest 
compareDocumentPosition 
contains 
_createBrowser 
_createPreloadBrowser 
createShadowRoot 
createTooltip 
detachFormFill 
dispatchEvent 
doCommand 
duplicateTab 
_endRemoveTab 
enterTabbedMode 
focus 
_generateUniquePanelID 
getAnimations 
getAttribute 
getAttributeNode 
getAttributeNodeNS 
getAttributeNS 
getBoundingClientRect 
getBoundMutationObservers 
getBoxQuads 
getBrowserAtIndex 
getBrowserContainer 
getBrowserForContentWindow 
getBrowserForDocument 
getBrowserForOuterWindowID 
getBrowserForTab 
getBrowserIndexForDocument 
getClientRects 
getDestinationInsertionPoints 
getElementsByAttribute 
getElementsByAttributeNS 
getElementsByClassName 
getElementsByTagName 
getElementsByTagNameNS 
getEventHandler 
getFindBar 
getIcon 
getNotificationBox 
_getPreloadedBrowser 
getSidebarContainer 
getStatusPanel 
getStripVisibility 
_getSwitcher 
_getTabForBrowser 
getTabForBrowser 
_getTabForContentWindow 
getTabModalPromptBox 
getTabsToTheEndFrom 
getUserData 
getWindowTitleForBrowser 
goBack 
goForward 
goHome 
gotoIndex 
handleEvent 
_handleKeyDownEvent 
_handleKeyPressEventMac 
hasAttribute 
hasAttributeNS 
hasAttributes 
hasChildNodes 
hideTab 
insertAdjacentHTML 
insertBefore 
isDefaultNamespace 
isEqualNode 
isFailedIcon 
isFindBarInitialized 
_isPreloadingEnabled 
loadOneTab 
loadTabs 
loadURI 
loadURIWithFlags 
lookupNamespaceURI 
lookupPrefix 
matches 
moveTabBackward 
moveTabForward 
moveTabOver 
moveTabTo 
moveTabToEnd 
moveTabToStart 
mozMatchesSelector 
mozRequestFullScreen 
mozRequestPointerLock 
mozScrollSnap 
mTabProgressListener 
normalize 
observe 
openNonRemoteWindow 
pinTab 
previewTab 
querySelector 
querySelectorAll 
receiveMessage 
releaseCapture 
reload 
reloadAllTabs 
reloadTab 
reloadWithFlags 
remove 
removeAllTabsBut 
removeAttribute 
removeAttributeNode 
removeAttributeNS 
removeChild 
removeCurrentTab 
removeEventListener 
removeProgressListener 
removeTab 
removeTabsProgressListener 
removeTabsToTheEndFrom 
replaceChild 
replaceTabWithWindow 
scroll 
scrollBy 
scrollByNoFlush 
scrollIntoView 
scrollTo 
selectTabAtIndex 
setAttribute 
setAttributeNode 
setAttributeNodeNS 
setAttributeNS 
setCapture 
_setCloseKeyState 
setEventHandler 
setIcon 
setIsPrerendered 
setStripVisibilityTo 
setTabTitle 
setTabTitleLoading 
setUserData 
shouldLoadFavIcon 
showOnlyTheseTabs 
showTab 
stop 
_swapBrowserDocShells 
swapBrowsersAndCloseOther 
swapFrameLoaders 
swapNewTabWithBrowser 
_swapRegisteredOpenURIs 
_tabAttrModified 
unpinTab 
updateBrowserRemoteness 
updateBrowserRemotenessByURL 
updateCurrentBrowser 
updateTitlebar 
updateWindowResizers 
useDefaultIcon 
warnAboutClosingTabs 
webkitMatchesSelector