diff --git a/content/treestyletab/bookmarksOverlay.xul b/content/treestyletab/bookmarksOverlay.xul index fc4a82bd..bec17360 100644 --- a/content/treestyletab/bookmarksOverlay.xul +++ b/content/treestyletab/bookmarksOverlay.xul @@ -1,6 +1,6 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/treestyletab/hide-embed.css b/content/treestyletab/hide-embed.css index 13cb6c2a..6dce1c77 100644 --- a/content/treestyletab/hide-embed.css +++ b/content/treestyletab/hide-embed.css @@ -1,5 +1,5 @@ -@namespace url("http://www.w3.org/1999/xhtml"); - -body > embed[name="plugin"] { - visibility: hidden !important; -} +@namespace url("http://www.w3.org/1999/xhtml"); + +body > embed[name="plugin"] { + visibility: hidden !important; +} diff --git a/content/treestyletab/license.txt b/content/treestyletab/license.txt index eeda95cd..0a245b03 100644 --- a/content/treestyletab/license.txt +++ b/content/treestyletab/license.txt @@ -1,35 +1,35 @@ -***** BEGIN LICENSE BLOCK ***** -Version: MPL 1.1/GPL 2.0/LGPL 2.1 - -The contents of these files are subject to the Mozilla Public License Version -1.1 (the "License"); you may not use these files except in compliance with -the License. You may obtain a copy of the License at -http://www.mozilla.org/MPL/ - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -for the specific language governing rights and limitations under the -License. - -The Original Code is the Tree Style Tab. - -The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. -Portions created by the Initial Developer are Copyright (C) 2007-2014 -the Initial Developer. All Rights Reserved. - -Contributor(s): YUKI "Piro" Hiroshi - Alice0775 (fixes some compatibility problems) - -Alternatively, the contents of these files may be used under the terms of -either the GNU General Public License Version 2 or later (the "GPL"), or -the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -in which case the provisions of the GPL or the LGPL are applicable instead -of those above. If you wish to allow use of your version of these files only -under the terms of either the GPL or the LGPL, and not to allow others to -use your version of these files under the terms of the MPL, indicate your -decision by deleting the provisions above and replace them with the notice -and other provisions required by the GPL or the LGPL. If you do not delete -the provisions above, a recipient may use your version of these files under -the terms of any one of the MPL, the GPL or the LGPL. - -***** END LICENSE BLOCK ***** +***** BEGIN LICENSE BLOCK ***** +Version: MPL 1.1/GPL 2.0/LGPL 2.1 + +The contents of these files are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use these files except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +The Original Code is the Tree Style Tab. + +The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. +Portions created by the Initial Developer are Copyright (C) 2007-2014 +the Initial Developer. All Rights Reserved. + +Contributor(s): YUKI "Piro" Hiroshi + Alice0775 (fixes some compatibility problems) + +Alternatively, the contents of these files may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of these files only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of these files under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of these files under +the terms of any one of the MPL, the GPL or the LGPL. + +***** END LICENSE BLOCK ***** diff --git a/content/treestyletab/multipletabConfigOverlay.xul b/content/treestyletab/multipletabConfigOverlay.xul index d50f2fe4..a2ac3a0c 100644 --- a/content/treestyletab/multipletabConfigOverlay.xul +++ b/content/treestyletab/multipletabConfigOverlay.xul @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/content/treestyletab/treestyletab-tmp.css b/content/treestyletab/treestyletab-tmp.css index a03f5037..8f6b96af 100644 --- a/content/treestyletab/treestyletab-tmp.css +++ b/content/treestyletab/treestyletab-tmp.css @@ -1,33 +1,33 @@ -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); - -/* hacks for Tab Mix Plus */ - -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] #vide-bar, -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] #tabs-scroll-up-down-box, -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] #alltabs-place-holder, -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] .tabs-scroll { - visibility: collapse !important; -} - -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] tabs[flowing] > * .tabs-frame { - overflow-x: hidden !important; - overflow-y: auto !important; -} - -:root[treestyletab-enable-compatibility-tmp="true"] - tabbrowser[treestyletab-mode="vertical"] tabs[flowing] > * .tabs-frame { - box-flex: 1 !important; - -moz-box-flex: 1 !important; -} - -.tabbrowser-tab[pinned] > .tab-image-left, -.tabbrowser-tab[pinned] > .tab-drag-indicator-left, -.tabbrowser-tab[pinned] > .tab-drag-indicator-right, -.tabbrowser-tab[pinned] > .tab-image-right { - display: none; -} +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); + +/* hacks for Tab Mix Plus */ + +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] #vide-bar, +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] #tabs-scroll-up-down-box, +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] #alltabs-place-holder, +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] .tabs-scroll { + visibility: collapse !important; +} + +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] tabs[flowing] > * .tabs-frame { + overflow-x: hidden !important; + overflow-y: auto !important; +} + +:root[treestyletab-enable-compatibility-tmp="true"] + tabbrowser[treestyletab-mode="vertical"] tabs[flowing] > * .tabs-frame { + box-flex: 1 !important; + -moz-box-flex: 1 !important; +} + +.tabbrowser-tab[pinned] > .tab-image-left, +.tabbrowser-tab[pinned] > .tab-drag-indicator-left, +.tabbrowser-tab[pinned] > .tab-drag-indicator-right, +.tabbrowser-tab[pinned] > .tab-image-right { + display: none; +} diff --git a/content/treestyletab/treestyletab.css b/content/treestyletab/treestyletab.css index 5e31618a..dc353f0c 100644 --- a/content/treestyletab/treestyletab.css +++ b/content/treestyletab/treestyletab.css @@ -1,691 +1,691 @@ -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -@namespace html url("http://www.w3.org/1999/xhtml"); - - -/* twisty in tabs */ -.treestyletab-twisty, -.tabbrowser-tabs:not([treestyletab-allow-subtree-collapse="true"]) - .treestyletab-twisty { - visibility: hidden; -} -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-allow-subtree-collapse="true"]:not([treestyletab-twisty-style="none"]) - .tabbrowser-tab[treestyletab-children][treestyletab-allow-subtree-collapse="true"] - .treestyletab-twisty, -.tabbrowser-tabs[treestyletab-mode="horizontal"][treestyletab-allow-subtree-collapse="true"]:not([treestyletab-twisty-style="none"]) - .tabbrowser-tab[treestyletab-children][treestyletab-allow-subtree-collapse="true"] - .treestyletab-twisty { - visibility: visible; -} -.tabbrowser-tabs[treestyletab-twisty-style="none"] - .treestyletab-twisty, -.tabbrowser-tabs[treestyletab-mode="horizontal"] - .treestyletab-twisty, -.tabbrowser-tab[pinned="true"] - .treestyletab-twisty { - visibility: collapse; -} - -/* collapsed children counter */ -.treestyletab-counter-container { - display: none; -} -.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"] - .tabbrowser-tab[treestyletab-children][treestyletab-subtree-collapsed="true"][treestyletab-allow-subtree-collapse="true"] - .treestyletab-counter-container { - display: -moz-box; -} - -/* collapse/expand tree of tabs */ -.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"] - .tabbrowser-tab[treestyletab-collapsed-done="true"] { - visibility: collapse; -} - -/* collapse/expand tree of tabs, horizontal stacked tabs */ -.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] - .tabbrowser-tab { - position: relative; -} -.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] - .tabbrowser-tab[treestyletab-collapsed="true"][fadein], -/** - * Tab Mix Plus dynamically inserts a CSS rule like - * "#tabbrowser-tabs > .tabbrowser-tab { ... }" and it unexpectedly - * expands width of "collapsed" tabs. So, to apply our rule prior than - * TMP's one, I put the "#tabbrowser-tabs" version also. - */ -#tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] - .tabbrowser-tab[treestyletab-collapsed="true"][fadein] { - visibility: visible; - max-width: 20px; - min-width: 20px; - width: 20px; - box-flex: 0; - -moz-box-flex: 0; - mask: url("res/tabEffects.svg#fadein-mask"); -} -.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] - .tabbrowser-tab[treestyletab-collapsed-done="true"] .tab-content > * { - visibility: collapse; -} - -/* fixed tab bar */ -.tabbrowser-strip[treestyletab-tabbar-fixed="true"]+splitter { - visibility: collapse; -} - - -/* tab bar in the DOM-fullscreen mode */ - -.tabbrowser-tabs[treestyletab-dom-fullscreen-activated="true"], -.tabbrowser-strip[treestyletab-dom-fullscreen-activated="true"], -.tabbrowser-strip[treestyletab-dom-fullscreen-activated="true"]+splitter, -.treestyletab-tabbar-toolbar[treestyletab-dom-fullscreen-activated="true"], -.treestyletab-tabbar-toolbar[treestyletab-dom-fullscreen-activated="true"] > *, -tabbrowser[treestyletab-dom-fullscreen-activated-mode="true"][tabcontainer] - .tabbrowser-strip.treestyletab-tabbar-placeholder, -tabbrowser[treestyletab-dom-fullscreen-activated-mode="true"][tabcontainer] - .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter { - visibility: collapse; -} - - -/* auto hide tab bar */ - -.tabbrowser-tabs[treestyletab-tabbar-autohide="hidden"], -.tabbrowser-strip[treestyletab-tabbar-autohide="hidden"], -.tabbrowser-strip[treestyletab-tabbar-autohide="hidden"]+splitter, -.tabbrowser-strip[collapsed="true"] + splitter:not([state="collapsed"]), -.treestyletab-tabbar-toggler, -.treestyletab-tabbar-toolbar[treestyletab-tabbar-autohide="hidden"], -.treestyletab-tabbar-toolbar[treestyletab-tabbar-autohide="hidden"] > *, -.treestyletab-tabbar-toolbar .treestyletab-splitter, -#treestyletab-tabbar-resizer-box, -tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] - .tabbrowser-strip.treestyletab-tabbar-placeholder, -tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] - .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter { - visibility: collapse; -} - -tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] - .treestyletab-tabbar-toggler, -tabbrowser[treestyletab-tabbar-autohide-mode="1"][treestyletab-tabbar-autohide="hidden"] - .treestyletab-tabbar-toggler, -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide][treestyletab-tabbar-autohide-state="expanded"] - #treestyletab-tabbar-resizer-box, -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide][treestyletab-tabbar-autohide-state="expanded"]:not([treestyletab-tabbar-fixed="true"]) - .treestyletab-splitter { - visibility: visible; -} - -/* put resizer under tabs, and raise up tabs */ -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] - > *, -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] - > #tabbrowser-tabs, -.treestyletab-toolbar-inner-box[treestyletab-tabbar-autohide]:not([treestyletab-tabbar-fixed="true"]) - > *, -.treestyletab-toolbar-inner-box[treestyletab-tabbar-autohide]:not([treestyletab-tabbar-fixed="true"]) - > #tabbrowser-tabs { - position: relative; - z-index: 2; -} -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] - #treestyletab-tabbar-resizer-box { - position: absolute; - bottom: 0; - left: 0; - right: 0; - top: 0; - z-index: 1; - line-height: 0 !important; /* to avoid an useless space above the splitter */ - font-size: 0 !important; /* to avoid an useless space above the splitter */ -} - -.tabbrowser-tabs[treestyletab-tabbar-autohide] - .tabs-stack - > *:first-child - > :not(html|canvas), -.tabbrowser-tabs[treestyletab-tabbar-resizing] - .tabs-stack - > *:first-child - > html|canvas { - display: none !important; -} - - -#treestyletab-autohide-content-area-screen { - appearance: none; - -moz-appearance: none; - /** - * Don't make this panel completely transparent, because - * mousemove event never fire on 100% transparent panel. - * The color should be a pale white, instead of a pale black, - * because many websites use white background. Black screen - * is too glaring for white background websites. - */ - background: rgba(255, 255, 255, 0.01); - line-height: 0; - margin: 0; - padding: 0; - -moz-border-bottom-colors: none; - -moz-border-left-colors: none; - -moz-border-right-colors: none; - -moz-border-top-colors: none; -} - - -/* vertical tab bar */ - -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-arrowscrollbox > scrollbox { - overflow-x: hidden !important; - overflow-y: auto !important; -} - -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-arrowscrollbox - .scrollbox-innerbox { - box-flex: 1 !important; - -moz-box-flex: 1 !important; - box-pack: start !important; - -moz-box-pack: start !important; -} - -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-tab, -toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] - > toolbarbutton, -toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] - > .treestyletab-toolbar-inner-box > toolbarbutton, -toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] - > toolbaritem, -toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] - > .treestyletab-toolbar-inner-box > toolbaritem { - box-flex: 0 !important; - -moz-box-flex: 0 !important; -} - -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] > toolbarbutton, -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] > toolbarpaletteitem > toolbarbutton { - max-width: none !important; -} - -/* workaround for https://github.com/piroor/treestyletab/issues/100 */ -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] .textbox-input-box { - box-align: stretch; - -moz-box-align: stretch; -} - - -/* leftside vertical tab bar */ - -tabs.tabbrowser-tabs[treestyletab-tabbar-position="left"][treestyletab-invert-scrollbar="true"] - .tabbrowser-arrowscrollbox - > scrollbox { - direction: rtl; -} -tabs.tabbrowser-tabs[treestyletab-tabbar-position="left"][treestyletab-invert-scrollbar="true"] - .tabbrowser-arrowscrollbox - > scrollbox - > .scrollbox-innerbox { - direction: ltr; -} - - -/* hide horizontal tab bar UI for vertical tab bar */ -.tabbrowser-tabs[treestyletab-mode="vertical"] .tabbrowser-arrowscrollbox > .scrollbutton-up, -.tabbrowser-tabs[treestyletab-mode="vertical"] .tabbrowser-arrowscrollbox > .scrollbutton-down:not([treestyletab-notifybgtab-phase]), -.tabbrowser-tabs[treestyletab-mode="vertical"] .tabs-closebutton-box, -.tabbrowser-tabs[treestyletab-mode="vertical"] .tab-drop-indicator-bar { - visibility: collapse !important; -} - -/** - * Firefox 4.0 finishes opening and closing processes of tabs by its - * "transitionend" events about max-width property. I think this is very - * dirty and specific implementation but it won't fixed. - * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=585417#c3 ) - * So we have to define max-width for both states [fadein] (means "completely - * opened") and :not([fadein]) (means "in opening/removing process". - */ -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-tab:not([pinned]) { - max-width: 65000px !important; -} -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] - .tabbrowser-tab:not([pinned]):not([fadein]) { - opacity: 0.5 !important; -} -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-tab:not([pinned])[fadein] { - max-width: 65250px !important; - min-width: 1px !important; -} -.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-animation-enabled="true"] - .tabbrowser-tab:not([pinned]):not([fadein])[treestyletab-removed="true"] { - max-height: 1px !important; - min-height: 1px !important; - opacity: 0 !important; - /** - * Sometimes "transitionend" event for "max-width" doesn't fire so ghotst - * tabs are left. To do cleanup process forcedly, we have to re-define new - * max-width to fire transitionend event. - */ - max-width: 64000px !important; -} - -/* override toolbar binding */ -/** - * Don't use :-moz-any() selector to override styles defined by the theme. - * Normal selectors in default theme is stronger than :-moz-any() even if - * it includes #TabsToolbar in its subqueries. - */ -@media all and (-moz-windows-compositor) { /* for winstripe */ - #navigator-toolbox - > .treestyletab-tabbar-toolbar:not(#toolbar-menubar), - #navigator-toolbox - > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, - #navigator-toolbox - > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar), - #navigator-toolbox - > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, - #navigator-toolbox[tabsontop="true"] - > .treestyletab-tabbar-toolbar:not(#toolbar-menubar), - #navigator-toolbox[tabsontop="true"] - > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, - #navigator-toolbox[tabsontop="true"] - > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar), - #navigator-toolbox[tabsontop="true"] - > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, - - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar[tabsontop="true"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar[tabsontop="true"]:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar[tabsontop="false"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar[tabsontop="false"]:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready[tabsontop="true"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready[tabsontop="true"]:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready[tabsontop="false"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + .treestyletab-tabbar-toolbar-ready[tabsontop="false"]:not(:last-child), - - #navigator-toolbox - > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, - #navigator-toolbox - > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, - - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar:not(:last-child),heme, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar[tabsontop="true"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar[tabsontop="true"]:not(:last-child), - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar[tabsontop="false"]:last-child, - #navigator-toolbox - > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] - + #TabsToolbar[tabsontop="false"]:not(:last-child), - - #navigator-toolbox - > toolbar:not(#toolbar-menubar):not(#addon-bar):-moz-lwtheme { - -moz-binding: url(treestyletab.xml#toolbar-drag); - } -} -@media not all and (-moz-windows-compositor) { /* for winstripe */ - #TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), - #TabsToolbar[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), - #navigator-toolbox[tabsontop="false"] - > toolbar.treestyletab-tabbar-toolbar:not(#toolbar-menubar):not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), - #navigator-toolbox[tabsontop="false"] - > toolbar.treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):not(:-moz-lwtheme):-moz-system-metric(windows-default-theme) { - -moz-binding: url(treestyletab.xml#toolbar-drag); - } -} -.treestyletab-tabbar-toolbar, -.treestyletab-tabbar-toolbar-ready, -/* for gnomestripe */ -#TabsToolbar.treestyletab-tabbar-toolbar:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), -#TabsToolbar.treestyletab-tabbar-toolbar:not([autohide="true"])[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), -#TabsToolbar.treestyletab-tabbar-toolbar-ready:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), -#TabsToolbar.treestyletab-tabbar-toolbar-ready:not([autohide="true"])[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), -/* for pinstripe */ -toolbar.treestyletab-tabbar-toolbar:not([nowindowdrag="true"]), -toolbar.treestyletab-tabbar-toolbar-ready:not([nowindowdrag="true"]), -/* Hide Caption Titlebar Plus (Smart) - https://addons.mozilla.org/firefox/addon/hide-caption-titlebar-plus-sma/ */ -#main-window[dz_adv_scroll_via_tabbar="true"] #TabsToolbar { - -moz-binding: url(treestyletab.xml#toolbar-drag); -} -#navigator-toolbox[tabsontop="false"] - > .treestyletab-tabbar-toolbar, -#navigator-toolbox[tabsontop="false"] - > .treestyletab-tabbar-toolbar-ready, -#navigator-toolbox[tabsontop="false"] - > .treestyletab-tabbar-toolbar:-moz-lwtheme, -#navigator-toolbox[tabsontop="false"] - > .treestyletab-tabbar-toolbar-ready:-moz-lwtheme { - -moz-binding: url(treestyletab.xml#toolbar); -} - -.treestyletab-tabbar-toolbar:not([treestyletab-tabbar-position="top"]), -.treestyletab-tabbar-toolbar[treestyletab-tabbar-position="top"]:not([treestyletab-tabbar-fixed="true"]) { - appearance: none !important; - -moz-appearance: none !important; - line-height: 0 !important; /* to avoid an useless space above the tab bar */ - position: fixed !important; -} -.treestyletab-tabbar-toolbar:not([treestyletab-tabbar-position="top"]) - .tabbrowser-tabs, -.treestyletab-tabbar-toolbar[treestyletab-tabbar-position="top"]:not([treestyletab-tabbar-fixed="true"]) - .tabbrowser-tabs { - overflow: hidden !important; -} - -.treestyletab-tabbar-toolbar toolbaritem { - overflow: hidden; -} - - - -/* animation effects */ - -.tabbrowser-tabs[treestyletab-animation-enabled="true"][treestyletab-mode="vertical"] - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.2s ease-out, - margin-right 0.2s ease-out, - margin-top 0.15s ease-out, - opacity 0.15s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; - transition: margin-left 0.2s ease-out, - margin-right 0.2s ease-out, - margin-top 0.15s ease-out, - opacity 0.15s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; -} -.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"][treestyletab-mode="vertical"] - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.2s ease-out, - margin-right 0.2s ease-out, - margin-top 0.15s ease-out, - opacity 0.15s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; - transition: margin-left 0.2s ease-out, - margin-right 0.2s ease-out, - margin-top 0.15s ease-out, - opacity 0.15s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; -} - -.tabbrowser-tabs[treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"]):not([treestyletab-tab-inverted="true"]) - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-top 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; - transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-top 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; -} -.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"]):not([treestyletab-tab-inverted="true"]) - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-top 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; - transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-top 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; -} - -.tabbrowser-tabs[treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"])[treestyletab-tab-inverted="true"] - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-bottom 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; - transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-bottom 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */; -} -.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"])[treestyletab-tab-inverted="true"] - .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { - -moz-transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-bottom 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; - transition: margin-left 0.15s ease-out, - opacity 0.15s ease-out, - margin-bottom 0.2s ease-out, - min-height 0.15s ease-out, - max-height 0.15s ease-out, - min-width 0.15s ease-out /* for Firefox itself */, - max-width 0.15s ease-out /* for Firefox itself */, - transform 0.2s ease-out /* for Firefox itself */; -} - - - - -/* separating of groups */ - -/* horizontal tab bar */ -.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) - + .tabbrowser-tab[treestyletab-nest="0"], -.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) - ~ .tabbrowser-tab[hidden] - + .tabbrowser-tab[treestyletab-nest="0"], -.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) - + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children], -.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) - ~ .tabbrowser-tab[hidden] - + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children] { - margin-left: 0.8em !important; -} - -/* vertical tab bar */ -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) - + .tabbrowser-tab[treestyletab-nest="0"], -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) - ~ .tabbrowser-tab[hidden] - + .tabbrowser-tab[treestyletab-nest="0"], -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) - + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children], -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) - ~ .tabbrowser-tab[hidden] - + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children] { - margin-top: 0.8em !important; -} - - - -/* compatibility for Firefox features */ - -/* hide all tabs button (by user's preference) */ -.tabbrowser-tabs[treestyletab-hide-alltabs-button] - .tabs-alltabs-stack { - visibility: collapse !important; -} - -/* pinned tabs */ -.tabbrowser-tabs[treestyletab-mode="vertical"] { - -moz-margin-start: 0 !important; -} - -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-tab[pinned] { - /* height: 24px; */ - line-height: 0; - position: fixed !important; - /* width: 24px; */ - z-index: 100; -} -.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-dom-fullscreen-activated="true"] - .tabbrowser-tab[pinned] { - z-index: -100; -} - -.tabbrowser-tabs[treestyletab-mode="vertical"] - .tabbrowser-tab[pinned]:not(.treestyletab-faviconized) - .tab-label[pinned] { - width: auto; -} - -/* print preview */ -.tabbrowser-strip[treestyletab-tabbar-fixed="true"]+splitter, -.tabbrowser-strip[treestyletab-print-preview="true"], -.tabbrowser-strip[treestyletab-print-preview="true"]+splitter, -.treestyletab-tabbar-toolbar[treestyletab-print-preview="true"], -tabbrowser[treestyletab-tabbar-autohide-mode="1"][treestyletab-print-preview="true"] - .treestyletab-tabbar-toggler { - visibility: collapse; -} - -/* stylized window (opened by window.open() with features) */ -window[chromehidden~="toolbar"] - .tabbrowser-strip.treestyletab-tabbar-placeholder, -window[chromehidden~="toolbar"] - .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter, -window[chromehidden~="toolbar"] - .treestyletab-tabbar-toolbar { - visibility: collapse; -} - - -/* full tooltip */ - -#treestyletab-full-tree-tooltip[popup-shown="true"] { - -moz-transition: margin-left 0.2s ease-out, - margin-top 0.2s ease-out, - max-height 0.2s ease-out, - max-width 0.2s ease-out; - transition: margin-left 0.2s ease-out, - margin-top 0.2s ease-out, - max-height 0.2s ease-out, - max-width 0.2s ease-out; -} - -#treestyletab-full-tree-tooltip > arrowscrollbox { - opacity: 0; -} - -#treestyletab-full-tree-tooltip[popup-shown="true"] > arrowscrollbox { - -moz-transition: opacity 0.2s ease-out; - transition: opacity 0.2s ease-out; - opacity: 1; -} - - -/* addon compatibility */ - -/* some themes */ -image.tab-icon { - display: -moz-stack; - -moz-binding: url(treestyletab.xml#tab-icon); -} - -/* Tabbrowser Preferences, and some theme */ -.tabbrowser-tabs .tabs-newbutton, -.tabbrowser-tabs[treestyletab-mode="vertical"] .scrollbutton-up, -.tabbrowser-tabs[treestyletab-mode="vertical"] .scrollbutton-down { - max-width: none !important; -} - -/* All-in-One Sidebar */ -#aiostbx-tableft-toolbox:not([incustomisemode="true"]) > toolbar[currentset="__empty"], -#aiostbx-tableft-toolbox:not([incustomisemode="true"]) > toolbar:not([currentset]), -#aiostbx-tabright-toolbox:not([incustomisemode="true"]) > toolbar[currentset="__empty"], -#aiostbx-tabright-toolbox:not([incustomisemode="true"]) > toolbar:not([currentset]) { - visibility: collapse; -} - -/* AutoHide */ -#appcontent[ahFull="true"] .tabbrowser-strip[ahHIDE="true"]+splitter { - visibility: collapse; -} - -/* ColorfulTabs */ -:root:not([treestyletab-tabbar-position="top"]) #ctStack, -:root:not([treestyletab-tabbar-fixed="true"]) #ctStack { - display: none; -} - -/* Hide Caption Titlebar Plus */ -:root[treestyletab-tabbar-position="top"] #menubar-items[dz_home_mainmenu_floating="true"] { - z-index: 650000; -} +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); +@namespace html url("http://www.w3.org/1999/xhtml"); + + +/* twisty in tabs */ +.treestyletab-twisty, +.tabbrowser-tabs:not([treestyletab-allow-subtree-collapse="true"]) + .treestyletab-twisty { + visibility: hidden; +} +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-allow-subtree-collapse="true"]:not([treestyletab-twisty-style="none"]) + .tabbrowser-tab[treestyletab-children][treestyletab-allow-subtree-collapse="true"] + .treestyletab-twisty, +.tabbrowser-tabs[treestyletab-mode="horizontal"][treestyletab-allow-subtree-collapse="true"]:not([treestyletab-twisty-style="none"]) + .tabbrowser-tab[treestyletab-children][treestyletab-allow-subtree-collapse="true"] + .treestyletab-twisty { + visibility: visible; +} +.tabbrowser-tabs[treestyletab-twisty-style="none"] + .treestyletab-twisty, +.tabbrowser-tabs[treestyletab-mode="horizontal"] + .treestyletab-twisty, +.tabbrowser-tab[pinned="true"] + .treestyletab-twisty { + visibility: collapse; +} + +/* collapsed children counter */ +.treestyletab-counter-container { + display: none; +} +.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"] + .tabbrowser-tab[treestyletab-children][treestyletab-subtree-collapsed="true"][treestyletab-allow-subtree-collapse="true"] + .treestyletab-counter-container { + display: -moz-box; +} + +/* collapse/expand tree of tabs */ +.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"] + .tabbrowser-tab[treestyletab-collapsed-done="true"] { + visibility: collapse; +} + +/* collapse/expand tree of tabs, horizontal stacked tabs */ +.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] + .tabbrowser-tab { + position: relative; +} +.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] + .tabbrowser-tab[treestyletab-collapsed="true"][fadein], +/** + * Tab Mix Plus dynamically inserts a CSS rule like + * "#tabbrowser-tabs > .tabbrowser-tab { ... }" and it unexpectedly + * expands width of "collapsed" tabs. So, to apply our rule prior than + * TMP's one, I put the "#tabbrowser-tabs" version also. + */ +#tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] + .tabbrowser-tab[treestyletab-collapsed="true"][fadein] { + visibility: visible; + max-width: 20px; + min-width: 20px; + width: 20px; + box-flex: 0; + -moz-box-flex: 0; + mask: url("res/tabEffects.svg#fadein-mask"); +} +.tabbrowser-tabs[treestyletab-allow-subtree-collapse="true"][treestyletab-stack-collapsed-tabs="true"] + .tabbrowser-tab[treestyletab-collapsed-done="true"] .tab-content > * { + visibility: collapse; +} + +/* fixed tab bar */ +.tabbrowser-strip[treestyletab-tabbar-fixed="true"]+splitter { + visibility: collapse; +} + + +/* tab bar in the DOM-fullscreen mode */ + +.tabbrowser-tabs[treestyletab-dom-fullscreen-activated="true"], +.tabbrowser-strip[treestyletab-dom-fullscreen-activated="true"], +.tabbrowser-strip[treestyletab-dom-fullscreen-activated="true"]+splitter, +.treestyletab-tabbar-toolbar[treestyletab-dom-fullscreen-activated="true"], +.treestyletab-tabbar-toolbar[treestyletab-dom-fullscreen-activated="true"] > *, +tabbrowser[treestyletab-dom-fullscreen-activated-mode="true"][tabcontainer] + .tabbrowser-strip.treestyletab-tabbar-placeholder, +tabbrowser[treestyletab-dom-fullscreen-activated-mode="true"][tabcontainer] + .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter { + visibility: collapse; +} + + +/* auto hide tab bar */ + +.tabbrowser-tabs[treestyletab-tabbar-autohide="hidden"], +.tabbrowser-strip[treestyletab-tabbar-autohide="hidden"], +.tabbrowser-strip[treestyletab-tabbar-autohide="hidden"]+splitter, +.tabbrowser-strip[collapsed="true"] + splitter:not([state="collapsed"]), +.treestyletab-tabbar-toggler, +.treestyletab-tabbar-toolbar[treestyletab-tabbar-autohide="hidden"], +.treestyletab-tabbar-toolbar[treestyletab-tabbar-autohide="hidden"] > *, +.treestyletab-tabbar-toolbar .treestyletab-splitter, +#treestyletab-tabbar-resizer-box, +tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] + .tabbrowser-strip.treestyletab-tabbar-placeholder, +tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] + .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter { + visibility: collapse; +} + +tabbrowser[treestyletab-tabbar-autohide-mode="1"][tabcontainer] + .treestyletab-tabbar-toggler, +tabbrowser[treestyletab-tabbar-autohide-mode="1"][treestyletab-tabbar-autohide="hidden"] + .treestyletab-tabbar-toggler, +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide][treestyletab-tabbar-autohide-state="expanded"] + #treestyletab-tabbar-resizer-box, +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide][treestyletab-tabbar-autohide-state="expanded"]:not([treestyletab-tabbar-fixed="true"]) + .treestyletab-splitter { + visibility: visible; +} + +/* put resizer under tabs, and raise up tabs */ +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] + > *, +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] + > #tabbrowser-tabs, +.treestyletab-toolbar-inner-box[treestyletab-tabbar-autohide]:not([treestyletab-tabbar-fixed="true"]) + > *, +.treestyletab-toolbar-inner-box[treestyletab-tabbar-autohide]:not([treestyletab-tabbar-fixed="true"]) + > #tabbrowser-tabs { + position: relative; + z-index: 2; +} +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-tabbar-autohide] + #treestyletab-tabbar-resizer-box { + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + z-index: 1; + line-height: 0 !important; /* to avoid an useless space above the splitter */ + font-size: 0 !important; /* to avoid an useless space above the splitter */ +} + +.tabbrowser-tabs[treestyletab-tabbar-autohide] + .tabs-stack + > *:first-child + > :not(html|canvas), +.tabbrowser-tabs[treestyletab-tabbar-resizing] + .tabs-stack + > *:first-child + > html|canvas { + display: none !important; +} + + +#treestyletab-autohide-content-area-screen { + appearance: none; + -moz-appearance: none; + /** + * Don't make this panel completely transparent, because + * mousemove event never fire on 100% transparent panel. + * The color should be a pale white, instead of a pale black, + * because many websites use white background. Black screen + * is too glaring for white background websites. + */ + background: rgba(255, 255, 255, 0.01); + line-height: 0; + margin: 0; + padding: 0; + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; +} + + +/* vertical tab bar */ + +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-arrowscrollbox > scrollbox { + overflow-x: hidden !important; + overflow-y: auto !important; +} + +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-arrowscrollbox + .scrollbox-innerbox { + box-flex: 1 !important; + -moz-box-flex: 1 !important; + box-pack: start !important; + -moz-box-pack: start !important; +} + +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-tab, +toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] + > toolbarbutton, +toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] + > .treestyletab-toolbar-inner-box > toolbarbutton, +toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] + > toolbaritem, +toolbar.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] + > .treestyletab-toolbar-inner-box > toolbaritem { + box-flex: 0 !important; + -moz-box-flex: 0 !important; +} + +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] > toolbarbutton, +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] > toolbarpaletteitem > toolbarbutton { + max-width: none !important; +} + +/* workaround for https://github.com/piroor/treestyletab/issues/100 */ +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] .textbox-input-box { + box-align: stretch; + -moz-box-align: stretch; +} + + +/* leftside vertical tab bar */ + +tabs.tabbrowser-tabs[treestyletab-tabbar-position="left"][treestyletab-invert-scrollbar="true"] + .tabbrowser-arrowscrollbox + > scrollbox { + direction: rtl; +} +tabs.tabbrowser-tabs[treestyletab-tabbar-position="left"][treestyletab-invert-scrollbar="true"] + .tabbrowser-arrowscrollbox + > scrollbox + > .scrollbox-innerbox { + direction: ltr; +} + + +/* hide horizontal tab bar UI for vertical tab bar */ +.tabbrowser-tabs[treestyletab-mode="vertical"] .tabbrowser-arrowscrollbox > .scrollbutton-up, +.tabbrowser-tabs[treestyletab-mode="vertical"] .tabbrowser-arrowscrollbox > .scrollbutton-down:not([treestyletab-notifybgtab-phase]), +.tabbrowser-tabs[treestyletab-mode="vertical"] .tabs-closebutton-box, +.tabbrowser-tabs[treestyletab-mode="vertical"] .tab-drop-indicator-bar { + visibility: collapse !important; +} + +/** + * Firefox 4.0 finishes opening and closing processes of tabs by its + * "transitionend" events about max-width property. I think this is very + * dirty and specific implementation but it won't fixed. + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=585417#c3 ) + * So we have to define max-width for both states [fadein] (means "completely + * opened") and :not([fadein]) (means "in opening/removing process". + */ +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-tab:not([pinned]) { + max-width: 65000px !important; +} +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"] + .tabbrowser-tab:not([pinned]):not([fadein]) { + opacity: 0.5 !important; +} +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-tab:not([pinned])[fadein] { + max-width: 65250px !important; + min-width: 1px !important; +} +.treestyletab-tabbar-toolbar[treestyletab-mode="vertical"][treestyletab-animation-enabled="true"] + .tabbrowser-tab:not([pinned]):not([fadein])[treestyletab-removed="true"] { + max-height: 1px !important; + min-height: 1px !important; + opacity: 0 !important; + /** + * Sometimes "transitionend" event for "max-width" doesn't fire so ghotst + * tabs are left. To do cleanup process forcedly, we have to re-define new + * max-width to fire transitionend event. + */ + max-width: 64000px !important; +} + +/* override toolbar binding */ +/** + * Don't use :-moz-any() selector to override styles defined by the theme. + * Normal selectors in default theme is stronger than :-moz-any() even if + * it includes #TabsToolbar in its subqueries. + */ +@media all and (-moz-windows-compositor) { /* for winstripe */ + #navigator-toolbox + > .treestyletab-tabbar-toolbar:not(#toolbar-menubar), + #navigator-toolbox + > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, + #navigator-toolbox + > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar), + #navigator-toolbox + > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, + #navigator-toolbox[tabsontop="true"] + > .treestyletab-tabbar-toolbar:not(#toolbar-menubar), + #navigator-toolbox[tabsontop="true"] + > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, + #navigator-toolbox[tabsontop="true"] + > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar), + #navigator-toolbox[tabsontop="true"] + > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, + + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar[tabsontop="true"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar[tabsontop="true"]:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar[tabsontop="false"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar[tabsontop="false"]:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready[tabsontop="true"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready[tabsontop="true"]:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready[tabsontop="false"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + .treestyletab-tabbar-toolbar-ready[tabsontop="false"]:not(:last-child), + + #navigator-toolbox + > .treestyletab-tabbar-toolbar:not(#toolbar-menubar):-moz-lwtheme, + #navigator-toolbox + > .treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):-moz-lwtheme, + + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar:not(:last-child),heme, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar[tabsontop="true"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar[tabsontop="true"]:not(:last-child), + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar[tabsontop="false"]:last-child, + #navigator-toolbox + > #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + + #TabsToolbar[tabsontop="false"]:not(:last-child), + + #navigator-toolbox + > toolbar:not(#toolbar-menubar):not(#addon-bar):-moz-lwtheme { + -moz-binding: url(treestyletab.xml#toolbar-drag); + } +} +@media not all and (-moz-windows-compositor) { /* for winstripe */ + #TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), + #TabsToolbar[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), + #navigator-toolbox[tabsontop="false"] + > toolbar.treestyletab-tabbar-toolbar:not(#toolbar-menubar):not(:-moz-lwtheme):-moz-system-metric(windows-default-theme), + #navigator-toolbox[tabsontop="false"] + > toolbar.treestyletab-tabbar-toolbar-ready:not(#toolbar-menubar):not(:-moz-lwtheme):-moz-system-metric(windows-default-theme) { + -moz-binding: url(treestyletab.xml#toolbar-drag); + } +} +.treestyletab-tabbar-toolbar, +.treestyletab-tabbar-toolbar-ready, +/* for gnomestripe */ +#TabsToolbar.treestyletab-tabbar-toolbar:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +#TabsToolbar.treestyletab-tabbar-toolbar:not([autohide="true"])[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +#TabsToolbar.treestyletab-tabbar-toolbar-ready:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +#TabsToolbar.treestyletab-tabbar-toolbar-ready:not([autohide="true"])[tabsontop="true"]:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +/* for pinstripe */ +toolbar.treestyletab-tabbar-toolbar:not([nowindowdrag="true"]), +toolbar.treestyletab-tabbar-toolbar-ready:not([nowindowdrag="true"]), +/* Hide Caption Titlebar Plus (Smart) + https://addons.mozilla.org/firefox/addon/hide-caption-titlebar-plus-sma/ */ +#main-window[dz_adv_scroll_via_tabbar="true"] #TabsToolbar { + -moz-binding: url(treestyletab.xml#toolbar-drag); +} +#navigator-toolbox[tabsontop="false"] + > .treestyletab-tabbar-toolbar, +#navigator-toolbox[tabsontop="false"] + > .treestyletab-tabbar-toolbar-ready, +#navigator-toolbox[tabsontop="false"] + > .treestyletab-tabbar-toolbar:-moz-lwtheme, +#navigator-toolbox[tabsontop="false"] + > .treestyletab-tabbar-toolbar-ready:-moz-lwtheme { + -moz-binding: url(treestyletab.xml#toolbar); +} + +.treestyletab-tabbar-toolbar:not([treestyletab-tabbar-position="top"]), +.treestyletab-tabbar-toolbar[treestyletab-tabbar-position="top"]:not([treestyletab-tabbar-fixed="true"]) { + appearance: none !important; + -moz-appearance: none !important; + line-height: 0 !important; /* to avoid an useless space above the tab bar */ + position: fixed !important; +} +.treestyletab-tabbar-toolbar:not([treestyletab-tabbar-position="top"]) + .tabbrowser-tabs, +.treestyletab-tabbar-toolbar[treestyletab-tabbar-position="top"]:not([treestyletab-tabbar-fixed="true"]) + .tabbrowser-tabs { + overflow: hidden !important; +} + +.treestyletab-tabbar-toolbar toolbaritem { + overflow: hidden; +} + + + +/* animation effects */ + +.tabbrowser-tabs[treestyletab-animation-enabled="true"][treestyletab-mode="vertical"] + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.2s ease-out, + margin-right 0.2s ease-out, + margin-top 0.15s ease-out, + opacity 0.15s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; + transition: margin-left 0.2s ease-out, + margin-right 0.2s ease-out, + margin-top 0.15s ease-out, + opacity 0.15s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; +} +.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"][treestyletab-mode="vertical"] + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.2s ease-out, + margin-right 0.2s ease-out, + margin-top 0.15s ease-out, + opacity 0.15s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; + transition: margin-left 0.2s ease-out, + margin-right 0.2s ease-out, + margin-top 0.15s ease-out, + opacity 0.15s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; +} + +.tabbrowser-tabs[treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"]):not([treestyletab-tab-inverted="true"]) + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-top 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; + transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-top 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; +} +.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"]):not([treestyletab-tab-inverted="true"]) + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-top 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; + transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-top 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; +} + +.tabbrowser-tabs[treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"])[treestyletab-tab-inverted="true"] + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-bottom 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; + transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-bottom 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */; +} +.tabbrowser-tabs[movingtab][treestyletab-animation-enabled="true"]:not([treestyletab-mode="vertical"])[treestyletab-tab-inverted="true"] + .tabbrowser-tab:not([treestyletab-collapsed-done="true"]) { + -moz-transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-bottom 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; + transition: margin-left 0.15s ease-out, + opacity 0.15s ease-out, + margin-bottom 0.2s ease-out, + min-height 0.15s ease-out, + max-height 0.15s ease-out, + min-width 0.15s ease-out /* for Firefox itself */, + max-width 0.15s ease-out /* for Firefox itself */, + transform 0.2s ease-out /* for Firefox itself */; +} + + + + +/* separating of groups */ + +/* horizontal tab bar */ +.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) + + .tabbrowser-tab[treestyletab-nest="0"], +.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) + ~ .tabbrowser-tab[hidden] + + .tabbrowser-tab[treestyletab-nest="0"], +.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) + + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children], +.tabbrowser-tabs[treestyletab-mode="horizontal"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) + ~ .tabbrowser-tab[hidden] + + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children] { + margin-left: 0.8em !important; +} + +/* vertical tab bar */ +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) + + .tabbrowser-tab[treestyletab-nest="0"], +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab:not([treestyletab-nest="0"]):not([hidden]) + ~ .tabbrowser-tab[hidden] + + .tabbrowser-tab[treestyletab-nest="0"], +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) + + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children], +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-max-tree-level="0"] .tabbrowser-tab[treestyletab-nest="0"]:not([hidden]) + ~ .tabbrowser-tab[hidden] + + .tabbrowser-tab[treestyletab-nest="0"][treestyletab-children] { + margin-top: 0.8em !important; +} + + + +/* compatibility for Firefox features */ + +/* hide all tabs button (by user's preference) */ +.tabbrowser-tabs[treestyletab-hide-alltabs-button] + .tabs-alltabs-stack { + visibility: collapse !important; +} + +/* pinned tabs */ +.tabbrowser-tabs[treestyletab-mode="vertical"] { + -moz-margin-start: 0 !important; +} + +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-tab[pinned] { + /* height: 24px; */ + line-height: 0; + position: fixed !important; + /* width: 24px; */ + z-index: 100; +} +.tabbrowser-tabs[treestyletab-mode="vertical"][treestyletab-dom-fullscreen-activated="true"] + .tabbrowser-tab[pinned] { + z-index: -100; +} + +.tabbrowser-tabs[treestyletab-mode="vertical"] + .tabbrowser-tab[pinned]:not(.treestyletab-faviconized) + .tab-label[pinned] { + width: auto; +} + +/* print preview */ +.tabbrowser-strip[treestyletab-tabbar-fixed="true"]+splitter, +.tabbrowser-strip[treestyletab-print-preview="true"], +.tabbrowser-strip[treestyletab-print-preview="true"]+splitter, +.treestyletab-tabbar-toolbar[treestyletab-print-preview="true"], +tabbrowser[treestyletab-tabbar-autohide-mode="1"][treestyletab-print-preview="true"] + .treestyletab-tabbar-toggler { + visibility: collapse; +} + +/* stylized window (opened by window.open() with features) */ +window[chromehidden~="toolbar"] + .tabbrowser-strip.treestyletab-tabbar-placeholder, +window[chromehidden~="toolbar"] + .tabbrowser-strip.treestyletab-tabbar-placeholder+splitter, +window[chromehidden~="toolbar"] + .treestyletab-tabbar-toolbar { + visibility: collapse; +} + + +/* full tooltip */ + +#treestyletab-full-tree-tooltip[popup-shown="true"] { + -moz-transition: margin-left 0.2s ease-out, + margin-top 0.2s ease-out, + max-height 0.2s ease-out, + max-width 0.2s ease-out; + transition: margin-left 0.2s ease-out, + margin-top 0.2s ease-out, + max-height 0.2s ease-out, + max-width 0.2s ease-out; +} + +#treestyletab-full-tree-tooltip > arrowscrollbox { + opacity: 0; +} + +#treestyletab-full-tree-tooltip[popup-shown="true"] > arrowscrollbox { + -moz-transition: opacity 0.2s ease-out; + transition: opacity 0.2s ease-out; + opacity: 1; +} + + +/* addon compatibility */ + +/* some themes */ +image.tab-icon { + display: -moz-stack; + -moz-binding: url(treestyletab.xml#tab-icon); +} + +/* Tabbrowser Preferences, and some theme */ +.tabbrowser-tabs .tabs-newbutton, +.tabbrowser-tabs[treestyletab-mode="vertical"] .scrollbutton-up, +.tabbrowser-tabs[treestyletab-mode="vertical"] .scrollbutton-down { + max-width: none !important; +} + +/* All-in-One Sidebar */ +#aiostbx-tableft-toolbox:not([incustomisemode="true"]) > toolbar[currentset="__empty"], +#aiostbx-tableft-toolbox:not([incustomisemode="true"]) > toolbar:not([currentset]), +#aiostbx-tabright-toolbox:not([incustomisemode="true"]) > toolbar[currentset="__empty"], +#aiostbx-tabright-toolbox:not([incustomisemode="true"]) > toolbar:not([currentset]) { + visibility: collapse; +} + +/* AutoHide */ +#appcontent[ahFull="true"] .tabbrowser-strip[ahHIDE="true"]+splitter { + visibility: collapse; +} + +/* ColorfulTabs */ +:root:not([treestyletab-tabbar-position="top"]) #ctStack, +:root:not([treestyletab-tabbar-fixed="true"]) #ctStack { + display: none; +} + +/* Hide Caption Titlebar Plus */ +:root[treestyletab-tabbar-position="top"] #menubar-items[dz_home_mainmenu_floating="true"] { + z-index: 650000; +} diff --git a/content/treestyletab/treestyletab.js b/content/treestyletab/treestyletab.js index e0cfae67..16c30415 100644 --- a/content/treestyletab/treestyletab.js +++ b/content/treestyletab/treestyletab.js @@ -1,23 +1,23 @@ -(function() { - /** - * On secondary (and later) window, SSWindowStateBusy event can be fired - * before DOMContentLoad, on "domwindowopened". - */ - var SSWindowStateBusyListener = function TSTSSWindowStateBusyListener(aEvent) { - window.removeEventListener(aEvent.type, TSTSSWindowStateBusyListener, false); - window.__treestyletab__WindowStateBusy = true; - SSWindowStateBusyListener = undefined; - }; - window.addEventListener('SSWindowStateBusy', SSWindowStateBusyListener, false); - window.addEventListener('DOMContentLoad', function onDOMContentLoad(aEvent) { - window.removeEventListener(aEvent.type, onDOMContentLoad, false); - if (SSWindowStateBusyListener) { - window.removeEventListener('SSWindowStateBusy', TSTSSWindowStateBusyListener, false); - SSWindowStateBusyListener = undefined; - } - }, false); - - var ns = {}; - Components.utils.import('resource://treestyletab-modules/window.js', ns); - new ns.TreeStyleTabWindow(window); -})(); +(function() { + /** + * On secondary (and later) window, SSWindowStateBusy event can be fired + * before DOMContentLoad, on "domwindowopened". + */ + var SSWindowStateBusyListener = function TSTSSWindowStateBusyListener(aEvent) { + window.removeEventListener(aEvent.type, TSTSSWindowStateBusyListener, false); + window.__treestyletab__WindowStateBusy = true; + SSWindowStateBusyListener = undefined; + }; + window.addEventListener('SSWindowStateBusy', SSWindowStateBusyListener, false); + window.addEventListener('DOMContentLoad', function onDOMContentLoad(aEvent) { + window.removeEventListener(aEvent.type, onDOMContentLoad, false); + if (SSWindowStateBusyListener) { + window.removeEventListener('SSWindowStateBusy', TSTSSWindowStateBusyListener, false); + SSWindowStateBusyListener = undefined; + } + }, false); + + var ns = {}; + Components.utils.import('resource://treestyletab-modules/window.js', ns); + new ns.TreeStyleTabWindow(window); +})(); diff --git a/content/treestyletab/treestyletab.xml b/content/treestyletab/treestyletab.xml index e1edfcd5..ff98e8a8 100644 --- a/content/treestyletab/treestyletab.xml +++ b/content/treestyletab/treestyletab.xml @@ -1,54 +1,54 @@ - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, 'anonid', 'toolbar-innerbox') - - - - - - - - - document.getAnonymousElementByAttribute(this, 'anonid', 'toolbar-innerbox') - - - - - + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, 'anonid', 'toolbar-innerbox') + + + + + + + + + document.getAnonymousElementByAttribute(this, 'anonid', 'toolbar-innerbox') + + + + + diff --git a/content/treestyletab/treestyletab.xul b/content/treestyletab/treestyletab.xul index 4dafd284..96cfdf1e 100644 --- a/content/treestyletab/treestyletab.xul +++ b/content/treestyletab/treestyletab.xul @@ -1,152 +1,152 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/treestyletab/windowHelper.js b/content/treestyletab/windowHelper.js index 242223f1..9335d504 100644 --- a/content/treestyletab/windowHelper.js +++ b/content/treestyletab/windowHelper.js @@ -1,599 +1,599 @@ -var TreeStyleTabWindowHelper = { - - get service() - { - return TreeStyleTabService; - }, - - preInit : function TSTWH_preInit() - { - var source; - var target; - if ('gBrowserInit' in window) { - if ( - '_delayedStartup' in gBrowserInit && - (source = gBrowserInit._delayedStartup.toSource()) && - source.indexOf('swapBrowsersAndCloseOther') > -1 - ) { - target = 'gBrowserInit._delayedStartup'; - } - } - if (!target) - dump('Tree Style Tab: failed to initialize startup function!'); - if (source.indexOf('!MultipleTabService.tearOffSelectedTabsFromRemote()') > -1) { - eval(target+' = '+source.replace( - '!MultipleTabService.tearOffSelectedTabsFromRemote()', - '!TreeStyleTabService.tearOffSubtreeFromRemote() && $&' - )); - } - else if (source.indexOf('gBrowser.swapBrowsersAndCloseOther') > -1) { - eval(target+' = '+source.replace( - 'gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);', - 'if (!TreeStyleTabService.tearOffSubtreeFromRemote()) { $& }' - ).replace( - // Workaround for https://github.com/piroor/treestyletab/issues/741 - // After the function is updated by TST, reassignment of a global variable raises an error like: - // > System JS : ERROR chrome://treestyletab/content/windowHelper.js line 30 > eval:130 - TypeError: can't redefine non-configurable property 'gBidiUI' - // If I access it as a property of the global object, the error doesn't appear. - /([^\.])\bgBidiUI =/, - '$1window.gBidiUI =' - )); - } - - eval('nsBrowserAccess.prototype.openURI = '+ - nsBrowserAccess.prototype.openURI.toSource().replace( - /(switch\s*\(aWhere\))/, - 'TreeStyleTabService.onBeforeBrowserAccessOpenURI(aOpener, aWhere); $1' - ) - ); - - if ('TabsInTitlebar' in window && - TabsInTitlebar._update) { - eval('window.TabsInTitlebar._update = '+ - window.TabsInTitlebar._update.toSource().replace( - /let fullTabsHeight = /, - '$& gBrowser.treeStyleTab.position != "top" ? 0 : ' - ) - ); - } - - if ('BrowserOpenTab' in window) { - eval('window.BrowserOpenTab = '+ - window.BrowserOpenTab.toSource().replace( - 'openUILinkIn(', - 'gBrowser.treeStyleTab.onBeforeNewTabCommand(); $&' - ) - ); - } - - if ('undoCloseTab' in window) { - eval('window.undoCloseTab = '+ - window.undoCloseTab.toSource().replace( - /(\btab\s*=\s*[^\.]+\.undoCloseTab\([^;]+\);)/, - 'gBrowser.__treestyletab__readyToUndoCloseTab = true;\n' + - '$1\n' + - 'tab.__treestyletab__restoredByUndoCloseTab = true;\n' + - 'delete gBrowser.__treestyletab__readyToUndoCloseTab;' - ) - ); - } - - if ('XULBrowserWindow' in window && - 'hideChromeForLocation' in window.XULBrowserWindow) { - eval('XULBrowserWindow.hideChromeForLocation = '+ - XULBrowserWindow.hideChromeForLocation.toSource().replace( - '{', - '{ if (gBrowser.treeStyleTab.isVertical) return false;\n' - ) - ); - } - - let (functions = [ - 'window.duplicateTab.handleLinkClick', - 'window.duplicatethistab.handleLinkClick', - 'window.__treestyletab__highlander__origHandleLinkClick', - 'window.__splitbrowser__handleLinkClick', - 'window.__ctxextensions__handleLinkClick', - 'window.handleLinkClick' - ]) { - for (let i = 0, maxi = functions.length; i < maxi; i++) - { - let func = functions[i]; - let source = this._getFunctionSource(func); - if (!source || !/^\(?function handleLinkClick/.test(source)) - continue; - eval(func+' = '+source.replace( - /(charset\s*:\s*doc\.characterSet\s*)/, - '$1, event : event, linkNode : linkNode' - )); - break; - } - } - - this.overrideExtensionsPreInit(); // windowHelperHacks.js - }, - - onBeforeBrowserInit : function TSTWH_onBeforeBrowserInit() - { - this.overrideExtensionsBeforeBrowserInit(); // windowHelperHacks.js - this.overrideGlobalFunctions(); - }, - - onAfterBrowserInit : function TSTWH_onAfterBrowserInit() - { - this.overrideExtensionsAfterBrowserInit(); // windowHelperHacks.js - }, - - updateTabDNDObserver : function TSTWH_updateTabDNDObserver(aObserver) - { - var strip = this.service.getTabStrip(aObserver) || - gBrowser.mStrip // fallback to the default strip, for Tab Mix Plus; - - if ( - aObserver.tabContainer && - aObserver.tabContainer.tabbrowser == aObserver - ) - aObserver = aObserver.tabContainer; - - if ('_setEffectAllowedForDataTransfer' in aObserver) { - eval('aObserver._setEffectAllowedForDataTransfer = '+ - aObserver._setEffectAllowedForDataTransfer.toSource().replace( - '{', - '{ var TSTTabBrowser = this instanceof Ci.nsIDOMElement ? (this.tabbrowser || this) : gBrowser ; var TST = TSTTabBrowser.treeStyleTab;' - ).replace( - /\.screenX/g, '[TST.screenPositionProp]' - ).replace( - /\.width/g, '[TST.sizeProp]' - ).replace( - /(return (?:true|dt.effectAllowed = "copyMove");)/, - 'if (!TST.tabbarDNDObserver.canDropTab(arguments[0])) {\n' + - ' return dt.effectAllowed = "none";\n' + - '}\n' + - '$1' - ).replace( - 'sourceNode.parentNode == this &&', - '$& TST.getTabFromEvent(event) == sourceNode &&' - ) - ); - } - }, - - overrideGlobalFunctions : function TSTWH_overrideGlobalFunctions() - { - this.initToolbarItems(); - - eval('nsContextMenu.prototype.openLinkInTab = '+ - nsContextMenu.prototype.openLinkInTab.toSource().replace( - '{', - '{\n' + - ' TreeStyleTabService.handleNewTabFromCurrent(this.target.ownerDocument.defaultView);' - ) - ); - eval('nsContextMenu.prototype.openFrameInTab = '+ - nsContextMenu.prototype.openFrameInTab.toSource().replace( - '{', - '{\n' + - ' TreeStyleTabService.handleNewTabFromCurrent(this.target.ownerDocument.defaultView);' - ) - ); - var viewImageMethod = ('viewImage' in nsContextMenu.prototype) ? 'viewImage' : 'viewMedia' ; - eval('nsContextMenu.prototype.'+viewImageMethod+' = '+ - nsContextMenu.prototype[viewImageMethod].toSource().replace( - 'openUILink(', - 'TreeStyleTabService.onBeforeViewMedia(e, this.target.ownerDocument.defaultView); $&' - ) - ); - eval('nsContextMenu.prototype.viewBGImage = '+ - nsContextMenu.prototype.viewBGImage.toSource().replace( - 'openUILink(', - 'TreeStyleTabService.onBeforeViewMedia(e, this.target.ownerDocument.defaultView); $&' - ) - ); - eval('nsContextMenu.prototype.addDictionaries = '+ - nsContextMenu.prototype.addDictionaries.toSource().replace( - 'openUILinkIn(', - 'TreeStyleTabService.onBeforeOpenLink(where, this.target.ownerDocument.defaultView); $&' - ) - ); - - if ('BrowserSearch' in window && - '_loadSearch' in BrowserSearch) { - eval('BrowserSearch._loadSearch = '+ - BrowserSearch._loadSearch.toSource().replace( - 'openLinkIn(', - 'TreeStyleTabService.onBeforeBrowserSearch(arguments[0], useNewTab); $&' - ) - ); - } - - if ('openLinkIn' in window) { - // Bug 1050447 changed this line in Fx 34 to - // newTab = w.gBrowser.loadOneTab( - eval('window.openLinkIn = '+ - window.openLinkIn.toSource().replace( - /((b|newTab = w\.gB)rowser.loadOneTab\()/g, - 'TreeStyleTabService.onBeforeOpenLinkWithTab(gBrowser.selectedTab, aFromChrome); $1' - ) - ); - } - - let (functions = [ - 'window.permaTabs.utils.wrappedFunctions["window.contentAreaClick"]', - 'window.__contentAreaClick', - 'window.__ctxextensions__contentAreaClick', - 'window.contentAreaClick' - ]) { - for (let i = 0, maxi = functions.length; i < maxi; i++) - { - let func = functions[i]; - let source = this._getFunctionSource(func); - if (!source || !/^\(?function contentAreaClick/.test(source)) - continue; - eval(func+' = '+source.replace( - // for Tab Utilities, etc. Some addons insert openNewTabWith() to the function. - // (calls for the function is not included by Firefox default.) - /(openNewTabWith\()/g, - 'TreeStyleTabService.onBeforeOpenNewTabByThirdParty(event.target.ownerDocument.defaultView); $1' - )); - } - } - - if (window.duplicateTabIn) { - eval('window.duplicateTabIn = '+ - window.duplicateTabIn.toSource().replace( - '{', - '{ gBrowser.treeStyleTab.onBeforeTabDuplicate(aTab, where, delta); ' - ) - ); - } - - let (functions = [ - 'permaTabs.utils.wrappedFunctions["window.BrowserHomeClick"]', - 'window.BrowserHomeClick', - 'window.BrowserGoHome' - ]) { - for (let i = 0, maxi = functions.length; i < maxi; i++) - { - let func = functions[i]; - let source = this._getFunctionSource(func); - if (!source || !/^\(?function (BrowserHomeClick|BrowserGoHome)/.test(source)) - continue; - eval(func+' = '+source.replace( - 'gBrowser.loadTabs(', - 'TreeStyleTabService.readyToOpenNewTabGroup(gBrowser); $&' - )); - } - } - - eval('FeedHandler.loadFeed = '+ - FeedHandler.loadFeed.toSource().replace( - 'openUILink(', - 'TreeStyleTabService.onBeforeViewMedia(event, gBrowser); $&' - ) - ); - - eval('FullScreen.mouseoverToggle = '+ - FullScreen.mouseoverToggle.toSource().replace( - 'this._isChromeCollapsed = !aShow;', - 'gBrowser.treeStyleTab.updateFloatingTabbar(gBrowser.treeStyleTab.kTABBAR_UPDATE_BY_FULLSCREEN); $&' - ) - ); - eval('FullScreen.toggle = '+ - FullScreen.toggle.toSource().replace( - '{', - '{ gBrowser.treeStyleTab.onBeforeFullScreenToggle(); ' - ) - ); - - if ('PrintUtils' in window) { - eval('PrintUtils.printPreview = '+PrintUtils.printPreview.toSource().replace( - '{', - '{ TreeStyleTabService.onPrintPreviewEnter();' - )); - eval('PrintUtils.exitPrintPreview = '+PrintUtils.exitPrintPreview.toSource().replace( - '{', - '{ TreeStyleTabService.onPrintPreviewExit();' - )); - } - - if ('TabsOnTop' in window && TabsOnTop.syncUI) { - eval('TabsOnTop.syncUI = '+TabsOnTop.syncUI.toSource().replace( - /(\}\)?)$/, - 'gBrowser.treeStyleTab.onTabsOnTopSyncCommand(enabled); $&' - )); - } - - if ('toggleSidebar' in window) { - eval('window.toggleSidebar = '+ - window.toggleSidebar.toSource().replace( - '{', - '{ gBrowser.treeStyleTab.updateFloatingTabbar(gBrowser.treeStyleTab.kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR);' - ) - ); - } - }, - _splitFunctionNames : function TSTWH__splitFunctionNames(aString) - { - return String(aString) - .split(/\s+/) - .map(function(aString) { - return aString - .replace(/\/\*.*\*\//g, '') - .replace(/\/\/.+$/, '') - .trim(); - }); - }, - _getFunctionSource : function TSTWH__getFunctionSource(aFunc) - { - var func; - try { - eval('func = '+aFunc); - } - catch(e) { - return null; - } - return func ? func.toSource() : null ; - }, - - initToolbarItems : function TSTWH_initToolbarItems() - { - var searchbar = document.getElementById('searchbar'); - if (searchbar && - searchbar.doSearch && - searchbar.doSearch.toSource().toSource().indexOf('TreeStyleTabService') < 0) { - eval('searchbar.doSearch = '+searchbar.doSearch.toSource().replace( - /(openUILinkIn\(.+?\);)/, - 'TreeStyleTabService.onBeforeBrowserSearch(arguments[0]);\n' + - '$1\n' + - 'TreeStyleTabService.stopToOpenChildTab();' - )); - } - - var goButton = document.getElementById('urlbar-go-button'); - if (goButton) - goButton.parentNode.addEventListener('click', this.service, true); - - var tabbar = this.service.getTabStrip(this.service.browser); - tabbar.addEventListener('click', this.service, true); - - var newTabButton = document.getElementById('new-tab-button'); - const nsIDOMNode = Ci.nsIDOMNode; - if (newTabButton && - !(tabbar.compareDocumentPosition(newTabButton) & nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY)) - newTabButton.parentNode.addEventListener('click', this.service, true); - - this.service.updateAllTabsButton(gBrowser); - }, - - destroyToolbarItems : function TSTWH_destroyToolbarItems() - { - var goButton = document.getElementById('urlbar-go-button'); - if (goButton) - goButton.parentNode.removeEventListener('click', this, true); - - var tabbar = this.service.getTabStrip(this.service.browser); - tabbar.removeEventListener('click', this.service, true); - - var newTabButton = document.getElementById('new-tab-button'); - const nsIDOMNode = Ci.nsIDOMNode; - if (newTabButton && - !(tabbar.compareDocumentPosition(newTabButton) & Ci.nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY)) - newTabButton.parentNode.removeEventListener('click', this.service, true); - - var allTabsButton = document.getElementById('alltabs-button'); - if (allTabsButton && allTabsButton.hasChildNodes()) - allTabsButton.firstChild.setAttribute('position', 'after_end'); - }, - - initTabbrowserMethods : function TSTWH_initTabbrowserMethods(aTabBrowser) - { - var b = aTabBrowser; - - let (source = b.moveTabForward.toSource()) { - eval('b.moveTabForward = '+ - source.replace( - 'if (nextTab)', - '(function() {\n' + - ' if (this.treeStyleTab.hasChildTabs(this.mCurrentTab)) {\n' + - ' let descendant = this.treeStyleTab.getDescendantTabs(this.mCurrentTab);\n' + - ' if (descendant.length)\n' + - ' nextTab = this.treeStyleTab.getNextTab(descendant[descendant.length-1]);\n' + - ' }\n' + - '}).call(this);' + - '$&' - ).replace( - /(this.moveTabTo\([^;]+\);)/, - '(function() {\n' + - ' let descendant = this.treeStyleTab.getDescendantTabs(nextTab);\n' + - ' if (descendant.length) {\n' + - ' nextTab = descendant[descendant.length-1];\n' + - ' }\n' + - ' $1\n' + - '}).call(this);' - ).replace( - 'this.moveTabToStart();', - '(function() {\n' + - ' this.treeStyleTab.internallyTabMovingCount++;\n' + - ' let parentTab = this.treeStyleTab.getParentTab(this.mCurrentTab);\n' + - ' if (parentTab) {\n' + - ' this.moveTabTo(this.mCurrentTab, this.treeStyleTab.getFirstChildTab(parentTab)._tPos);\n' + - ' this.mCurrentTab.focus();\n' + - ' }\n' + - ' else {\n' + - ' $&\n' + - ' }\n' + - ' this.treeStyleTab.internallyTabMovingCount--;\n' + - '}).call(this);' - ) - ); - } - - let (source = b.moveTabBackward.toSource()) { - eval('b.moveTabBackward = '+ - source.replace( - 'this.moveTabToEnd();', - '(function() {\n' + - ' this.treeStyleTab.internallyTabMovingCount++;\n' + - ' let parentTab = this.treeStyleTab.getParentTab(this.mCurrentTab);\n' + - ' if (parentTab) {\n' + - ' this.moveTabTo(this.mCurrentTab, this.treeStyleTab.getLastChildTab(parentTab)._tPos);\n' + - ' this.mCurrentTab.focus();\n' + - ' }\n' + - ' else {\n' + - ' $&\n' + - ' }\n' + - ' this.treeStyleTab.internallyTabMovingCount--;\n' + - '}).call(this);' - ) - ); - } - - eval('b.loadTabs = '+ - b.loadTabs.toSource().replace( - 'var tabNum = ', - 'if (this.treeStyleTab.readiedToAttachNewTabGroup)\n' + - ' TreeStyleTabService.readyToOpenChildTab(firstTabAdded || this.selectedTab, true);\n' + - '$&' - ).replace( - 'if (!aLoadInBackground)', - 'if (TreeStyleTabService.checkToOpenChildTab(this))\n' + - ' TreeStyleTabService.stopToOpenChildTab(this);\n' + - '$&' - ).replace( - 'this.selectedTab = firstTabAdded;', - 'this.selectedTab = aURIs[0].indexOf("about:treestyletab-group") < 0 ? \n' + - ' firstTabAdded :\n' + - ' TreeStyleTabService.getNextTab(firstTabAdded) ;' - ) - ); - - if ('_beginRemoveTab' in b) { - eval('b._beginRemoveTab = '+ - b._beginRemoveTab.toSource().replace( - 'if (this.tabs.length - this._removingTabs.length == 1) {', - 'if (this.tabs.length - this._removingTabs.length == 1 || this.treeStyleTab.shouldCloseLastTabSubtreeOf(aTab)) {' - ).replace( - 'this._removingTabs.length == 0', - '(this.treeStyleTab.shouldCloseLastTabSubtreeOf(aTab) || $&)' - ) - ); - } - - eval('b.removeCurrentTab = '+b.removeCurrentTab.toSource().replace( - '{', - '{ if (!this.treeStyleTab.warnAboutClosingTabSubtreeOf(this.selectedTab)) return;' - )); - }, - - initTabbarMethods : function TSTWH_initTabbarMethods(aTabBrowser) - { - var b = aTabBrowser; - - var source = b.mTabContainer.advanceSelectedTab.toSource(); - if (source.indexOf('treeStyleTab.handleAdvanceSelectedTab') < 0) { - eval('b.mTabContainer.advanceSelectedTab = '+ - source.replace( - '{', - '{\n' + - ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + - ' if (treeStyleTab.handleAdvanceSelectedTab(arguments[0], arguments[1]))\n' + - ' return;' - ) - ); - } - - source = b.mTabContainer._notifyBackgroundTab.toSource(); - if (source.indexOf('TreeStyleTabService.getTabBrowserFromChild') < 0) { - eval('b.mTabContainer._notifyBackgroundTab = '+ - source.replace( - '{', - '{\n' + - ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + - ' if (treeStyleTab.scrollToNewTabMode == 0 ||\n' + - ' treeStyleTab.shouldCancelEnsureElementIsVisible())\n' + - ' return;' - ).replace( - /\.screenX/g, '[treeStyleTab.screenPositionProp]' - ).replace( - /\.width/g, '[treeStyleTab.sizeProp]' - ).replace( - /\.left/g, '[treeStyleTab.startProp]' - ).replace( - /\.right/g, '[treeStyleTab.endProp]' - - // replace such codes: - // tab = {left: tab.left, right: tab.right}; - ).replace( - /left\s*:/g, 'start:' - ).replace( - /right\s*:/g, 'end:' - ).replace( - /((tab|selected)\s*=\s*\{\s*start:[^\}]+\})/g, - '$1; $2[treeStyleTab.startProp] = $2.start; $2[treeStyleTab.endProp] = $2.end;' - - ).replace( - '!selected ||', - '$& treeStyleTab.scrollToNewTabMode == 1 && ' - ).replace( - /(\}\)?)$/, - 'treeStyleTab.notifyBackgroundTab(); $1' - ) - ); - } - - if (b.tabContainer && '_getDropIndex' in b.tabContainer) { - eval('b.tabContainer._getDropIndex = '+ - b.tabContainer._getDropIndex.toSource().replace( - /\.screenX/g, '[this.treeStyleTab.screenPositionProp]' - ).replace( - /\.width/g, '[this.treeStyleTab.sizeProp]' - ) - ); - } - - /** - * The default implementation fails to scroll to tab if it is expanding. - * So we have to inject codes to override its effect. - */ - let (scrollbox = aTabBrowser.treeStyleTab.scrollBox) { - let source = scrollbox.ensureElementIsVisible.toSource(); - if ( - source.indexOf('treeStyleTab') < 0 && // not updated yet - source.indexOf('ensureTabIsVisible') < 0 // not replaced by Tab Mix Plus - ) { - eval('scrollbox.ensureElementIsVisible = '+ - source.replace( - '{', - '{\n' + - ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + - ' if (treeStyleTab && treeStyleTab.shouldCancelEnsureElementIsVisible())\n' + - ' return;\n' + - ' if (\n' + - ' treeStyleTab &&\n' + - ' (arguments.length == 1 || arguments[1])\n' + - ' )\n' + - ' return treeStyleTab.scrollToTab(arguments[0]);' - ) - ); - } - } - - let (popup = document.getElementById('alltabs-popup')) { - if (popup && '_updateTabsVisibilityStatus' in popup) { - eval('popup._updateTabsVisibilityStatus = '+ - popup._updateTabsVisibilityStatus.toSource().replace( - '{', - '{ var treeStyleTab = gBrowser.treeStyleTab;' - ).replace( - /\.screenX/g, '[treeStyleTab.screenPositionProp]' - ).replace( - /\.width/g, '[treeStyleTab.sizeProp]' - ) - ); - } - } - - } - -}; - +var TreeStyleTabWindowHelper = { + + get service() + { + return TreeStyleTabService; + }, + + preInit : function TSTWH_preInit() + { + var source; + var target; + if ('gBrowserInit' in window) { + if ( + '_delayedStartup' in gBrowserInit && + (source = gBrowserInit._delayedStartup.toSource()) && + source.indexOf('swapBrowsersAndCloseOther') > -1 + ) { + target = 'gBrowserInit._delayedStartup'; + } + } + if (!target) + dump('Tree Style Tab: failed to initialize startup function!'); + if (source.indexOf('!MultipleTabService.tearOffSelectedTabsFromRemote()') > -1) { + eval(target+' = '+source.replace( + '!MultipleTabService.tearOffSelectedTabsFromRemote()', + '!TreeStyleTabService.tearOffSubtreeFromRemote() && $&' + )); + } + else if (source.indexOf('gBrowser.swapBrowsersAndCloseOther') > -1) { + eval(target+' = '+source.replace( + 'gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);', + 'if (!TreeStyleTabService.tearOffSubtreeFromRemote()) { $& }' + ).replace( + // Workaround for https://github.com/piroor/treestyletab/issues/741 + // After the function is updated by TST, reassignment of a global variable raises an error like: + // > System JS : ERROR chrome://treestyletab/content/windowHelper.js line 30 > eval:130 - TypeError: can't redefine non-configurable property 'gBidiUI' + // If I access it as a property of the global object, the error doesn't appear. + /([^\.])\bgBidiUI =/, + '$1window.gBidiUI =' + )); + } + + eval('nsBrowserAccess.prototype.openURI = '+ + nsBrowserAccess.prototype.openURI.toSource().replace( + /(switch\s*\(aWhere\))/, + 'TreeStyleTabService.onBeforeBrowserAccessOpenURI(aOpener, aWhere); $1' + ) + ); + + if ('TabsInTitlebar' in window && + TabsInTitlebar._update) { + eval('window.TabsInTitlebar._update = '+ + window.TabsInTitlebar._update.toSource().replace( + /let fullTabsHeight = /, + '$& gBrowser.treeStyleTab.position != "top" ? 0 : ' + ) + ); + } + + if ('BrowserOpenTab' in window) { + eval('window.BrowserOpenTab = '+ + window.BrowserOpenTab.toSource().replace( + 'openUILinkIn(', + 'gBrowser.treeStyleTab.onBeforeNewTabCommand(); $&' + ) + ); + } + + if ('undoCloseTab' in window) { + eval('window.undoCloseTab = '+ + window.undoCloseTab.toSource().replace( + /(\btab\s*=\s*[^\.]+\.undoCloseTab\([^;]+\);)/, + 'gBrowser.__treestyletab__readyToUndoCloseTab = true;\n' + + '$1\n' + + 'tab.__treestyletab__restoredByUndoCloseTab = true;\n' + + 'delete gBrowser.__treestyletab__readyToUndoCloseTab;' + ) + ); + } + + if ('XULBrowserWindow' in window && + 'hideChromeForLocation' in window.XULBrowserWindow) { + eval('XULBrowserWindow.hideChromeForLocation = '+ + XULBrowserWindow.hideChromeForLocation.toSource().replace( + '{', + '{ if (gBrowser.treeStyleTab.isVertical) return false;\n' + ) + ); + } + + let (functions = [ + 'window.duplicateTab.handleLinkClick', + 'window.duplicatethistab.handleLinkClick', + 'window.__treestyletab__highlander__origHandleLinkClick', + 'window.__splitbrowser__handleLinkClick', + 'window.__ctxextensions__handleLinkClick', + 'window.handleLinkClick' + ]) { + for (let i = 0, maxi = functions.length; i < maxi; i++) + { + let func = functions[i]; + let source = this._getFunctionSource(func); + if (!source || !/^\(?function handleLinkClick/.test(source)) + continue; + eval(func+' = '+source.replace( + /(charset\s*:\s*doc\.characterSet\s*)/, + '$1, event : event, linkNode : linkNode' + )); + break; + } + } + + this.overrideExtensionsPreInit(); // windowHelperHacks.js + }, + + onBeforeBrowserInit : function TSTWH_onBeforeBrowserInit() + { + this.overrideExtensionsBeforeBrowserInit(); // windowHelperHacks.js + this.overrideGlobalFunctions(); + }, + + onAfterBrowserInit : function TSTWH_onAfterBrowserInit() + { + this.overrideExtensionsAfterBrowserInit(); // windowHelperHacks.js + }, + + updateTabDNDObserver : function TSTWH_updateTabDNDObserver(aObserver) + { + var strip = this.service.getTabStrip(aObserver) || + gBrowser.mStrip // fallback to the default strip, for Tab Mix Plus; + + if ( + aObserver.tabContainer && + aObserver.tabContainer.tabbrowser == aObserver + ) + aObserver = aObserver.tabContainer; + + if ('_setEffectAllowedForDataTransfer' in aObserver) { + eval('aObserver._setEffectAllowedForDataTransfer = '+ + aObserver._setEffectAllowedForDataTransfer.toSource().replace( + '{', + '{ var TSTTabBrowser = this instanceof Ci.nsIDOMElement ? (this.tabbrowser || this) : gBrowser ; var TST = TSTTabBrowser.treeStyleTab;' + ).replace( + /\.screenX/g, '[TST.screenPositionProp]' + ).replace( + /\.width/g, '[TST.sizeProp]' + ).replace( + /(return (?:true|dt.effectAllowed = "copyMove");)/, + 'if (!TST.tabbarDNDObserver.canDropTab(arguments[0])) {\n' + + ' return dt.effectAllowed = "none";\n' + + '}\n' + + '$1' + ).replace( + 'sourceNode.parentNode == this &&', + '$& TST.getTabFromEvent(event) == sourceNode &&' + ) + ); + } + }, + + overrideGlobalFunctions : function TSTWH_overrideGlobalFunctions() + { + this.initToolbarItems(); + + eval('nsContextMenu.prototype.openLinkInTab = '+ + nsContextMenu.prototype.openLinkInTab.toSource().replace( + '{', + '{\n' + + ' TreeStyleTabService.handleNewTabFromCurrent(this.target.ownerDocument.defaultView);' + ) + ); + eval('nsContextMenu.prototype.openFrameInTab = '+ + nsContextMenu.prototype.openFrameInTab.toSource().replace( + '{', + '{\n' + + ' TreeStyleTabService.handleNewTabFromCurrent(this.target.ownerDocument.defaultView);' + ) + ); + var viewImageMethod = ('viewImage' in nsContextMenu.prototype) ? 'viewImage' : 'viewMedia' ; + eval('nsContextMenu.prototype.'+viewImageMethod+' = '+ + nsContextMenu.prototype[viewImageMethod].toSource().replace( + 'openUILink(', + 'TreeStyleTabService.onBeforeViewMedia(e, this.target.ownerDocument.defaultView); $&' + ) + ); + eval('nsContextMenu.prototype.viewBGImage = '+ + nsContextMenu.prototype.viewBGImage.toSource().replace( + 'openUILink(', + 'TreeStyleTabService.onBeforeViewMedia(e, this.target.ownerDocument.defaultView); $&' + ) + ); + eval('nsContextMenu.prototype.addDictionaries = '+ + nsContextMenu.prototype.addDictionaries.toSource().replace( + 'openUILinkIn(', + 'TreeStyleTabService.onBeforeOpenLink(where, this.target.ownerDocument.defaultView); $&' + ) + ); + + if ('BrowserSearch' in window && + '_loadSearch' in BrowserSearch) { + eval('BrowserSearch._loadSearch = '+ + BrowserSearch._loadSearch.toSource().replace( + 'openLinkIn(', + 'TreeStyleTabService.onBeforeBrowserSearch(arguments[0], useNewTab); $&' + ) + ); + } + + if ('openLinkIn' in window) { + // Bug 1050447 changed this line in Fx 34 to + // newTab = w.gBrowser.loadOneTab( + eval('window.openLinkIn = '+ + window.openLinkIn.toSource().replace( + /((b|newTab = w\.gB)rowser.loadOneTab\()/g, + 'TreeStyleTabService.onBeforeOpenLinkWithTab(gBrowser.selectedTab, aFromChrome); $1' + ) + ); + } + + let (functions = [ + 'window.permaTabs.utils.wrappedFunctions["window.contentAreaClick"]', + 'window.__contentAreaClick', + 'window.__ctxextensions__contentAreaClick', + 'window.contentAreaClick' + ]) { + for (let i = 0, maxi = functions.length; i < maxi; i++) + { + let func = functions[i]; + let source = this._getFunctionSource(func); + if (!source || !/^\(?function contentAreaClick/.test(source)) + continue; + eval(func+' = '+source.replace( + // for Tab Utilities, etc. Some addons insert openNewTabWith() to the function. + // (calls for the function is not included by Firefox default.) + /(openNewTabWith\()/g, + 'TreeStyleTabService.onBeforeOpenNewTabByThirdParty(event.target.ownerDocument.defaultView); $1' + )); + } + } + + if (window.duplicateTabIn) { + eval('window.duplicateTabIn = '+ + window.duplicateTabIn.toSource().replace( + '{', + '{ gBrowser.treeStyleTab.onBeforeTabDuplicate(aTab, where, delta); ' + ) + ); + } + + let (functions = [ + 'permaTabs.utils.wrappedFunctions["window.BrowserHomeClick"]', + 'window.BrowserHomeClick', + 'window.BrowserGoHome' + ]) { + for (let i = 0, maxi = functions.length; i < maxi; i++) + { + let func = functions[i]; + let source = this._getFunctionSource(func); + if (!source || !/^\(?function (BrowserHomeClick|BrowserGoHome)/.test(source)) + continue; + eval(func+' = '+source.replace( + 'gBrowser.loadTabs(', + 'TreeStyleTabService.readyToOpenNewTabGroup(gBrowser); $&' + )); + } + } + + eval('FeedHandler.loadFeed = '+ + FeedHandler.loadFeed.toSource().replace( + 'openUILink(', + 'TreeStyleTabService.onBeforeViewMedia(event, gBrowser); $&' + ) + ); + + eval('FullScreen.mouseoverToggle = '+ + FullScreen.mouseoverToggle.toSource().replace( + 'this._isChromeCollapsed = !aShow;', + 'gBrowser.treeStyleTab.updateFloatingTabbar(gBrowser.treeStyleTab.kTABBAR_UPDATE_BY_FULLSCREEN); $&' + ) + ); + eval('FullScreen.toggle = '+ + FullScreen.toggle.toSource().replace( + '{', + '{ gBrowser.treeStyleTab.onBeforeFullScreenToggle(); ' + ) + ); + + if ('PrintUtils' in window) { + eval('PrintUtils.printPreview = '+PrintUtils.printPreview.toSource().replace( + '{', + '{ TreeStyleTabService.onPrintPreviewEnter();' + )); + eval('PrintUtils.exitPrintPreview = '+PrintUtils.exitPrintPreview.toSource().replace( + '{', + '{ TreeStyleTabService.onPrintPreviewExit();' + )); + } + + if ('TabsOnTop' in window && TabsOnTop.syncUI) { + eval('TabsOnTop.syncUI = '+TabsOnTop.syncUI.toSource().replace( + /(\}\)?)$/, + 'gBrowser.treeStyleTab.onTabsOnTopSyncCommand(enabled); $&' + )); + } + + if ('toggleSidebar' in window) { + eval('window.toggleSidebar = '+ + window.toggleSidebar.toSource().replace( + '{', + '{ gBrowser.treeStyleTab.updateFloatingTabbar(gBrowser.treeStyleTab.kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR);' + ) + ); + } + }, + _splitFunctionNames : function TSTWH__splitFunctionNames(aString) + { + return String(aString) + .split(/\s+/) + .map(function(aString) { + return aString + .replace(/\/\*.*\*\//g, '') + .replace(/\/\/.+$/, '') + .trim(); + }); + }, + _getFunctionSource : function TSTWH__getFunctionSource(aFunc) + { + var func; + try { + eval('func = '+aFunc); + } + catch(e) { + return null; + } + return func ? func.toSource() : null ; + }, + + initToolbarItems : function TSTWH_initToolbarItems() + { + var searchbar = document.getElementById('searchbar'); + if (searchbar && + searchbar.doSearch && + searchbar.doSearch.toSource().toSource().indexOf('TreeStyleTabService') < 0) { + eval('searchbar.doSearch = '+searchbar.doSearch.toSource().replace( + /(openUILinkIn\(.+?\);)/, + 'TreeStyleTabService.onBeforeBrowserSearch(arguments[0]);\n' + + '$1\n' + + 'TreeStyleTabService.stopToOpenChildTab();' + )); + } + + var goButton = document.getElementById('urlbar-go-button'); + if (goButton) + goButton.parentNode.addEventListener('click', this.service, true); + + var tabbar = this.service.getTabStrip(this.service.browser); + tabbar.addEventListener('click', this.service, true); + + var newTabButton = document.getElementById('new-tab-button'); + const nsIDOMNode = Ci.nsIDOMNode; + if (newTabButton && + !(tabbar.compareDocumentPosition(newTabButton) & nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY)) + newTabButton.parentNode.addEventListener('click', this.service, true); + + this.service.updateAllTabsButton(gBrowser); + }, + + destroyToolbarItems : function TSTWH_destroyToolbarItems() + { + var goButton = document.getElementById('urlbar-go-button'); + if (goButton) + goButton.parentNode.removeEventListener('click', this, true); + + var tabbar = this.service.getTabStrip(this.service.browser); + tabbar.removeEventListener('click', this.service, true); + + var newTabButton = document.getElementById('new-tab-button'); + const nsIDOMNode = Ci.nsIDOMNode; + if (newTabButton && + !(tabbar.compareDocumentPosition(newTabButton) & Ci.nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY)) + newTabButton.parentNode.removeEventListener('click', this.service, true); + + var allTabsButton = document.getElementById('alltabs-button'); + if (allTabsButton && allTabsButton.hasChildNodes()) + allTabsButton.firstChild.setAttribute('position', 'after_end'); + }, + + initTabbrowserMethods : function TSTWH_initTabbrowserMethods(aTabBrowser) + { + var b = aTabBrowser; + + let (source = b.moveTabForward.toSource()) { + eval('b.moveTabForward = '+ + source.replace( + 'if (nextTab)', + '(function() {\n' + + ' if (this.treeStyleTab.hasChildTabs(this.mCurrentTab)) {\n' + + ' let descendant = this.treeStyleTab.getDescendantTabs(this.mCurrentTab);\n' + + ' if (descendant.length)\n' + + ' nextTab = this.treeStyleTab.getNextTab(descendant[descendant.length-1]);\n' + + ' }\n' + + '}).call(this);' + + '$&' + ).replace( + /(this.moveTabTo\([^;]+\);)/, + '(function() {\n' + + ' let descendant = this.treeStyleTab.getDescendantTabs(nextTab);\n' + + ' if (descendant.length) {\n' + + ' nextTab = descendant[descendant.length-1];\n' + + ' }\n' + + ' $1\n' + + '}).call(this);' + ).replace( + 'this.moveTabToStart();', + '(function() {\n' + + ' this.treeStyleTab.internallyTabMovingCount++;\n' + + ' let parentTab = this.treeStyleTab.getParentTab(this.mCurrentTab);\n' + + ' if (parentTab) {\n' + + ' this.moveTabTo(this.mCurrentTab, this.treeStyleTab.getFirstChildTab(parentTab)._tPos);\n' + + ' this.mCurrentTab.focus();\n' + + ' }\n' + + ' else {\n' + + ' $&\n' + + ' }\n' + + ' this.treeStyleTab.internallyTabMovingCount--;\n' + + '}).call(this);' + ) + ); + } + + let (source = b.moveTabBackward.toSource()) { + eval('b.moveTabBackward = '+ + source.replace( + 'this.moveTabToEnd();', + '(function() {\n' + + ' this.treeStyleTab.internallyTabMovingCount++;\n' + + ' let parentTab = this.treeStyleTab.getParentTab(this.mCurrentTab);\n' + + ' if (parentTab) {\n' + + ' this.moveTabTo(this.mCurrentTab, this.treeStyleTab.getLastChildTab(parentTab)._tPos);\n' + + ' this.mCurrentTab.focus();\n' + + ' }\n' + + ' else {\n' + + ' $&\n' + + ' }\n' + + ' this.treeStyleTab.internallyTabMovingCount--;\n' + + '}).call(this);' + ) + ); + } + + eval('b.loadTabs = '+ + b.loadTabs.toSource().replace( + 'var tabNum = ', + 'if (this.treeStyleTab.readiedToAttachNewTabGroup)\n' + + ' TreeStyleTabService.readyToOpenChildTab(firstTabAdded || this.selectedTab, true);\n' + + '$&' + ).replace( + 'if (!aLoadInBackground)', + 'if (TreeStyleTabService.checkToOpenChildTab(this))\n' + + ' TreeStyleTabService.stopToOpenChildTab(this);\n' + + '$&' + ).replace( + 'this.selectedTab = firstTabAdded;', + 'this.selectedTab = aURIs[0].indexOf("about:treestyletab-group") < 0 ? \n' + + ' firstTabAdded :\n' + + ' TreeStyleTabService.getNextTab(firstTabAdded) ;' + ) + ); + + if ('_beginRemoveTab' in b) { + eval('b._beginRemoveTab = '+ + b._beginRemoveTab.toSource().replace( + 'if (this.tabs.length - this._removingTabs.length == 1) {', + 'if (this.tabs.length - this._removingTabs.length == 1 || this.treeStyleTab.shouldCloseLastTabSubtreeOf(aTab)) {' + ).replace( + 'this._removingTabs.length == 0', + '(this.treeStyleTab.shouldCloseLastTabSubtreeOf(aTab) || $&)' + ) + ); + } + + eval('b.removeCurrentTab = '+b.removeCurrentTab.toSource().replace( + '{', + '{ if (!this.treeStyleTab.warnAboutClosingTabSubtreeOf(this.selectedTab)) return;' + )); + }, + + initTabbarMethods : function TSTWH_initTabbarMethods(aTabBrowser) + { + var b = aTabBrowser; + + var source = b.mTabContainer.advanceSelectedTab.toSource(); + if (source.indexOf('treeStyleTab.handleAdvanceSelectedTab') < 0) { + eval('b.mTabContainer.advanceSelectedTab = '+ + source.replace( + '{', + '{\n' + + ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + + ' if (treeStyleTab.handleAdvanceSelectedTab(arguments[0], arguments[1]))\n' + + ' return;' + ) + ); + } + + source = b.mTabContainer._notifyBackgroundTab.toSource(); + if (source.indexOf('TreeStyleTabService.getTabBrowserFromChild') < 0) { + eval('b.mTabContainer._notifyBackgroundTab = '+ + source.replace( + '{', + '{\n' + + ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + + ' if (treeStyleTab.scrollToNewTabMode == 0 ||\n' + + ' treeStyleTab.shouldCancelEnsureElementIsVisible())\n' + + ' return;' + ).replace( + /\.screenX/g, '[treeStyleTab.screenPositionProp]' + ).replace( + /\.width/g, '[treeStyleTab.sizeProp]' + ).replace( + /\.left/g, '[treeStyleTab.startProp]' + ).replace( + /\.right/g, '[treeStyleTab.endProp]' + + // replace such codes: + // tab = {left: tab.left, right: tab.right}; + ).replace( + /left\s*:/g, 'start:' + ).replace( + /right\s*:/g, 'end:' + ).replace( + /((tab|selected)\s*=\s*\{\s*start:[^\}]+\})/g, + '$1; $2[treeStyleTab.startProp] = $2.start; $2[treeStyleTab.endProp] = $2.end;' + + ).replace( + '!selected ||', + '$& treeStyleTab.scrollToNewTabMode == 1 && ' + ).replace( + /(\}\)?)$/, + 'treeStyleTab.notifyBackgroundTab(); $1' + ) + ); + } + + if (b.tabContainer && '_getDropIndex' in b.tabContainer) { + eval('b.tabContainer._getDropIndex = '+ + b.tabContainer._getDropIndex.toSource().replace( + /\.screenX/g, '[this.treeStyleTab.screenPositionProp]' + ).replace( + /\.width/g, '[this.treeStyleTab.sizeProp]' + ) + ); + } + + /** + * The default implementation fails to scroll to tab if it is expanding. + * So we have to inject codes to override its effect. + */ + let (scrollbox = aTabBrowser.treeStyleTab.scrollBox) { + let source = scrollbox.ensureElementIsVisible.toSource(); + if ( + source.indexOf('treeStyleTab') < 0 && // not updated yet + source.indexOf('ensureTabIsVisible') < 0 // not replaced by Tab Mix Plus + ) { + eval('scrollbox.ensureElementIsVisible = '+ + source.replace( + '{', + '{\n' + + ' var treeStyleTab = TreeStyleTabService.getTabBrowserFromChild(this).treeStyleTab;\n' + + ' if (treeStyleTab && treeStyleTab.shouldCancelEnsureElementIsVisible())\n' + + ' return;\n' + + ' if (\n' + + ' treeStyleTab &&\n' + + ' (arguments.length == 1 || arguments[1])\n' + + ' )\n' + + ' return treeStyleTab.scrollToTab(arguments[0]);' + ) + ); + } + } + + let (popup = document.getElementById('alltabs-popup')) { + if (popup && '_updateTabsVisibilityStatus' in popup) { + eval('popup._updateTabsVisibilityStatus = '+ + popup._updateTabsVisibilityStatus.toSource().replace( + '{', + '{ var treeStyleTab = gBrowser.treeStyleTab;' + ).replace( + /\.screenX/g, '[treeStyleTab.screenPositionProp]' + ).replace( + /\.width/g, '[treeStyleTab.sizeProp]' + ) + ); + } + } + + } + +}; + diff --git a/content/treestyletab/windowHelperHacks.js b/content/treestyletab/windowHelperHacks.js index 2fddc7ca..d1e7ae85 100644 --- a/content/treestyletab/windowHelperHacks.js +++ b/content/treestyletab/windowHelperHacks.js @@ -1,1484 +1,1484 @@ -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); -XPCOMUtils.defineLazyModuleGetter(this, - 'Services', 'resource://gre/modules/Services.jsm'); -XPCOMUtils.defineLazyModuleGetter(this, - 'TreeStyleTabUtils', 'resource://treestyletab-modules/utils.js'); - -TreeStyleTabWindowHelper.extraProperties = [ - TreeStyleTabService.kID, - TreeStyleTabService.kCOLLAPSED, - TreeStyleTabService.kSUBTREE_COLLAPSED, - TreeStyleTabService.kCHILDREN, - TreeStyleTabService.kPARENT, - TreeStyleTabService.kANCESTOR, - TreeStyleTabService.kINSERT_BEFORE, - TreeStyleTabService.kINSERT_AFTER -]; - -TreeStyleTabWindowHelper.overrideExtensionsPreInit = function TSTWH_overrideExtensionsPreInit() { - var sv = this.service; - - // Highlander - // https://addons.mozilla.org/firefox/addon/4086 - if ('Highlander' in window && - TreeStyleTabUtils.getTreePref('compatibility.Highlander')) { - eval('Highlander.overrideHandleLinkClick = '+ - Highlander.overrideHandleLinkClick.toSource().replace( - /(var )?origHandleLinkClick/g, - 'window.__treestyletab__highlander__origHandleLinkClick' - ) - ); - } - - // PermaTabs - // https://addons.mozilla.org/firefox/addon/2558 - // PermaTabs Mod - // https://addons.mozilla.org/firefox/addon/7816 - if ('permaTabs' in window && - TreeStyleTabUtils.getTreePref('compatibility.PermaTabs')) { - if ('__init' in permaTabs) { - // without delay, Firefox crashes on startup. - eval('permaTabs.__init = '+ - permaTabs.__init.toSource().replace( - 'aTab.setAttribute(\\"image\\", ', - 'window.setTimeout(function(aTab, aImage) { aTab.setAttribute(\\"image\\", aImage); }, 100, aTab, ' - ) - ); - } - if ('showPermaTab' in permaTabs) { - eval('permaTabs.showPermaTab = '+ - permaTabs.showPermaTab.toSource().replace( - /(\}\)?)$/, - '(function(tab, id) {\n' + - ' if (this.ssWillRestore) return;\n' + - ' var TST = TreeStyleTabService;\n' + - ' if (this.TSTRestoredPermaTabsInfo === void(0)) {\n' + - ' try {\n' + - ' eval("this.TSTRestoredPermaTabsInfo = "+(TreeStyleTabUtils.getTreePref("permaTabsInfo") || "null"));\n' + - ' }\n' + - ' catch(e) {\n' + - ' }\n' + - ' }\n' + - ' if (!this.TSTRestoredPermaTabsInfo) return;\n' + - - ' var info = this.TSTRestoredPermaTabsInfo[id];\n' + - ' if (!info) return;\n' + - - ' for (var i in info)\n' + - ' {\n' + - ' TST.SessionStore.setTabValue(tab, i, String(info[i]));\n' + - ' }\n' + - ' var count = 0;\n' + - ' window.setTimeout(function onTimeout() {\n' + - ' var b = TST.getTabBrowserFromChild(tab);\n' + - ' if (!b.treeStyleTab) {\n' + - ' if (++count < 50)\n' + - ' window.setTimeout(onTimeout, 100);\n' + - ' return;\n' + - ' }\n' + - ' b.treeStyleTab.handleRestoredTab(tab);\n' + - ' }, 0);\n' + - '}).call(this, tab, id)\n' + - '$1' - ) - ); - } - if ('savePermaTabs' in permaTabs) { - eval('permaTabs.savePermaTabs = '+ - permaTabs.savePermaTabs.toSource().replace( - '{', - '{\n' + - '(function() {\n' + - ' var tabsInfo = {};\n' + - ' var TST = TreeStyleTabService;\n' + - ' var allTabs = getBrowser().mTabContainer.childNodes;\n' + - ' for (let i = 0, maxi = allTabs.length; i < maxi; i++)\n' + - ' {\n' + - ' let tab = allTabs[i];\n' + - ' let index = this.getPermaTabLocalIndex(tab);\n' + - ' if (index < 0) continue;\n' + - ' let info = {};\n' + - ' for (let i = 0, maxi = TST.extraProperties.length; i < maxi; i++)\n' + - ' {\n' + - ' let property = TST.extraProperties[i];\n' + - ' info[property] = TST.getTabValue(tab, property);\n' + - ' }\n' + - ' tabsInfo[this.permaTabs[index].id] = info;\n' + - ' }\n' + - ' TreeStyleTabUtils.setTreePref("permaTabsInfo", tabsInfo.toSource());\n' + - '}).call(this);' - ) - ); - } - } - - // Tab Mix Plus - if (TreeStyleTabUtils.getTreePref('compatibility.TMP')) { - document.documentElement.setAttribute('treestyletab-enable-compatibility-tmp', true); - } - // Tab Mix Plus, SessionStore API - if ( - TreeStyleTabUtils.getTreePref('compatibility.TMP') && - ('TabmixSessionData' in window || 'SessionData' in window) - ) { - let sessionData = window.TabmixSessionData || window.SessionData; - if ('getTabProperties' in sessionData && 'setTabProperties' in sessionData) { - let prefix = sv.kTMP_SESSION_DATA_PREFIX; - let sessionManager = window.TabmixSessionManager || window.SessionManager; - sessionData.tabTSTProperties = this.extraProperties.map(function(aProperty) { - return prefix+aProperty; - }); - eval('sessionData.getTabProperties = '+ - sessionData.getTabProperties.toSource().replace( - 'return tabProperties;', - ' for (let i = 0, maxi = this.tabTSTProperties.length; i < maxi; i++)\n' + - ' {\n' + - ' let property = this.tabTSTProperties[i];\n' + - ' tabProperties += "|" + property + "=" + encodeURIComponent(aTab.getAttribute(property));\n' + - ' }\n' + - '$&' - ) - ); - eval('sessionData.setTabProperties = '+ - sessionData.setTabProperties.toSource().replace( - '{', - '$&\n' + - ' var TSTProps = tabProperties.split("|");\n' + - ' tabProperties = TSTProps.shift();\n' + - ' for (let i = 0, maxi = TSTProps.length; i < maxi; i++)\n' + - ' {\n' + - ' let property = TSTProps[i];\n' + - ' let index = property.indexOf("=");\n' + - ' let name = property.substring(0, index);\n' + - ' let value = decodeURIComponent(property.substring(index+1));\n' + - ' if (name && value)\n' + - ' aTab.setAttribute(name, value);\n' + - ' }' - ) - ); - eval('sessionManager.loadOneTab = '+ - sessionManager.loadOneTab.toSource().replace( - /(\}\))?$/, - ' if (gBrowser.treeStyleTab.useTMPSessionAPI)\n' + - ' gBrowser.treeStyleTab.handleRestoredTab(aTab);\n' + - '$1' - ) - ); - let source = tablib.init.toSource().split('gBrowser.restoreTab = '); - source[1] = source[1].replace( - 'return newTab;', - ' if (this.treeStyleTab.useTMPSessionAPI)\n' + - ' this.treeStyleTab.handleRestoredTab(newTab);\n' + - '$&' - ); - eval('tablib.init = '+source.join('gBrowser.restoreTab = ')); - eval('sessionManager.loadOneWindow = '+ - sessionManager.loadOneWindow.toSource().replace( - 'gBrowser.tabsToLoad = ', - ' gBrowser.treeStyleTab.resetAllTabs(true, true);\n' + - ' TreeStyleTabService.restoringTree = true;\n' + - '$&' - ).replace( - /(\}\))?$/, - 'TreeStyleTabService.restoringTree = false; $1' - ) - ); - sv.useTMPSessionAPI = true; - } - } - - // Session Manager - // https://addons.mozilla.org/firefox/addon/2324 - // We need to initialize TST before Session Manager restores the last session anyway! - if ('gSessionManager' in window && - TreeStyleTabUtils.getTreePref('compatibility.SessionManager')) { - if ('onLoad_proxy' in gSessionManager && - 'onLoad' in gSessionManager) { - eval('gSessionManager.onLoad = '+gSessionManager.onLoad.toSource().replace( - '{', - '{ TreeStyleTabService.init();' - )); - } - if ('load' in gSessionManager) { - eval('gSessionManager.load = '+gSessionManager.load.toSource().replace( - 'var tabcount = ', - ' gBrowser.treeStyleTab.collapseExpandAllSubtree(false, true);\n' + - ' let (tabs = gBrowser.treeStyleTab.getTabs(gBrowser).slice(1).reverse()) {\n' + - ' for (let i = 0, maxi = tabs.length; i < maxi; i++)\n' + - ' {\n' + - ' let tab = tabs[i];\n' + - ' gBrowser.removeTab(tab);\n' + - ' }\n' + - ' }\n' + - ' TreeStyleTabService.restoringTree = true;\n' + - '$&' - )); - } - } - - // FullerScreen - // https://addons.mozilla.org/firefox/addon/4650 - if ('FS_onFullerScreen' in window && - TreeStyleTabUtils.getTreePref('compatibility.FullerScreen')) { - let (functions = 'CheckIfFullScreen,FS_onFullerScreen,FS_onMouseMove'.split(',')) { - for (let i = 0, maxi = functions.length; i < maxi; i++) - { - let func = functions[i]; - if (!(func in window)) continue; - eval('window.'+func+' = '+window[func].toSource().replace( - /FS_data.mTabs.(removeAttribute\("moz-collapsed"\)|setAttribute\("moz-collapsed", "true"\));/g, - 'if (gBrowser.treeStyleTab.currentTabbarPosition == "top") { $& }' - )); - } - } - } - - // TooManyTabs - // https://addons.mozilla.org/firefox/addon/9429 - if ('tooManyTabs' in window && - TreeStyleTabUtils.getTreePref('compatibility.TooManyTabs')) { - sv.registerExpandTwistyAreaBlocker('tooManyTabs'); - } - - // DragNDrop Toolbars - // https://addons.mozilla.org/firefox/addon/dragndrop-toolbars/ - if ('globDndtb' in window && - globDndtb.setTheStuff && - TreeStyleTabUtils.getTreePref('compatibility.DragNDropToolbars')) { - let reinitTabbar = function() { - TreeStyleTabService.stopRendering(); - gBrowser.treeStyleTab.syncDestroyTabbar(); - window.setTimeout(function() { - gBrowser.treeStyleTab.syncReinitTabbar(); - TreeStyleTabService.startRendering(); - }, 100); - }; - globDndtb.__treestyletab__setOrder = globDndtb.setOrder; - globDndtb.setOrder = function() { - reinitTabbar(); - return this.__treestyletab__setOrder.apply(this, arguments); - }; - globDndtb.__treestyletab__setTheStuff = globDndtb.setTheStuff; - globDndtb.setTheStuff = function() { - var result = this.__treestyletab__setTheStuff.apply(this, arguments); - if (this.dndObserver && - this.dndObserver.onDrop && - !this.dndObserver.__treestyletab__onDrop) { - this.dndObserver.__treestyletab__onDrop = this.dndObserver.onDrop; - this.dndObserver.onDrop = function(aEvent, aDropData, aSession) { - if (document.getElementById(aDropData.data) == gBrowser.treeStyleTab.tabStrip) { - reinitTabbar(); - } - return this.__treestyletab__onDrop.apply(this, arguments); - }; - } - return result; - }; - } - - // Optimoz Tweaks - // http://optimoz.mozdev.org/tweaks/ - // https://addons.mozilla.org/firefox/addon/optimoz-tweaks-ja-version/ - if ('mtSidebarStartup' in window && - 'mtSidebarShutdown' in window && - 'mtPreventHiding' in window && - TreeStyleTabUtils.getTreePref('compatibility.OptimozTweaks')) { - eval('window.mtSidebarStartup = '+window.mtSidebarStartup.toSource().replace( - '{', - '{\n' + - ' document.getElementById("TabsToolbar")\n' + - ' .addEventListener("mousemove", mtMouseMoveListener, false);' - )); - eval('window.mtSidebarShutdown = '+window.mtSidebarShutdown.toSource().replace( - '{', - '{\n' + - ' document.getElementById("TabsToolbar")\n' + - ' .removeEventListener("mousemove", mtMouseMoveListener, false);' - )); - eval('window.mtPreventHiding = '+window.mtPreventHiding.toSource().replace( - '{', - '{\n' + - ' if (TreeStyleTabService.getTabbarFromEvent(arguments[0]))\n' + - ' return;' - )); - } - - /** - * Hide Caption Titlebar Plus (Smart) - * https://addons.mozilla.org/firefox/addon/hide-caption-titlebar-plus-sma/ - */ - if ('HideCaption' in window && - 'do_alter' in HideCaption) { - eval('HideCaption.do_alter = '+HideCaption.do_alter.toSource().replace( - 'if (!theSettings) {', - ' if (!theSettings ||\n' + - ' gBrowser.treeStyleTab.isVertical) {' - )); - } - - // Greasemonkey - // https://addons.mozilla.org/firefox/addon/748 - if (TreeStyleTabUtils.getTreePref('compatibility.Greasemonkey')) { - try { - let hitchModule = Components.utils.import('resource://greasemonkey/util/hitch.js', {}); - let hitch = hitchModule.hitch; - if (hitch.toSource().indexOf('TreeStyleTabService') < 0) { - let ns = {}; - Components.utils.import('resource://greasemonkey/third-party/getChromeWinForContentWin.js', ns); - let getChromeWinForContentWin = ns.getChromeWinForContentWin; - hitchModule.hitch = function(aObject, aMethod) { - if (typeof aMethod == 'function' && - aMethod.toSource().indexOf('function openInTab') > -1) { - let originalOpenInTab = aMethod; - /** - * This function must be replaced on scripts in "chrome:" URL, like this. - * Otherwise the original openInTab() will raise violation error. - * Don't move this hack into JS code modules with "resource:" URL. - */ - aMethod = function openInTab(aSafeContentWindow, aURL, aLoadInBackgtound) { - let chrome = getChromeWinForContentWin(aSafeContentWindow); - if (chrome && chrome.TreeStyleTabService) - chrome.TreeStyleTabService.readyToOpenChildTabNow(aSafeContentWindow); - return originalOpenInTab.apply(this, arguments); - }; - } - return hitch.apply(this, arguments); - }; - Components.utils.import('resource://greasemonkey/util.js', ns); - if (ns.GM_util) - ns.GM_util.hitch = hitchModule.hitch; - } - } - catch(e) { - dump('Tree Style Tab: failed to patch to Greasemonkey.\n'); - dump(e+'\n'); - } - } -}; - -TreeStyleTabWindowHelper.overrideExtensionsBeforeBrowserInit = function TSTWH_overrideExtensionsBeforeBrowserInit() { - var sv = this.service; - - // Tab Mix Plus - if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && - 'TMP_LastTab' in window) { - TMP_LastTab.TabBar = gBrowser.mTabContainer; - } - if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && - 'isTabVisible' in gBrowser.mTabContainer && - 'ensureTabIsVisible' in gBrowser.mTabContainer) { - let replaceHorizontalProps = function replaceHorizontalProps(aString) - { - return aString.replace( - /boxObject\.x/g, - 'boxObject[posProp]' - ).replace( - /boxObject\.screenX/g, - 'boxObject[screenPosProp]' - ).replace( - /boxObject\.width/g, - 'boxObject[sizeProp]' - ).replace( - '{', - '{\n' + - ' var posProp = gBrowser.treeStyleTab.isVertical ? "y" : "x" ;\n' + - ' var screenPosProp = gBrowser.treeStyleTab.isVertical ? "screenY" : "screenX" ;\n' + - ' var sizeProp = gBrowser.treeStyleTab.isVertical ? "height" : "width" ;' - ) - } - eval('gBrowser.mTabContainer.ensureTabIsVisible = '+ - replaceHorizontalProps(gBrowser.mTabContainer.ensureTabIsVisible.toSource().replace( - 'boxObject.width < 250', - '$& || gBrowser.treeStyleTab.isVertical' - )) - ); - eval('gBrowser.mTabContainer.isTabVisible = '+ - replaceHorizontalProps(gBrowser.mTabContainer.isTabVisible.toSource()) - ); - } - - // Tabberwocky - // https://addons.mozilla.org/firefox/addon/14439 - if ('tabberwocky' in window && - TreeStyleTabUtils.getTreePref('compatibility.Tabberwocky')) { - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case 'TreeStyleTabTabbarPositionChanged': - var b = aEvent.originalTarget; - if (b.treeStyleTab.isVertical) - TreeStyleTabUtils.prefs.setPref('tabberwocky.multirow', false); - break; - - case 'unload': - document.removeEventListener('TreeStyleTabTabbarPositionChanged', this, false); - document.removeEventListener('unload', this, false); - break; - } - } - }; - document.addEventListener('TreeStyleTabTabbarPositionChanged', listener, false); - document.addEventListener('unload', listener, false); - - if ('openSelectedLinks' in tabberwocky) { - eval('tabberwocky.openSelectedLinks = '+ - tabberwocky.openSelectedLinks.toSource().replace( - 'links.forEach(', - ' TreeStyleTabService.readyToOpenChildTab(aFrame, true)\n' + - '$&' - ).replace( - /(\}\)?)$/, - ' TreeStyleTabService.stopToOpenChildTab(aFrame)\n' + - '$1' - ) - ); - } - } -}; - -TreeStyleTabWindowHelper.overrideExtensionsAfterBrowserInit = function TSTWH_overrideExtensionsAfterBrowserInit() { - var sv = this.service; - - // Selection Links - // https://addons.mozilla.org/firefox/addon/8644 - if ('selectionlinks' in window && - 'parseSelection' in selectionlinks && - TreeStyleTabUtils.getTreePref('compatibility.SelectionLinks')) { - eval('selectionlinks.parseSelection = '+ - selectionlinks.parseSelection.toSource().replace( - /((?:[^\s:;]+.selectedTab\s*=\s*)?([^\s:;]+).addTab\()/g, - ' if ($2.treeStyleTab)\n' + - ' $2.treeStyleTab.readyToOpenChildTab(focusedWindow);\n' + - '$1' - ) - ); - } - - - // Tab Mix Plus - if ( - TreeStyleTabUtils.getTreePref('compatibility.TMP') && - 'TabmixTabbar' in window - ) { - let DNDObserver = 'TMP_tabDNDObserver' in window ? TMP_tabDNDObserver : TabDNDObserver ; - this.updateTabDNDObserver(DNDObserver); - eval('DNDObserver.clearDragmark = '+ - DNDObserver.clearDragmark.toSource().replace( - /(\})(\))?$/, - 'gBrowser.treeStyleTab.tabbarDNDObserver.clearDropPosition(); $1$2' - ) - ); - eval('DNDObserver.onDragStart = '+ - DNDObserver.onDragStart.toSource().replace( - 'event.target.localName != "tab"', - ' gBrowser.treeStyleTab.tabbarDNDObserver.canDragTabbar(event) ||\n' + - ' $&' - ) - ); - - eval('window.TMP_howToOpen = '+ - window.TMP_howToOpen.toSource().replace( - /(window.openNewTabWith\()/g, - 'TreeStyleTabService.readyToOpenChildTab(event.target.ownerDocument.defaultView); $1' - ) - ); - - if ('TabmixContext' in window && - typeof TabmixContext.openMultipleLinks == 'function') { - eval('TabmixContext.openMultipleLinks = '+ - TabmixContext.openMultipleLinks.toSource().replace( - /(TMP_loadTabs\([^\)]+\);)/g, - 'TreeStyleTabService.readyToOpenChildTab(gBrowser, true); $1 TreeStyleTabService.stopToOpenChildTab(gBrowser);' - ) - ); - } - - - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED: - TabmixTabbar.updateScrollStatus(); - break; - - case sv.kEVENT_TYPE_FOCUS_NEXT_TAB: - let mode = TreeStyleTabUtils.prefs.getPref('extensions.tabmix.focusTab'); - if (mode != 2 && mode != 5) - aEvent.preventDefault(); - break; - - case 'unload': - document.removeEventListener(sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); - document.removeEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); - document.removeEventListener('unload', this, false); - break; - } - } - }; - document.addEventListener(sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, listener, false); - document.addEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); - document.addEventListener('unload', listener, false); - - gBrowser.treeStyleTab.internallyTabMovingCount++; // until "TMmoveTabTo" method is overwritten - } - - - // Super DragAndGo - // https://addons.mozilla.org/firefox/addon/137 - if ('superDrag' in window && - TreeStyleTabUtils.getTreePref('compatibility.SuperDragAndGo')) { - eval('superDrag.onDrop = '+ - superDrag.onDrop.toSource().replace( - /(var newTab = getBrowser\(\).addTab\([^\)]+\);)/g, - ' if (aDragSession.sourceNode &&\n' + - ' aDragSession.sourceNode.ownerDocument.defaultView.top == getBrowser().contentWindow)\n' + - ' TreeStyleTabService.readyToOpenChildTab(getBrowser());\n' + - ' $1' - ) - ); - } - - // Drag de Go - // https://addons.mozilla.org/firefox/addon/2918 - if ('ddg_ges' in window && - TreeStyleTabUtils.getTreePref('compatibility.DragDeGo')) { - eval('ddg_ges.Open = '+ - ddg_ges.Open.toSource().replace( - 'if (mode[1] == "h" || mode[1] == "f") {', - '$&\n' + - ' if ("sourceNode" in aData) // only for dragging from the content tarea.\n' + - ' TreeStyleTabService.readyToOpenChildTab(getBrowser());' - ) - ); - eval('ddg_ges.Search = '+ - ddg_ges.Search.toSource().replace( - 'if (mode[1] == "h" || mode[1] == "f") {', - '$&\n' + - ' TreeStyleTabService.readyToOpenChildTab(getBrowser());' - ) - ); - } - - // DragIt - // https://addons.mozilla.org/firefox/addon/dragit-formerly-drag-de-go/ - if ('DragIt' in window && - DragIt.tab && - DragIt.tab.open && - TreeStyleTabUtils.getTreePref('compatibility.DragIt')) { - eval('DragIt.tab.open = '+ - DragIt.tab.open.toSource().replace( - 'try {', - 'try { TreeStyleTabService.readyToOpenChildTabNow(gBrowser);' - ) - ); - } - - // Colorful Tabs - // https://addons.mozilla.org/firefox/addon/1368 - if ('colorfulTabs' in window && - TreeStyleTabUtils.getTreePref('compatibility.ColorfulTabs')) { - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case 'TabOpen': - case 'TreeStyleTabAttached': - case 'TreeStyleTabParted': - var child = aEvent.originalTarget; - var parent = aEvent.parentTab; - if (child && parent) { - let color = TreeStyleTabService.SessionStore.getTabValue(parent, 'tabClr'); - if (/^\d+,\d+,\d+$/.test(color)) - color = 'rgb('+color+')'; - window.setTimeout(function() { - colorfulTabs.setColor(child, color); - }, 0); - } - else if (child) { - TreeStyleTabService.SessionStore.setTabValue(child, 'tabClr', ''); - colorfulTabs.calcTabClr({ - target : child, - originalTarget : child, - }); - } - break; - - case 'unload': - document.removeEventListener('TabOpen', this, false); - document.removeEventListener('TreeStyleTabAttached', this, false); - document.removeEventListener('TreeStyleTabParted', this, false); - document.removeEventListener('unload', this, false); - break; - } - } - }; - eval('colorfulTabs.show_ctStack = '+ - colorfulTabs.show_ctStack.toSource().replace( - '.setProperty("display", "-moz-stack", "important")', - '.display = ""' - ) - ); - document.addEventListener('TabOpen', listener, false); - document.addEventListener('TreeStyleTabAttached', listener, false); - document.addEventListener('TreeStyleTabParted', listener, false); - document.addEventListener('unload', listener, false); - } - - // FLST (Focus Last Selected Tab) - // https://addons.mozilla.org/firefox/addon/32 - if ('flst' in window && - TreeStyleTabUtils.getTreePref('compatibility.FLST')) { - TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { - return !TreeStyleTabUtils.prefs.getPref('extensions.flst.enabled'); - }); - } - - // Focus Last Selected Tab 0.9.5.x - // http://www.gozer.org/mozilla/extensions/ - if (TreeStyleTabUtils.getTreePref('compatibility.FocusLastSelectedTab')) { - sv.extensions.isAvailable('focuslastselectedtab@gozer.org', { ok : function() { - TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { - return !aTabBrowser.selectedTab.hasAttribute('lastselected'); - }); - }}); - } - - // LastTab - // https://addons.mozilla.org/firefox/addon/112 - if ('LastTab' in window && - TreeStyleTabUtils.getTreePref('compatibility.LastTab')) { - TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { - return !TreeStyleTabUtils.prefs.getPref('extensions.lasttab.focusLastTabOnClose'); - }); - } - - // FireGestures - // https://addons.mozilla.org/firefox/addon/6366 - if ('FireGestures' in window && - TreeStyleTabUtils.getTreePref('compatibility.FireGestures')) { - eval('FireGestures.onExtraGesture = '+ - FireGestures.onExtraGesture.toSource().replace( - 'case "keypress-stop":', - '$&\n' + - ' TreeStyleTabService.readyToOpenChildTab(gBrowser, true);' - ).replace( - 'break;case "gesture-timeout":', - ' TreeStyleTabService.stopToOpenChildTab(gBrowser);\n' + - '$&' - ) - ); - eval('FireGestures._performAction = '+ - FireGestures._performAction.toSource().replace( - 'gBrowser.loadOneTab(', - ' TreeStyleTabService.readyToOpenChildTab(gBrowser);\n' + - '$&' - ) - ); - eval('FireGestures.openURLsInSelection = '+ - FireGestures.openURLsInSelection.toSource().replace( - 'var tab =', - ' if (!TreeStyleTabService.checkToOpenChildTab(gBrowser))\n' + - ' TreeStyleTabService.readyToOpenChildTab(gBrowser, true);\n' + - '$&' - ).replace( - 'if (!flag)', - ' if (TreeStyleTabService.checkToOpenChildTab(gBrowser))\n' + - ' TreeStyleTabService.stopToOpenChildTab(gBrowser);\n' + - '$&' - ) - ); - eval('FireGestures.handleEvent = '+ - FireGestures.handleEvent.toSource().replace( - 'gBrowser.loadOneTab(', - ' TreeStyleTabService.readyToOpenChildTab(gBrowser);\n' + - '$&' - ) - ); - } - - // Mouse Gestures Redox - // http://www.mousegestures.org/ - if ('mgBuiltInFunctions' in window && - 'mgLinkInTab' in mgBuiltInFunctions && - TreeStyleTabUtils.getTreePref('compatibility.MouseGesturesRedox')) { - eval('mgBuiltInFunctions.mgLinkInTab = '+ - mgBuiltInFunctions.mgLinkInTab.toSource().replace( - 'var tab', - 'TreeStyleTabService.readyToOpenChildTab(gBrowser); $&' - ) - ); - } - - // SBM Counter - // http://miniturbo.org/products/sbmcounter/ - if ('SBMCounter' in window && - TreeStyleTabUtils.getTreePref('compatibility.SBMCounter')) { - eval('SBMCounter.action = '+ - SBMCounter.action.toSource().replace( - 'gBrowser.selectedTab = gBrowser.addTab', - 'TreeStyleTabService.readyToOpenChildTab(gBrowser); $&' - ) - ); - } - - // Aging Tabs - // https://addons.mozilla.org/firefox/addon/3542 - if ('agingTabs' in window && - TreeStyleTabUtils.getTreePref('compatibility.AgingTabs')) { - eval('agingTabs.setColor = '+ - agingTabs.setColor.toSource().replace( - '{', - '{ important = true;' - ) - ); - } - - // Snap Links - // https://addons.mozilla.org/firefox/addon/4336 - // Snap Links Plus - // http://snaplinks.mozdev.org/ - if (TreeStyleTabUtils.getTreePref('compatibility.SnapLinks')) { - if ('executeAction' in window && - 'openTabs' in window) { - eval('window.openTabs = '+ - window.openTabs.toSource().replace( - /((sContent|gBrowser|getBrowser\(\))\.addTab)/, - 'TreeStyleTabService.readyToOpenChildTab($2); $1' - ) - ); - } - if ('SnapLinks' in window && - 'OpenTabs' in SnapLinks) { - eval('SnapLinks.OpenTabs = '+ - SnapLinks.OpenTabs.toSource().replace( - /((sContent|gBrowser|getBrowser\(\))\.addTab)/, - 'TreeStyleTabService.readyToOpenChildTab($2); $1' - ) - ); - } - } - - // Mouseless Browsing - // https://addons.mozilla.org/firefox/addon/879 - if ('mouselessbrowsing' in window && - 'EventHandler' in mouselessbrowsing && - TreeStyleTabUtils.getTreePref('compatibility.MouselessBrowsing')) { - if ('execute' in mouselessbrowsing.EventHandler) { - eval('mouselessbrowsing.EventHandler.execute = '+ - mouselessbrowsing.EventHandler.execute.toSource().replace( - '{', - '{ var Prefs = mlb_common.Prefs;'+ - ' var Utils = mlb_common.Utils;'+ - ' var MlbUtils = mouselessbrowsing.MlbUtils;' - ).replace( - /((?:var [^=]+ = )?Utils.openUrlInNewTab\()/g, - 'TreeStyleTabService.readyToOpenChildTab(); $1' - ) - ); - } - if ('openLinkInOtherLocationViaPostfixKey' in mouselessbrowsing.EventHandler) { - eval('mouselessbrowsing.EventHandler.openLinkInOtherLocationViaPostfixKey = '+ - mouselessbrowsing.EventHandler.openLinkInOtherLocationViaPostfixKey.toSource().replace( - '{', - '{ var Prefs = mlb_common.Prefs;'+ - ' var Utils = mlb_common.Utils;'+ - ' var MlbUtils = mouselessbrowsing.MlbUtils;'+ - ' var MlbCommon = mouselessbrowsing.MlbCommon;'+ - ' var ShortcutManager = mlb_common.ShortcutManager;' - ).replace( - 'Utils.openUrlInNewTab(', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - } - } - - // Linky - // https://addons.mozilla.org/firefox/addon/425 - if ('LinkyContext' in window && - 'prototype' in LinkyContext && - TreeStyleTabUtils.getTreePref('compatibility.Linky')) { - let (methods = 'openLink,openLinks,generateDocument'.split(',')) { - for (let i = 0, maxi = methods.length; i < maxi; i++) - { - let method = methods[i]; - if (!(method in LinkyContext.prototype)) continue; - eval('LinkyContext.prototype.'+method+' = '+ - LinkyContext.prototype[method].toSource().replace( - '{', - '{ TreeStyleTabService.readyToOpenChildTabNow(null, true);' - ) - ); - } - } - } - - // QuickDrag - // https://addons.mozilla.org/firefox/addon/6912 - if ('QuickDrag' in window && - '_loadTab' in QuickDrag && - TreeStyleTabUtils.getTreePref('compatibility.QuickDrag')) { - eval('QuickDrag._loadTab = '+ - QuickDrag._loadTab.toSource().replace( - /(gBrowser.loadOneTab\()/g, - 'TreeStyleTabService.readyToOpenChildTab(), $1' - ) - ); - } - - // Autohide - // http://www.krickelkrackel.de/autohide/ - if ('autoHIDE' in window && - TreeStyleTabUtils.getTreePref('compatibility.Autohide')) { - let autoHideEventListener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case 'TreeStyleTabAutoHideStateChanging': - if (!window.fullScreen) return; - if (!aEvent.shown) { - if ( - autoHIDE.statBar && - gBrowser.treeStyleTab.currentTabbarPosition == 'bottom' && - !TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar.always') && - TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar') - ) { - autoHIDE.statBar.setAttribute('ahHIDE', true); - } - } - else { - TreeStyleTabService.getTabStrip(gBrowser).removeAttribute('ahHIDE'); - if ( - autoHIDE.statBar && - aTabBrowser.treeStyleTab.currentTabbarPosition == 'bottom' && - !TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar.always') && - TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar') - ) { - autoHIDE.statBar.removeAttribute('ahHIDE'); - } - } - break; - - case 'fullscreen': - var treeStyleTab = gBrowser.treeStyleTab; - if (gBrowser.treeStyleTab.currentTabbarPosition != 'top') { - if (window.fullScreen) - treeStyleTab.autoHide.endForFullScreen(); - else - treeStyleTab.autoHide.startForFullScreen(); - } - break; - - case 'unload': - document.removeEventListener('TreeStyleTabAutoHideStateChanging', this, false); - document.removeEventListener('unload', this, false); - document.removeEventListener('fullscreen', this, false); - break; - } - } - }; - document.addEventListener('TreeStyleTabAutoHideStateChanging', autoHideEventListener, false); - document.addEventListener('fullscreen', autoHideEventListener, false); - document.addEventListener('unload', autoHideEventListener, false); - - if ('MoveContent' in autoHIDE) { - eval('autoHIDE.MoveContent = '+autoHIDE.MoveContent.toSource().replace( - /(;)([^;]*\.setPosition\(0, -\s*ah\.delta\);)/, - '$1\n' + - ' if (autoHIDE.winUtil)\n' + - ' autoHIDE.winUtil.setRedraw(false, false);\n' + - ' $2\n' + - ' gBrowser.treeStyleTab.autoHide.extraYOffset = ah.delta;\n' + - ' window.setTimeout(function() {\n' + - ' gBrowser.treeStyleTab.autoHide.redrawContentArea();\n' + - ' if (autoHIDE.winUtil)\n' + - ' autoHIDE.winUtil.setRedraw(true, false);\n' + - ' }, 0);' - ).replace( - /(;)([^;]*\.setPosition\(0, 0\);)/, - '$1\n' + - ' if (autoHIDE.winUtil)\n' + - ' autoHIDE.winUtil.setRedraw(false, false);\n' + - ' $2\n' + - ' gBrowser.treeStyleTab.autoHide.extraYOffset = 0;\n' + - ' window.setTimeout(function() {\n' + - ' gBrowser.treeStyleTab.autoHide.redrawContentArea();\n' + - ' if (autoHIDE.winUtil)\n' + - ' autoHIDE.winUtil.setRedraw(true, false);\n' + - ' }, 0);' - )); - } - } - - - // Google Toolbar Sidewiki - if ('sidewikiWindowHandler' in window && - window.sidewikiWindowHandler && - sidewikiWindowHandler.barsContainer_ && - sidewikiWindowHandler.barsContainer_.geometry_ && - sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers && - TreeStyleTabUtils.getTreePref('compatibility.GoogleToolbar.Sidewiki')) { - let func = sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers.toSource(); - if (func.indexOf('treeStyleTab') < 0) { - eval('sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers = '+func.replace( - 'return {', - ' if ("treeStyleTab" in this.topLevelDocument_.getElementById("content")) {\n' + - ' let b = this.topLevelDocument_.getElementById("content");\n' + - ' let box = b.mPanelContainer.boxObject;\n' + - ' return {\n' + - ' height : box.height,\n' + - ' width : box.width,\n' + - ' top : box.y,\n' + - ' left : box.x,\n' + - ' right : this.topLevelWindow_.innerWidth - box.x - box.width,\n' + - ' tabBoxHeight : 0\n' + - ' };\n' + - ' }\n' + - '$&' - )); - } - } - - - // Smoothly Close Tabs - // https://addons.mozilla.org/firefox/addon/71410 - if ('SMOOTHLYCLOSETABS' in window && - TreeStyleTabUtils.getTreePref('compatibility.SmoothlyCloseTabs')) { - let replaceScrollProps = function(aString) { - return aString.replace( - /\.scrollWidth/g, - '[scrollProp]' - ).replace( - /"width"/g, - 'sizeProp' - ).replace( - /\.maxWidth/g, - '[maxSizeProp]' - ).replace( - '{', - '{\n' + - ' var scrollProp = gBrowser.treeStyleTab.isVertical ? "scrollHeight" : "scrollWidth" ;\n' + - ' var sizeProp = gBrowser.treeStyleTab.isVertical ? "height" : "width" ;\n' + - ' var maxSizeProp = gBrowser.treeStyleTab.isVertical ? "maxHeight" : "maxWidth" ;' - ) - } - eval('SMOOTHLYCLOSETABS.shrinkTab = '+ - replaceScrollProps(SMOOTHLYCLOSETABS.shrinkTab.toSource()) - ); - eval('SMOOTHLYCLOSETABS.shrinkTabIcon = '+ - replaceScrollProps(SMOOTHLYCLOSETABS.shrinkTabIcon.toSource()) - ); - } - - // Super Tab Mode - // https://addons.mozilla.org/firefox/addon/13288 - if ('stmM' in window && - TreeStyleTabUtils.getTreePref('compatibility.STM')) { - var observer = { - domain : 'extensions.stm.', - observe : function(aSubject, aTopic, aData) - { - switch (aData) - { - case 'extensions.stm.tabBarMultiRows': - case 'extensions.stm.tabBarPosition': - if ( - TreeStyleTabUtils.prefs.getPref('extensions.stm.tabBarMultiRows') && - TreeStyleTabUtils.prefs.getPref('extensions.stm.tabBarPosition') == 0 - ) { - TreeStyleTabUtils.prefs.setPref('extensions.stm.tabBarMultiRows.override', false); - } - return; - - case 'extensions.stm.newTabBtnPos': - if (TreeStyleTabUtils.prefs.getPref(aData) == 0) - document.documentElement.removeAttribute(TreeStyleTabService.kHIDE_NEWTAB); - else - document.documentElement.setAttribute(TreeStyleTabService.kHIDE_NEWTAB, true); - return; - } - } - }; - observer.observe(null, null, 'extensions.stm.tabBarMultiRows'); - observer.observe(null, null, 'extensions.stm.newTabBtnPos'); - TreeStyleTabUtils.prefs.addPrefListener(observer); - document.addEventListener('unload', function onUnload() { - document.removeEventListener('unload', onUnload, false); - TreeStyleTabUtils.prefs.removePrefListener(observer); - }, false); - - let warnPref = 'extensions.treestyletab.compatibility.STM.warnForNewTabPosition'; - if ( - TreeStyleTabUtils.prefs.getPref(warnPref) && - TreeStyleTabUtils.prefs.getPref('extensions.stm.newTabPosition') != 0 - ) { - let checked = { value : false }; - if (Services.prompt.confirmEx( - null, - TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_title'), - TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_text'), - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), - TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_use_TST'), - TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_use_STM'), - null, - TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_never'), - checked - ) == 0) { - TreeStyleTabUtils.prefs.setPref('extensions.stm.newTabPosition', 0); - } - if (checked.value) - TreeStyleTabUtils.prefs.setPref(warnPref, false); - } - - sv.registerTabFocusAllowance(function(aTabBrowser) { - return TreeStyleTabUtils.prefs.getPref('extensions.stm.focusAfterCloseTab') == 0; - }); - } - - // Tab Utilities - // https://addons.mozilla.org/firefox/addon/59961 - if ('tabutils' in window && - TreeStyleTabUtils.getTreePref('compatibility.TabUtilities')) { - TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { - return TreeStyleTabUtils.prefs.getPref('extensions.tabutils.selectOnClose') == 0; - }); - } - - // Remove New Tab Button - // https://addons.mozilla.org/firefox/addon/10535 - if (TreeStyleTabUtils.getTreePref('compatibility.RemoveNewTabButton')) { - sv.extensions.isAvailable('remove-new-tab-button@forerunnerdesigns.com', { ok : function() { - document.documentElement.setAttribute(TreeStyleTabService.kHIDE_NEWTAB, true); - }}); - } - - // IE Tab Plus - // https://addons.mozilla.org/firefox/addon/10909/ - if ('IeTab' in window && - IeTab.prototype && - TreeStyleTabUtils.getTreePref('compatibility.IETabPlus')) { - if (IeTab.prototype.switchTabEngine) - eval('IeTab.prototype.switchTabEngine = '+ - IeTab.prototype.switchTabEngine.toSource().replace( - 'var newTab = ', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - - if (IeTab.prototype.addIeTab) - eval('IeTab.prototype.addIeTab = '+ - IeTab.prototype.addIeTab.toSource().replace( - 'var newTab = ', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - } - - // Locationbar2 - // https://addons.mozilla.org/firefox/addon/locationbar²/ - if ('lb2_alternateStyles' in window && - TreeStyleTabUtils.getTreePref('compatibility.Locationbar2')) { - let listening = false; - let listener = function(aEvent) { - switch (aEvent.type) - { - case 'unload': - document.removeEventListener('unload', listener, false); - document.removeEventListener('beforecustomization', listener, true); - document.removeEventListener('aftercustomization', listener, false); - case 'beforecustomization': - if (gURLBar && listening) - gURLBar.removeEventListener('click', listener, true); - listening = false; - return; - - case 'aftercustomization': - if (gURLBar && !listening) { - gURLBar.addEventListener('click', listener, true); - listening = true; - } - return; - - case 'click': - if (sv.evaluateXPath( - 'ancestor-or-self::*[' - +'contains(concat(" ", normalize-space(@class), " "), " textbox-presentation-segment ")' - +']', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue) - sv.readyToOpenChildTabNow(gBrowser.selectedTab); - return; - } - }; - document.addEventListener('unload', listener, false); - document.addEventListener('beforecustomization', listener, true); - document.addEventListener('aftercustomization', listener, false); - if (gURLBar && !listening) { - gURLBar.addEventListener('click', listener, true); - listening = true; - } - } - - // InstaClick - // https://addons.mozilla.org/firefox/addon/instaclick/ - if ('instaclick' in window && - 'contentAreaClick2' in window.instaclick && - TreeStyleTabUtils.getTreePref('compatibility.InstaClick')) { - eval('instaclick.contentAreaClick2 = '+ - instaclick.contentAreaClick2.toSource().replace( - 'gBrowser.loadOneTab(', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - } - - // Duplicate This Tab - // https://addons.mozilla.org/firefox/addon/duplicate-this-tab/ - if ('duplicatethistab' in window && - 'openLinkWithHistory' in window.duplicatethistab && - TreeStyleTabUtils.getTreePref('compatibility.DuplicateThisTab')) { - eval('duplicatethistab.openLinkWithHistory = '+ - duplicatethistab.openLinkWithHistory.toSource().replace( - 'var newTab = ', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - } - - // Context Search - // http://www.cusser.net/extensions/contextsearch/ - if ('contextsearch' in window && - 'search' in window.contextsearch && - TreeStyleTabUtils.getTreePref('compatibility.ContextSearch')) { - eval('contextsearch.search = '+ - contextsearch.search.toSource().replace( - 'var newTab = ', - 'TreeStyleTabService.readyToOpenChildTab(); $&' - ) - ); - } - - // Tile Tabs - // https://addons.mozilla.org/firefox/addon/tile-tabs/ - if ('tileTabs' in window && - TreeStyleTabUtils.getTreePref('compatibility.TileTabs')) { - if ('allocateTab' in window.tileTabs) - eval('tileTabs.allocateTab = '+ - tileTabs.allocateTab.toSource().replace( - /(tab = gBrowser.addTab)/g, - 'TreeStyleTabService.readyToOpenNextSiblingTabNow(); $1' - ) - ); - if ('doClickBrowser' in window.tileTabs) - eval('tileTabs.doClickBrowser = '+ - tileTabs.doClickBrowser.toSource().replace( - /(newTab = gBrowser.loadOneTab)/g, - 'TreeStyleTabService.readyToOpenNextSiblingTabNow(); $1' - ) - ); - if ('doDropBrowserTile' in window.tileTabs) - eval('tileTabs.doDropBrowserTile = '+ - tileTabs.doDropBrowserTile.toSource().replace( - /(tab = gBrowser.loadOneTab)/g, - 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' - ) - ); - if ('menuActions' in window.tileTabs) - eval('tileTabs.menuActions = '+ - tileTabs.menuActions.toSource().replace( - /(tab = gBrowser.loadOneTab)/g, - 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' - ) - ); - if ('applyLayoutString' in window.tileTabs) - eval('tileTabs.applyLayoutString = '+ - tileTabs.applyLayoutString.toSource().replace( - /(tab = gBrowser.loadOneTab)/g, - 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' - ) - ); - } - - window.setTimeout(function(aSelf) { - aSelf.overrideExtensionsDelayed(); - }, 0, this); -}; - - -TreeStyleTabWindowHelper.overrideExtensionsDelayed = function TSTWH_overrideExtensionsDelayed() { - var sv = this.service; - - // Tab Mix Plus - if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && - 'TabmixTabbar' in window) { - // correct broken appearance of the first tab - var t = gBrowser.treeStyleTab.getFirstTab(gBrowser); - gBrowser.treeStyleTab.initTabAttributes(t); - gBrowser.treeStyleTab.initTabContentsOrder(t); - - eval('gBrowser.openInverseLink = '+ - gBrowser.openInverseLink.toSource().replace( - /(var newTab)/, - 'TreeStyleTabService.readyToOpenChildTab(aTab); $1' - ) - ); - - eval('gBrowser.TMP_openTabNext = '+ - gBrowser.TMP_openTabNext.toSource().replace( - 'this.mCurrentTab._tPos + this.tabContainer.nextTab', - ' (function() {\n' + - ' var tabs = this.treeStyleTab.getDescendantTabs(this.mCurrentTab);\n' + - ' if (tabs.length) {\n' + - ' var index = TreeStyleTabUtils.prefs.getPref("extensions.tabmix.openTabNextInverse") ?\n' + - ' tabs[tabs.length - 1]._tPos :\n' + - ' this.mCurrentTab._tPos ;\n' + - ' if (index < aTab._tPos) index++;\n' + - ' return index;\n' + - ' }\n' + - ' else {\n' + - ' return ($&);\n' + - ' }\n' + - ' }).call(this)' - ) - ); - - gBrowser.treeStyleTab.internallyTabMovingCount--; - } - - // Multi Links - // https://addons.mozilla.org/firefox/addon/13494 - if ('MultiLinks_Wrapper' in window && - 'LinksManager' in MultiLinks_Wrapper && - 'OpenInNewTabs' in MultiLinks_Wrapper.LinksManager && - TreeStyleTabUtils.getTreePref('compatibility.MultiLinks')) { - eval('MultiLinks_Wrapper.LinksManager.OpenInNewTabs = '+ - MultiLinks_Wrapper.LinksManager.OpenInNewTabs.toSource().replace( - '{', - '{\n' + - ' if (!TreeStyleTabService.checkToOpenChildTab(getBrowser()))\n' + - ' TreeStyleTabService.readyToOpenChildTab(getBrowser(), true);' - ).replace( - /(\}\)?)$/, - ' if (TreeStyleTabService.checkToOpenChildTab(getBrowser()))\n' + - ' TreeStyleTabService.stopToOpenChildTab(getBrowser());\n' + - '$1' - ) - ); - } - - // DomainTab - // https://addons.mozilla.org/firefox/addon/13906/ - if ('domaintab' in window && - 'TMP_howToOpen' in domaintab && - TreeStyleTabUtils.getTreePref('compatibility.DomainTab')) { - eval('domaintab.TMP_howToOpen = '+ - domaintab.TMP_howToOpen.toSource().replace( - /(domaintab.DT_openNewTabWith\()/g, - 'TreeStyleTabService.readyToOpenChildTab(); $1' - ) - ); - } - - // Personal Titlebar - // https://addons.mozilla.org/irefox/addon/personal-titlebar/ - if (document.getElementById('personal-titlebar') && - TreeStyleTabUtils.getTreePref('compatibility.PersonalTitlebar')) { - let titlebar = document.getElementById('titlebar'); - let personalTitlebar = document.getElementById('personal-titlebar'); - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case 'beforecustomization': - titlebar.removeEventListener('DOMAttrModified', this, true); - gBrowser.treeStyleTab.destroyTabStrip(personalTitlebar); - break; - - case 'aftercustomization': - titlebar.addEventListener('DOMAttrModified', this, true); - break; - - case 'DOMAttrModified': - if ( - aEvent.attrName == 'hidden' && - gBrowser.tabContainer.parentNode.id == (aEvent.newValue == 'true' ? 'toolbar-menubar' : 'personal-titlebar' ) - ) { - TreeStyleTabService.stopRendering(); - gBrowser.treeStyleTab.syncDestroyTabbar(); - window.setTimeout(function() { - gBrowser.treeStyleTab.syncReinitTabbar(); - TreeStyleTabService.startRendering(); - }, 0); - } - break; - - case 'unload': - titlebar.removeEventListener('DOMAttrModified', this, true); - document.removeEventListener('beforecustomization', this, false); - document.removeEventListener('aftercustomization', this, false); - document.removeEventListener('unload', this, false); - personalTitlebar = null; - break; - } - } - }; - document.addEventListener('beforecustomization', listener, false); - document.addEventListener('aftercustomization', listener, false); - document.addEventListener('unload', listener, false); - titlebar.addEventListener('DOMAttrModified', listener, true); - } - - // TotalToolbar - // http://totaltoolbar.mozdev.org/ - let (menu = document.getElementById('tt-toolbar-properties') && - TreeStyleTabUtils.getTreePref('compatibility.TotalToolbar')) { - if (menu) { - let tabbarToolboxes = ['tt-toolbox-tabright', 'tt-toolbox-tableft'] - .map(document.getElementById, document) - .filter(function(aToolbox) { return aToolbox; }); - let listener = { - handleEvent : function(aEvent) - { - var sv = TreeStyleTabService; - switch (aEvent.type) - { - case 'command': - gBrowser.treeStyleTab.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_WINDOW_RESIZE); - break; - - case 'beforecustomization': - for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) - { - tabbarToolboxes[i].removeAttribute('collapsed'); - } - break; - - case 'aftercustomization': - for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) - { - let toolbox = tabbarToolboxes[i]; - if (!toolbox.firstChild.hasChildNodes()) - toolbox.setAttribute('collapsed', true); - } - break; - - case 'unload': - menu.removeEventListener('command', this, true); - document.removeEventListener('beforecustomization', listener, true); - document.removeEventListener('aftercustomization', listener, false); - document.removeEventListener('unload', this, false); - menu = null; - break; - } - } - }; - menu.addEventListener('command', listener, false); - document.addEventListener('beforecustomization', listener, true); - document.addEventListener('aftercustomization', listener, false); - document.addEventListener('unload', listener, false); - for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) - { - let toolbox = tabbarToolboxes[i]; - if (!toolbox.firstChild.hasChildNodes()) - toolbox.setAttribute('collapsed', true); - } - } - } - - // Tab Control - // https://addons.mozilla.org/firefox/addon/tab-control/ - if ( - TreeStyleTabUtils.getTreePref('compatibility.TabControl') && - 'gTabControl' in window - ) { - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case sv.kEVENT_TYPE_FOCUS_NEXT_TAB: - if (TreeStyleTabUtils.prefs.getPref('tabcontrol.focusLeftOnClose')) - aEvent.preventDefault(); - break; - - case 'unload': - document.removeEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); - break; - } - } - }; - document.addEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); - document.addEventListener('unload', listener, false); - } - - // Firefox Sync (Weave) - // http://www.mozilla.com/en-US/firefox/sync/ - if ( - ( - 'gFxWeaveGlue' in window || // addon - 'gSyncUI' in window // Firefox 4 built-in - ) && - TreeStyleTabUtils.getTreePref('compatibility.FirefoxSync') - ) { - let ns = {}; - try { // 1.4 - Components.utils.import('resource://services-sync/service.js', ns); - } - catch(e) { // 1.3 - Components.utils.import('resource://weave/service.js', ns); - } - let listener = { - handleEvent : function(aEvent) - { - switch (aEvent.type) - { - case 'TabOpen': - let tab = aEvent.originalTarget - let b = TreeStyleTabService.getTabBrowserFromChild(tab); - if (b.selectedTab.linkedBrowser.currentURI.spec != 'about:sync-tabs') - return; - - let service = ns.Service || ns.Weave /* old name */; - let manager = service.engineManager || service.Engines /* old name */; - let engine = manager.get('tabs'); - - let uri = tab.getAttribute('label'); - if (engine.locallyOpenTabMatchesURL(uri)) - return; - - for (let [guid, client] in Iterator(engine.getAllClients())) - { - if (client.tabs.some(function({ urlHistory }) { - return urlHistory[0] == uri; - })) { - let parent = b.selectedTab; - window.setTimeout(function() { - if (tab.parentNode && !b.treeStyleTab.getParentTab(tab)) - b.treeStyleTab.attachTabTo(tab, parent); - }, 0); - return; - } - } - return; - - case 'unload': - document.removeEventListener('TabOpen', this, true); - document.removeEventListener('unload', this, false); - return; - } - } - }; - document.addEventListener('TabOpen', listener, true); - document.addEventListener('unload', listener, false); - } - -}; +Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, + 'Services', 'resource://gre/modules/Services.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, + 'TreeStyleTabUtils', 'resource://treestyletab-modules/utils.js'); + +TreeStyleTabWindowHelper.extraProperties = [ + TreeStyleTabService.kID, + TreeStyleTabService.kCOLLAPSED, + TreeStyleTabService.kSUBTREE_COLLAPSED, + TreeStyleTabService.kCHILDREN, + TreeStyleTabService.kPARENT, + TreeStyleTabService.kANCESTOR, + TreeStyleTabService.kINSERT_BEFORE, + TreeStyleTabService.kINSERT_AFTER +]; + +TreeStyleTabWindowHelper.overrideExtensionsPreInit = function TSTWH_overrideExtensionsPreInit() { + var sv = this.service; + + // Highlander + // https://addons.mozilla.org/firefox/addon/4086 + if ('Highlander' in window && + TreeStyleTabUtils.getTreePref('compatibility.Highlander')) { + eval('Highlander.overrideHandleLinkClick = '+ + Highlander.overrideHandleLinkClick.toSource().replace( + /(var )?origHandleLinkClick/g, + 'window.__treestyletab__highlander__origHandleLinkClick' + ) + ); + } + + // PermaTabs + // https://addons.mozilla.org/firefox/addon/2558 + // PermaTabs Mod + // https://addons.mozilla.org/firefox/addon/7816 + if ('permaTabs' in window && + TreeStyleTabUtils.getTreePref('compatibility.PermaTabs')) { + if ('__init' in permaTabs) { + // without delay, Firefox crashes on startup. + eval('permaTabs.__init = '+ + permaTabs.__init.toSource().replace( + 'aTab.setAttribute(\\"image\\", ', + 'window.setTimeout(function(aTab, aImage) { aTab.setAttribute(\\"image\\", aImage); }, 100, aTab, ' + ) + ); + } + if ('showPermaTab' in permaTabs) { + eval('permaTabs.showPermaTab = '+ + permaTabs.showPermaTab.toSource().replace( + /(\}\)?)$/, + '(function(tab, id) {\n' + + ' if (this.ssWillRestore) return;\n' + + ' var TST = TreeStyleTabService;\n' + + ' if (this.TSTRestoredPermaTabsInfo === void(0)) {\n' + + ' try {\n' + + ' eval("this.TSTRestoredPermaTabsInfo = "+(TreeStyleTabUtils.getTreePref("permaTabsInfo") || "null"));\n' + + ' }\n' + + ' catch(e) {\n' + + ' }\n' + + ' }\n' + + ' if (!this.TSTRestoredPermaTabsInfo) return;\n' + + + ' var info = this.TSTRestoredPermaTabsInfo[id];\n' + + ' if (!info) return;\n' + + + ' for (var i in info)\n' + + ' {\n' + + ' TST.SessionStore.setTabValue(tab, i, String(info[i]));\n' + + ' }\n' + + ' var count = 0;\n' + + ' window.setTimeout(function onTimeout() {\n' + + ' var b = TST.getTabBrowserFromChild(tab);\n' + + ' if (!b.treeStyleTab) {\n' + + ' if (++count < 50)\n' + + ' window.setTimeout(onTimeout, 100);\n' + + ' return;\n' + + ' }\n' + + ' b.treeStyleTab.handleRestoredTab(tab);\n' + + ' }, 0);\n' + + '}).call(this, tab, id)\n' + + '$1' + ) + ); + } + if ('savePermaTabs' in permaTabs) { + eval('permaTabs.savePermaTabs = '+ + permaTabs.savePermaTabs.toSource().replace( + '{', + '{\n' + + '(function() {\n' + + ' var tabsInfo = {};\n' + + ' var TST = TreeStyleTabService;\n' + + ' var allTabs = getBrowser().mTabContainer.childNodes;\n' + + ' for (let i = 0, maxi = allTabs.length; i < maxi; i++)\n' + + ' {\n' + + ' let tab = allTabs[i];\n' + + ' let index = this.getPermaTabLocalIndex(tab);\n' + + ' if (index < 0) continue;\n' + + ' let info = {};\n' + + ' for (let i = 0, maxi = TST.extraProperties.length; i < maxi; i++)\n' + + ' {\n' + + ' let property = TST.extraProperties[i];\n' + + ' info[property] = TST.getTabValue(tab, property);\n' + + ' }\n' + + ' tabsInfo[this.permaTabs[index].id] = info;\n' + + ' }\n' + + ' TreeStyleTabUtils.setTreePref("permaTabsInfo", tabsInfo.toSource());\n' + + '}).call(this);' + ) + ); + } + } + + // Tab Mix Plus + if (TreeStyleTabUtils.getTreePref('compatibility.TMP')) { + document.documentElement.setAttribute('treestyletab-enable-compatibility-tmp', true); + } + // Tab Mix Plus, SessionStore API + if ( + TreeStyleTabUtils.getTreePref('compatibility.TMP') && + ('TabmixSessionData' in window || 'SessionData' in window) + ) { + let sessionData = window.TabmixSessionData || window.SessionData; + if ('getTabProperties' in sessionData && 'setTabProperties' in sessionData) { + let prefix = sv.kTMP_SESSION_DATA_PREFIX; + let sessionManager = window.TabmixSessionManager || window.SessionManager; + sessionData.tabTSTProperties = this.extraProperties.map(function(aProperty) { + return prefix+aProperty; + }); + eval('sessionData.getTabProperties = '+ + sessionData.getTabProperties.toSource().replace( + 'return tabProperties;', + ' for (let i = 0, maxi = this.tabTSTProperties.length; i < maxi; i++)\n' + + ' {\n' + + ' let property = this.tabTSTProperties[i];\n' + + ' tabProperties += "|" + property + "=" + encodeURIComponent(aTab.getAttribute(property));\n' + + ' }\n' + + '$&' + ) + ); + eval('sessionData.setTabProperties = '+ + sessionData.setTabProperties.toSource().replace( + '{', + '$&\n' + + ' var TSTProps = tabProperties.split("|");\n' + + ' tabProperties = TSTProps.shift();\n' + + ' for (let i = 0, maxi = TSTProps.length; i < maxi; i++)\n' + + ' {\n' + + ' let property = TSTProps[i];\n' + + ' let index = property.indexOf("=");\n' + + ' let name = property.substring(0, index);\n' + + ' let value = decodeURIComponent(property.substring(index+1));\n' + + ' if (name && value)\n' + + ' aTab.setAttribute(name, value);\n' + + ' }' + ) + ); + eval('sessionManager.loadOneTab = '+ + sessionManager.loadOneTab.toSource().replace( + /(\}\))?$/, + ' if (gBrowser.treeStyleTab.useTMPSessionAPI)\n' + + ' gBrowser.treeStyleTab.handleRestoredTab(aTab);\n' + + '$1' + ) + ); + let source = tablib.init.toSource().split('gBrowser.restoreTab = '); + source[1] = source[1].replace( + 'return newTab;', + ' if (this.treeStyleTab.useTMPSessionAPI)\n' + + ' this.treeStyleTab.handleRestoredTab(newTab);\n' + + '$&' + ); + eval('tablib.init = '+source.join('gBrowser.restoreTab = ')); + eval('sessionManager.loadOneWindow = '+ + sessionManager.loadOneWindow.toSource().replace( + 'gBrowser.tabsToLoad = ', + ' gBrowser.treeStyleTab.resetAllTabs(true, true);\n' + + ' TreeStyleTabService.restoringTree = true;\n' + + '$&' + ).replace( + /(\}\))?$/, + 'TreeStyleTabService.restoringTree = false; $1' + ) + ); + sv.useTMPSessionAPI = true; + } + } + + // Session Manager + // https://addons.mozilla.org/firefox/addon/2324 + // We need to initialize TST before Session Manager restores the last session anyway! + if ('gSessionManager' in window && + TreeStyleTabUtils.getTreePref('compatibility.SessionManager')) { + if ('onLoad_proxy' in gSessionManager && + 'onLoad' in gSessionManager) { + eval('gSessionManager.onLoad = '+gSessionManager.onLoad.toSource().replace( + '{', + '{ TreeStyleTabService.init();' + )); + } + if ('load' in gSessionManager) { + eval('gSessionManager.load = '+gSessionManager.load.toSource().replace( + 'var tabcount = ', + ' gBrowser.treeStyleTab.collapseExpandAllSubtree(false, true);\n' + + ' let (tabs = gBrowser.treeStyleTab.getTabs(gBrowser).slice(1).reverse()) {\n' + + ' for (let i = 0, maxi = tabs.length; i < maxi; i++)\n' + + ' {\n' + + ' let tab = tabs[i];\n' + + ' gBrowser.removeTab(tab);\n' + + ' }\n' + + ' }\n' + + ' TreeStyleTabService.restoringTree = true;\n' + + '$&' + )); + } + } + + // FullerScreen + // https://addons.mozilla.org/firefox/addon/4650 + if ('FS_onFullerScreen' in window && + TreeStyleTabUtils.getTreePref('compatibility.FullerScreen')) { + let (functions = 'CheckIfFullScreen,FS_onFullerScreen,FS_onMouseMove'.split(',')) { + for (let i = 0, maxi = functions.length; i < maxi; i++) + { + let func = functions[i]; + if (!(func in window)) continue; + eval('window.'+func+' = '+window[func].toSource().replace( + /FS_data.mTabs.(removeAttribute\("moz-collapsed"\)|setAttribute\("moz-collapsed", "true"\));/g, + 'if (gBrowser.treeStyleTab.currentTabbarPosition == "top") { $& }' + )); + } + } + } + + // TooManyTabs + // https://addons.mozilla.org/firefox/addon/9429 + if ('tooManyTabs' in window && + TreeStyleTabUtils.getTreePref('compatibility.TooManyTabs')) { + sv.registerExpandTwistyAreaBlocker('tooManyTabs'); + } + + // DragNDrop Toolbars + // https://addons.mozilla.org/firefox/addon/dragndrop-toolbars/ + if ('globDndtb' in window && + globDndtb.setTheStuff && + TreeStyleTabUtils.getTreePref('compatibility.DragNDropToolbars')) { + let reinitTabbar = function() { + TreeStyleTabService.stopRendering(); + gBrowser.treeStyleTab.syncDestroyTabbar(); + window.setTimeout(function() { + gBrowser.treeStyleTab.syncReinitTabbar(); + TreeStyleTabService.startRendering(); + }, 100); + }; + globDndtb.__treestyletab__setOrder = globDndtb.setOrder; + globDndtb.setOrder = function() { + reinitTabbar(); + return this.__treestyletab__setOrder.apply(this, arguments); + }; + globDndtb.__treestyletab__setTheStuff = globDndtb.setTheStuff; + globDndtb.setTheStuff = function() { + var result = this.__treestyletab__setTheStuff.apply(this, arguments); + if (this.dndObserver && + this.dndObserver.onDrop && + !this.dndObserver.__treestyletab__onDrop) { + this.dndObserver.__treestyletab__onDrop = this.dndObserver.onDrop; + this.dndObserver.onDrop = function(aEvent, aDropData, aSession) { + if (document.getElementById(aDropData.data) == gBrowser.treeStyleTab.tabStrip) { + reinitTabbar(); + } + return this.__treestyletab__onDrop.apply(this, arguments); + }; + } + return result; + }; + } + + // Optimoz Tweaks + // http://optimoz.mozdev.org/tweaks/ + // https://addons.mozilla.org/firefox/addon/optimoz-tweaks-ja-version/ + if ('mtSidebarStartup' in window && + 'mtSidebarShutdown' in window && + 'mtPreventHiding' in window && + TreeStyleTabUtils.getTreePref('compatibility.OptimozTweaks')) { + eval('window.mtSidebarStartup = '+window.mtSidebarStartup.toSource().replace( + '{', + '{\n' + + ' document.getElementById("TabsToolbar")\n' + + ' .addEventListener("mousemove", mtMouseMoveListener, false);' + )); + eval('window.mtSidebarShutdown = '+window.mtSidebarShutdown.toSource().replace( + '{', + '{\n' + + ' document.getElementById("TabsToolbar")\n' + + ' .removeEventListener("mousemove", mtMouseMoveListener, false);' + )); + eval('window.mtPreventHiding = '+window.mtPreventHiding.toSource().replace( + '{', + '{\n' + + ' if (TreeStyleTabService.getTabbarFromEvent(arguments[0]))\n' + + ' return;' + )); + } + + /** + * Hide Caption Titlebar Plus (Smart) + * https://addons.mozilla.org/firefox/addon/hide-caption-titlebar-plus-sma/ + */ + if ('HideCaption' in window && + 'do_alter' in HideCaption) { + eval('HideCaption.do_alter = '+HideCaption.do_alter.toSource().replace( + 'if (!theSettings) {', + ' if (!theSettings ||\n' + + ' gBrowser.treeStyleTab.isVertical) {' + )); + } + + // Greasemonkey + // https://addons.mozilla.org/firefox/addon/748 + if (TreeStyleTabUtils.getTreePref('compatibility.Greasemonkey')) { + try { + let hitchModule = Components.utils.import('resource://greasemonkey/util/hitch.js', {}); + let hitch = hitchModule.hitch; + if (hitch.toSource().indexOf('TreeStyleTabService') < 0) { + let ns = {}; + Components.utils.import('resource://greasemonkey/third-party/getChromeWinForContentWin.js', ns); + let getChromeWinForContentWin = ns.getChromeWinForContentWin; + hitchModule.hitch = function(aObject, aMethod) { + if (typeof aMethod == 'function' && + aMethod.toSource().indexOf('function openInTab') > -1) { + let originalOpenInTab = aMethod; + /** + * This function must be replaced on scripts in "chrome:" URL, like this. + * Otherwise the original openInTab() will raise violation error. + * Don't move this hack into JS code modules with "resource:" URL. + */ + aMethod = function openInTab(aSafeContentWindow, aURL, aLoadInBackgtound) { + let chrome = getChromeWinForContentWin(aSafeContentWindow); + if (chrome && chrome.TreeStyleTabService) + chrome.TreeStyleTabService.readyToOpenChildTabNow(aSafeContentWindow); + return originalOpenInTab.apply(this, arguments); + }; + } + return hitch.apply(this, arguments); + }; + Components.utils.import('resource://greasemonkey/util.js', ns); + if (ns.GM_util) + ns.GM_util.hitch = hitchModule.hitch; + } + } + catch(e) { + dump('Tree Style Tab: failed to patch to Greasemonkey.\n'); + dump(e+'\n'); + } + } +}; + +TreeStyleTabWindowHelper.overrideExtensionsBeforeBrowserInit = function TSTWH_overrideExtensionsBeforeBrowserInit() { + var sv = this.service; + + // Tab Mix Plus + if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && + 'TMP_LastTab' in window) { + TMP_LastTab.TabBar = gBrowser.mTabContainer; + } + if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && + 'isTabVisible' in gBrowser.mTabContainer && + 'ensureTabIsVisible' in gBrowser.mTabContainer) { + let replaceHorizontalProps = function replaceHorizontalProps(aString) + { + return aString.replace( + /boxObject\.x/g, + 'boxObject[posProp]' + ).replace( + /boxObject\.screenX/g, + 'boxObject[screenPosProp]' + ).replace( + /boxObject\.width/g, + 'boxObject[sizeProp]' + ).replace( + '{', + '{\n' + + ' var posProp = gBrowser.treeStyleTab.isVertical ? "y" : "x" ;\n' + + ' var screenPosProp = gBrowser.treeStyleTab.isVertical ? "screenY" : "screenX" ;\n' + + ' var sizeProp = gBrowser.treeStyleTab.isVertical ? "height" : "width" ;' + ) + } + eval('gBrowser.mTabContainer.ensureTabIsVisible = '+ + replaceHorizontalProps(gBrowser.mTabContainer.ensureTabIsVisible.toSource().replace( + 'boxObject.width < 250', + '$& || gBrowser.treeStyleTab.isVertical' + )) + ); + eval('gBrowser.mTabContainer.isTabVisible = '+ + replaceHorizontalProps(gBrowser.mTabContainer.isTabVisible.toSource()) + ); + } + + // Tabberwocky + // https://addons.mozilla.org/firefox/addon/14439 + if ('tabberwocky' in window && + TreeStyleTabUtils.getTreePref('compatibility.Tabberwocky')) { + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case 'TreeStyleTabTabbarPositionChanged': + var b = aEvent.originalTarget; + if (b.treeStyleTab.isVertical) + TreeStyleTabUtils.prefs.setPref('tabberwocky.multirow', false); + break; + + case 'unload': + document.removeEventListener('TreeStyleTabTabbarPositionChanged', this, false); + document.removeEventListener('unload', this, false); + break; + } + } + }; + document.addEventListener('TreeStyleTabTabbarPositionChanged', listener, false); + document.addEventListener('unload', listener, false); + + if ('openSelectedLinks' in tabberwocky) { + eval('tabberwocky.openSelectedLinks = '+ + tabberwocky.openSelectedLinks.toSource().replace( + 'links.forEach(', + ' TreeStyleTabService.readyToOpenChildTab(aFrame, true)\n' + + '$&' + ).replace( + /(\}\)?)$/, + ' TreeStyleTabService.stopToOpenChildTab(aFrame)\n' + + '$1' + ) + ); + } + } +}; + +TreeStyleTabWindowHelper.overrideExtensionsAfterBrowserInit = function TSTWH_overrideExtensionsAfterBrowserInit() { + var sv = this.service; + + // Selection Links + // https://addons.mozilla.org/firefox/addon/8644 + if ('selectionlinks' in window && + 'parseSelection' in selectionlinks && + TreeStyleTabUtils.getTreePref('compatibility.SelectionLinks')) { + eval('selectionlinks.parseSelection = '+ + selectionlinks.parseSelection.toSource().replace( + /((?:[^\s:;]+.selectedTab\s*=\s*)?([^\s:;]+).addTab\()/g, + ' if ($2.treeStyleTab)\n' + + ' $2.treeStyleTab.readyToOpenChildTab(focusedWindow);\n' + + '$1' + ) + ); + } + + + // Tab Mix Plus + if ( + TreeStyleTabUtils.getTreePref('compatibility.TMP') && + 'TabmixTabbar' in window + ) { + let DNDObserver = 'TMP_tabDNDObserver' in window ? TMP_tabDNDObserver : TabDNDObserver ; + this.updateTabDNDObserver(DNDObserver); + eval('DNDObserver.clearDragmark = '+ + DNDObserver.clearDragmark.toSource().replace( + /(\})(\))?$/, + 'gBrowser.treeStyleTab.tabbarDNDObserver.clearDropPosition(); $1$2' + ) + ); + eval('DNDObserver.onDragStart = '+ + DNDObserver.onDragStart.toSource().replace( + 'event.target.localName != "tab"', + ' gBrowser.treeStyleTab.tabbarDNDObserver.canDragTabbar(event) ||\n' + + ' $&' + ) + ); + + eval('window.TMP_howToOpen = '+ + window.TMP_howToOpen.toSource().replace( + /(window.openNewTabWith\()/g, + 'TreeStyleTabService.readyToOpenChildTab(event.target.ownerDocument.defaultView); $1' + ) + ); + + if ('TabmixContext' in window && + typeof TabmixContext.openMultipleLinks == 'function') { + eval('TabmixContext.openMultipleLinks = '+ + TabmixContext.openMultipleLinks.toSource().replace( + /(TMP_loadTabs\([^\)]+\);)/g, + 'TreeStyleTabService.readyToOpenChildTab(gBrowser, true); $1 TreeStyleTabService.stopToOpenChildTab(gBrowser);' + ) + ); + } + + + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED: + TabmixTabbar.updateScrollStatus(); + break; + + case sv.kEVENT_TYPE_FOCUS_NEXT_TAB: + let mode = TreeStyleTabUtils.prefs.getPref('extensions.tabmix.focusTab'); + if (mode != 2 && mode != 5) + aEvent.preventDefault(); + break; + + case 'unload': + document.removeEventListener(sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); + document.removeEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); + document.removeEventListener('unload', this, false); + break; + } + } + }; + document.addEventListener(sv.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, listener, false); + document.addEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); + document.addEventListener('unload', listener, false); + + gBrowser.treeStyleTab.internallyTabMovingCount++; // until "TMmoveTabTo" method is overwritten + } + + + // Super DragAndGo + // https://addons.mozilla.org/firefox/addon/137 + if ('superDrag' in window && + TreeStyleTabUtils.getTreePref('compatibility.SuperDragAndGo')) { + eval('superDrag.onDrop = '+ + superDrag.onDrop.toSource().replace( + /(var newTab = getBrowser\(\).addTab\([^\)]+\);)/g, + ' if (aDragSession.sourceNode &&\n' + + ' aDragSession.sourceNode.ownerDocument.defaultView.top == getBrowser().contentWindow)\n' + + ' TreeStyleTabService.readyToOpenChildTab(getBrowser());\n' + + ' $1' + ) + ); + } + + // Drag de Go + // https://addons.mozilla.org/firefox/addon/2918 + if ('ddg_ges' in window && + TreeStyleTabUtils.getTreePref('compatibility.DragDeGo')) { + eval('ddg_ges.Open = '+ + ddg_ges.Open.toSource().replace( + 'if (mode[1] == "h" || mode[1] == "f") {', + '$&\n' + + ' if ("sourceNode" in aData) // only for dragging from the content tarea.\n' + + ' TreeStyleTabService.readyToOpenChildTab(getBrowser());' + ) + ); + eval('ddg_ges.Search = '+ + ddg_ges.Search.toSource().replace( + 'if (mode[1] == "h" || mode[1] == "f") {', + '$&\n' + + ' TreeStyleTabService.readyToOpenChildTab(getBrowser());' + ) + ); + } + + // DragIt + // https://addons.mozilla.org/firefox/addon/dragit-formerly-drag-de-go/ + if ('DragIt' in window && + DragIt.tab && + DragIt.tab.open && + TreeStyleTabUtils.getTreePref('compatibility.DragIt')) { + eval('DragIt.tab.open = '+ + DragIt.tab.open.toSource().replace( + 'try {', + 'try { TreeStyleTabService.readyToOpenChildTabNow(gBrowser);' + ) + ); + } + + // Colorful Tabs + // https://addons.mozilla.org/firefox/addon/1368 + if ('colorfulTabs' in window && + TreeStyleTabUtils.getTreePref('compatibility.ColorfulTabs')) { + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case 'TabOpen': + case 'TreeStyleTabAttached': + case 'TreeStyleTabParted': + var child = aEvent.originalTarget; + var parent = aEvent.parentTab; + if (child && parent) { + let color = TreeStyleTabService.SessionStore.getTabValue(parent, 'tabClr'); + if (/^\d+,\d+,\d+$/.test(color)) + color = 'rgb('+color+')'; + window.setTimeout(function() { + colorfulTabs.setColor(child, color); + }, 0); + } + else if (child) { + TreeStyleTabService.SessionStore.setTabValue(child, 'tabClr', ''); + colorfulTabs.calcTabClr({ + target : child, + originalTarget : child, + }); + } + break; + + case 'unload': + document.removeEventListener('TabOpen', this, false); + document.removeEventListener('TreeStyleTabAttached', this, false); + document.removeEventListener('TreeStyleTabParted', this, false); + document.removeEventListener('unload', this, false); + break; + } + } + }; + eval('colorfulTabs.show_ctStack = '+ + colorfulTabs.show_ctStack.toSource().replace( + '.setProperty("display", "-moz-stack", "important")', + '.display = ""' + ) + ); + document.addEventListener('TabOpen', listener, false); + document.addEventListener('TreeStyleTabAttached', listener, false); + document.addEventListener('TreeStyleTabParted', listener, false); + document.addEventListener('unload', listener, false); + } + + // FLST (Focus Last Selected Tab) + // https://addons.mozilla.org/firefox/addon/32 + if ('flst' in window && + TreeStyleTabUtils.getTreePref('compatibility.FLST')) { + TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { + return !TreeStyleTabUtils.prefs.getPref('extensions.flst.enabled'); + }); + } + + // Focus Last Selected Tab 0.9.5.x + // http://www.gozer.org/mozilla/extensions/ + if (TreeStyleTabUtils.getTreePref('compatibility.FocusLastSelectedTab')) { + sv.extensions.isAvailable('focuslastselectedtab@gozer.org', { ok : function() { + TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { + return !aTabBrowser.selectedTab.hasAttribute('lastselected'); + }); + }}); + } + + // LastTab + // https://addons.mozilla.org/firefox/addon/112 + if ('LastTab' in window && + TreeStyleTabUtils.getTreePref('compatibility.LastTab')) { + TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { + return !TreeStyleTabUtils.prefs.getPref('extensions.lasttab.focusLastTabOnClose'); + }); + } + + // FireGestures + // https://addons.mozilla.org/firefox/addon/6366 + if ('FireGestures' in window && + TreeStyleTabUtils.getTreePref('compatibility.FireGestures')) { + eval('FireGestures.onExtraGesture = '+ + FireGestures.onExtraGesture.toSource().replace( + 'case "keypress-stop":', + '$&\n' + + ' TreeStyleTabService.readyToOpenChildTab(gBrowser, true);' + ).replace( + 'break;case "gesture-timeout":', + ' TreeStyleTabService.stopToOpenChildTab(gBrowser);\n' + + '$&' + ) + ); + eval('FireGestures._performAction = '+ + FireGestures._performAction.toSource().replace( + 'gBrowser.loadOneTab(', + ' TreeStyleTabService.readyToOpenChildTab(gBrowser);\n' + + '$&' + ) + ); + eval('FireGestures.openURLsInSelection = '+ + FireGestures.openURLsInSelection.toSource().replace( + 'var tab =', + ' if (!TreeStyleTabService.checkToOpenChildTab(gBrowser))\n' + + ' TreeStyleTabService.readyToOpenChildTab(gBrowser, true);\n' + + '$&' + ).replace( + 'if (!flag)', + ' if (TreeStyleTabService.checkToOpenChildTab(gBrowser))\n' + + ' TreeStyleTabService.stopToOpenChildTab(gBrowser);\n' + + '$&' + ) + ); + eval('FireGestures.handleEvent = '+ + FireGestures.handleEvent.toSource().replace( + 'gBrowser.loadOneTab(', + ' TreeStyleTabService.readyToOpenChildTab(gBrowser);\n' + + '$&' + ) + ); + } + + // Mouse Gestures Redox + // http://www.mousegestures.org/ + if ('mgBuiltInFunctions' in window && + 'mgLinkInTab' in mgBuiltInFunctions && + TreeStyleTabUtils.getTreePref('compatibility.MouseGesturesRedox')) { + eval('mgBuiltInFunctions.mgLinkInTab = '+ + mgBuiltInFunctions.mgLinkInTab.toSource().replace( + 'var tab', + 'TreeStyleTabService.readyToOpenChildTab(gBrowser); $&' + ) + ); + } + + // SBM Counter + // http://miniturbo.org/products/sbmcounter/ + if ('SBMCounter' in window && + TreeStyleTabUtils.getTreePref('compatibility.SBMCounter')) { + eval('SBMCounter.action = '+ + SBMCounter.action.toSource().replace( + 'gBrowser.selectedTab = gBrowser.addTab', + 'TreeStyleTabService.readyToOpenChildTab(gBrowser); $&' + ) + ); + } + + // Aging Tabs + // https://addons.mozilla.org/firefox/addon/3542 + if ('agingTabs' in window && + TreeStyleTabUtils.getTreePref('compatibility.AgingTabs')) { + eval('agingTabs.setColor = '+ + agingTabs.setColor.toSource().replace( + '{', + '{ important = true;' + ) + ); + } + + // Snap Links + // https://addons.mozilla.org/firefox/addon/4336 + // Snap Links Plus + // http://snaplinks.mozdev.org/ + if (TreeStyleTabUtils.getTreePref('compatibility.SnapLinks')) { + if ('executeAction' in window && + 'openTabs' in window) { + eval('window.openTabs = '+ + window.openTabs.toSource().replace( + /((sContent|gBrowser|getBrowser\(\))\.addTab)/, + 'TreeStyleTabService.readyToOpenChildTab($2); $1' + ) + ); + } + if ('SnapLinks' in window && + 'OpenTabs' in SnapLinks) { + eval('SnapLinks.OpenTabs = '+ + SnapLinks.OpenTabs.toSource().replace( + /((sContent|gBrowser|getBrowser\(\))\.addTab)/, + 'TreeStyleTabService.readyToOpenChildTab($2); $1' + ) + ); + } + } + + // Mouseless Browsing + // https://addons.mozilla.org/firefox/addon/879 + if ('mouselessbrowsing' in window && + 'EventHandler' in mouselessbrowsing && + TreeStyleTabUtils.getTreePref('compatibility.MouselessBrowsing')) { + if ('execute' in mouselessbrowsing.EventHandler) { + eval('mouselessbrowsing.EventHandler.execute = '+ + mouselessbrowsing.EventHandler.execute.toSource().replace( + '{', + '{ var Prefs = mlb_common.Prefs;'+ + ' var Utils = mlb_common.Utils;'+ + ' var MlbUtils = mouselessbrowsing.MlbUtils;' + ).replace( + /((?:var [^=]+ = )?Utils.openUrlInNewTab\()/g, + 'TreeStyleTabService.readyToOpenChildTab(); $1' + ) + ); + } + if ('openLinkInOtherLocationViaPostfixKey' in mouselessbrowsing.EventHandler) { + eval('mouselessbrowsing.EventHandler.openLinkInOtherLocationViaPostfixKey = '+ + mouselessbrowsing.EventHandler.openLinkInOtherLocationViaPostfixKey.toSource().replace( + '{', + '{ var Prefs = mlb_common.Prefs;'+ + ' var Utils = mlb_common.Utils;'+ + ' var MlbUtils = mouselessbrowsing.MlbUtils;'+ + ' var MlbCommon = mouselessbrowsing.MlbCommon;'+ + ' var ShortcutManager = mlb_common.ShortcutManager;' + ).replace( + 'Utils.openUrlInNewTab(', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + } + } + + // Linky + // https://addons.mozilla.org/firefox/addon/425 + if ('LinkyContext' in window && + 'prototype' in LinkyContext && + TreeStyleTabUtils.getTreePref('compatibility.Linky')) { + let (methods = 'openLink,openLinks,generateDocument'.split(',')) { + for (let i = 0, maxi = methods.length; i < maxi; i++) + { + let method = methods[i]; + if (!(method in LinkyContext.prototype)) continue; + eval('LinkyContext.prototype.'+method+' = '+ + LinkyContext.prototype[method].toSource().replace( + '{', + '{ TreeStyleTabService.readyToOpenChildTabNow(null, true);' + ) + ); + } + } + } + + // QuickDrag + // https://addons.mozilla.org/firefox/addon/6912 + if ('QuickDrag' in window && + '_loadTab' in QuickDrag && + TreeStyleTabUtils.getTreePref('compatibility.QuickDrag')) { + eval('QuickDrag._loadTab = '+ + QuickDrag._loadTab.toSource().replace( + /(gBrowser.loadOneTab\()/g, + 'TreeStyleTabService.readyToOpenChildTab(), $1' + ) + ); + } + + // Autohide + // http://www.krickelkrackel.de/autohide/ + if ('autoHIDE' in window && + TreeStyleTabUtils.getTreePref('compatibility.Autohide')) { + let autoHideEventListener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case 'TreeStyleTabAutoHideStateChanging': + if (!window.fullScreen) return; + if (!aEvent.shown) { + if ( + autoHIDE.statBar && + gBrowser.treeStyleTab.currentTabbarPosition == 'bottom' && + !TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar.always') && + TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar') + ) { + autoHIDE.statBar.setAttribute('ahHIDE', true); + } + } + else { + TreeStyleTabService.getTabStrip(gBrowser).removeAttribute('ahHIDE'); + if ( + autoHIDE.statBar && + aTabBrowser.treeStyleTab.currentTabbarPosition == 'bottom' && + !TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar.always') && + TreeStyleTabUtils.prefs.getPref('extensions.autohide.bars.statBar') + ) { + autoHIDE.statBar.removeAttribute('ahHIDE'); + } + } + break; + + case 'fullscreen': + var treeStyleTab = gBrowser.treeStyleTab; + if (gBrowser.treeStyleTab.currentTabbarPosition != 'top') { + if (window.fullScreen) + treeStyleTab.autoHide.endForFullScreen(); + else + treeStyleTab.autoHide.startForFullScreen(); + } + break; + + case 'unload': + document.removeEventListener('TreeStyleTabAutoHideStateChanging', this, false); + document.removeEventListener('unload', this, false); + document.removeEventListener('fullscreen', this, false); + break; + } + } + }; + document.addEventListener('TreeStyleTabAutoHideStateChanging', autoHideEventListener, false); + document.addEventListener('fullscreen', autoHideEventListener, false); + document.addEventListener('unload', autoHideEventListener, false); + + if ('MoveContent' in autoHIDE) { + eval('autoHIDE.MoveContent = '+autoHIDE.MoveContent.toSource().replace( + /(;)([^;]*\.setPosition\(0, -\s*ah\.delta\);)/, + '$1\n' + + ' if (autoHIDE.winUtil)\n' + + ' autoHIDE.winUtil.setRedraw(false, false);\n' + + ' $2\n' + + ' gBrowser.treeStyleTab.autoHide.extraYOffset = ah.delta;\n' + + ' window.setTimeout(function() {\n' + + ' gBrowser.treeStyleTab.autoHide.redrawContentArea();\n' + + ' if (autoHIDE.winUtil)\n' + + ' autoHIDE.winUtil.setRedraw(true, false);\n' + + ' }, 0);' + ).replace( + /(;)([^;]*\.setPosition\(0, 0\);)/, + '$1\n' + + ' if (autoHIDE.winUtil)\n' + + ' autoHIDE.winUtil.setRedraw(false, false);\n' + + ' $2\n' + + ' gBrowser.treeStyleTab.autoHide.extraYOffset = 0;\n' + + ' window.setTimeout(function() {\n' + + ' gBrowser.treeStyleTab.autoHide.redrawContentArea();\n' + + ' if (autoHIDE.winUtil)\n' + + ' autoHIDE.winUtil.setRedraw(true, false);\n' + + ' }, 0);' + )); + } + } + + + // Google Toolbar Sidewiki + if ('sidewikiWindowHandler' in window && + window.sidewikiWindowHandler && + sidewikiWindowHandler.barsContainer_ && + sidewikiWindowHandler.barsContainer_.geometry_ && + sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers && + TreeStyleTabUtils.getTreePref('compatibility.GoogleToolbar.Sidewiki')) { + let func = sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers.toSource(); + if (func.indexOf('treeStyleTab') < 0) { + eval('sidewikiWindowHandler.barsContainer_.geometry_.__proto__.getWindowSizeForDrawers = '+func.replace( + 'return {', + ' if ("treeStyleTab" in this.topLevelDocument_.getElementById("content")) {\n' + + ' let b = this.topLevelDocument_.getElementById("content");\n' + + ' let box = b.mPanelContainer.boxObject;\n' + + ' return {\n' + + ' height : box.height,\n' + + ' width : box.width,\n' + + ' top : box.y,\n' + + ' left : box.x,\n' + + ' right : this.topLevelWindow_.innerWidth - box.x - box.width,\n' + + ' tabBoxHeight : 0\n' + + ' };\n' + + ' }\n' + + '$&' + )); + } + } + + + // Smoothly Close Tabs + // https://addons.mozilla.org/firefox/addon/71410 + if ('SMOOTHLYCLOSETABS' in window && + TreeStyleTabUtils.getTreePref('compatibility.SmoothlyCloseTabs')) { + let replaceScrollProps = function(aString) { + return aString.replace( + /\.scrollWidth/g, + '[scrollProp]' + ).replace( + /"width"/g, + 'sizeProp' + ).replace( + /\.maxWidth/g, + '[maxSizeProp]' + ).replace( + '{', + '{\n' + + ' var scrollProp = gBrowser.treeStyleTab.isVertical ? "scrollHeight" : "scrollWidth" ;\n' + + ' var sizeProp = gBrowser.treeStyleTab.isVertical ? "height" : "width" ;\n' + + ' var maxSizeProp = gBrowser.treeStyleTab.isVertical ? "maxHeight" : "maxWidth" ;' + ) + } + eval('SMOOTHLYCLOSETABS.shrinkTab = '+ + replaceScrollProps(SMOOTHLYCLOSETABS.shrinkTab.toSource()) + ); + eval('SMOOTHLYCLOSETABS.shrinkTabIcon = '+ + replaceScrollProps(SMOOTHLYCLOSETABS.shrinkTabIcon.toSource()) + ); + } + + // Super Tab Mode + // https://addons.mozilla.org/firefox/addon/13288 + if ('stmM' in window && + TreeStyleTabUtils.getTreePref('compatibility.STM')) { + var observer = { + domain : 'extensions.stm.', + observe : function(aSubject, aTopic, aData) + { + switch (aData) + { + case 'extensions.stm.tabBarMultiRows': + case 'extensions.stm.tabBarPosition': + if ( + TreeStyleTabUtils.prefs.getPref('extensions.stm.tabBarMultiRows') && + TreeStyleTabUtils.prefs.getPref('extensions.stm.tabBarPosition') == 0 + ) { + TreeStyleTabUtils.prefs.setPref('extensions.stm.tabBarMultiRows.override', false); + } + return; + + case 'extensions.stm.newTabBtnPos': + if (TreeStyleTabUtils.prefs.getPref(aData) == 0) + document.documentElement.removeAttribute(TreeStyleTabService.kHIDE_NEWTAB); + else + document.documentElement.setAttribute(TreeStyleTabService.kHIDE_NEWTAB, true); + return; + } + } + }; + observer.observe(null, null, 'extensions.stm.tabBarMultiRows'); + observer.observe(null, null, 'extensions.stm.newTabBtnPos'); + TreeStyleTabUtils.prefs.addPrefListener(observer); + document.addEventListener('unload', function onUnload() { + document.removeEventListener('unload', onUnload, false); + TreeStyleTabUtils.prefs.removePrefListener(observer); + }, false); + + let warnPref = 'extensions.treestyletab.compatibility.STM.warnForNewTabPosition'; + if ( + TreeStyleTabUtils.prefs.getPref(warnPref) && + TreeStyleTabUtils.prefs.getPref('extensions.stm.newTabPosition') != 0 + ) { + let checked = { value : false }; + if (Services.prompt.confirmEx( + null, + TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_title'), + TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_text'), + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), + TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_use_TST'), + TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_use_STM'), + null, + TreeStyleTabUtils.treeBundle.getString('compatibility_STM_warning_never'), + checked + ) == 0) { + TreeStyleTabUtils.prefs.setPref('extensions.stm.newTabPosition', 0); + } + if (checked.value) + TreeStyleTabUtils.prefs.setPref(warnPref, false); + } + + sv.registerTabFocusAllowance(function(aTabBrowser) { + return TreeStyleTabUtils.prefs.getPref('extensions.stm.focusAfterCloseTab') == 0; + }); + } + + // Tab Utilities + // https://addons.mozilla.org/firefox/addon/59961 + if ('tabutils' in window && + TreeStyleTabUtils.getTreePref('compatibility.TabUtilities')) { + TreeStyleTabService.registerTabFocusAllowance(function(aTabBrowser) { + return TreeStyleTabUtils.prefs.getPref('extensions.tabutils.selectOnClose') == 0; + }); + } + + // Remove New Tab Button + // https://addons.mozilla.org/firefox/addon/10535 + if (TreeStyleTabUtils.getTreePref('compatibility.RemoveNewTabButton')) { + sv.extensions.isAvailable('remove-new-tab-button@forerunnerdesigns.com', { ok : function() { + document.documentElement.setAttribute(TreeStyleTabService.kHIDE_NEWTAB, true); + }}); + } + + // IE Tab Plus + // https://addons.mozilla.org/firefox/addon/10909/ + if ('IeTab' in window && + IeTab.prototype && + TreeStyleTabUtils.getTreePref('compatibility.IETabPlus')) { + if (IeTab.prototype.switchTabEngine) + eval('IeTab.prototype.switchTabEngine = '+ + IeTab.prototype.switchTabEngine.toSource().replace( + 'var newTab = ', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + + if (IeTab.prototype.addIeTab) + eval('IeTab.prototype.addIeTab = '+ + IeTab.prototype.addIeTab.toSource().replace( + 'var newTab = ', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + } + + // Locationbar2 + // https://addons.mozilla.org/firefox/addon/locationbar²/ + if ('lb2_alternateStyles' in window && + TreeStyleTabUtils.getTreePref('compatibility.Locationbar2')) { + let listening = false; + let listener = function(aEvent) { + switch (aEvent.type) + { + case 'unload': + document.removeEventListener('unload', listener, false); + document.removeEventListener('beforecustomization', listener, true); + document.removeEventListener('aftercustomization', listener, false); + case 'beforecustomization': + if (gURLBar && listening) + gURLBar.removeEventListener('click', listener, true); + listening = false; + return; + + case 'aftercustomization': + if (gURLBar && !listening) { + gURLBar.addEventListener('click', listener, true); + listening = true; + } + return; + + case 'click': + if (sv.evaluateXPath( + 'ancestor-or-self::*[' + +'contains(concat(" ", normalize-space(@class), " "), " textbox-presentation-segment ")' + +']', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue) + sv.readyToOpenChildTabNow(gBrowser.selectedTab); + return; + } + }; + document.addEventListener('unload', listener, false); + document.addEventListener('beforecustomization', listener, true); + document.addEventListener('aftercustomization', listener, false); + if (gURLBar && !listening) { + gURLBar.addEventListener('click', listener, true); + listening = true; + } + } + + // InstaClick + // https://addons.mozilla.org/firefox/addon/instaclick/ + if ('instaclick' in window && + 'contentAreaClick2' in window.instaclick && + TreeStyleTabUtils.getTreePref('compatibility.InstaClick')) { + eval('instaclick.contentAreaClick2 = '+ + instaclick.contentAreaClick2.toSource().replace( + 'gBrowser.loadOneTab(', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + } + + // Duplicate This Tab + // https://addons.mozilla.org/firefox/addon/duplicate-this-tab/ + if ('duplicatethistab' in window && + 'openLinkWithHistory' in window.duplicatethistab && + TreeStyleTabUtils.getTreePref('compatibility.DuplicateThisTab')) { + eval('duplicatethistab.openLinkWithHistory = '+ + duplicatethistab.openLinkWithHistory.toSource().replace( + 'var newTab = ', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + } + + // Context Search + // http://www.cusser.net/extensions/contextsearch/ + if ('contextsearch' in window && + 'search' in window.contextsearch && + TreeStyleTabUtils.getTreePref('compatibility.ContextSearch')) { + eval('contextsearch.search = '+ + contextsearch.search.toSource().replace( + 'var newTab = ', + 'TreeStyleTabService.readyToOpenChildTab(); $&' + ) + ); + } + + // Tile Tabs + // https://addons.mozilla.org/firefox/addon/tile-tabs/ + if ('tileTabs' in window && + TreeStyleTabUtils.getTreePref('compatibility.TileTabs')) { + if ('allocateTab' in window.tileTabs) + eval('tileTabs.allocateTab = '+ + tileTabs.allocateTab.toSource().replace( + /(tab = gBrowser.addTab)/g, + 'TreeStyleTabService.readyToOpenNextSiblingTabNow(); $1' + ) + ); + if ('doClickBrowser' in window.tileTabs) + eval('tileTabs.doClickBrowser = '+ + tileTabs.doClickBrowser.toSource().replace( + /(newTab = gBrowser.loadOneTab)/g, + 'TreeStyleTabService.readyToOpenNextSiblingTabNow(); $1' + ) + ); + if ('doDropBrowserTile' in window.tileTabs) + eval('tileTabs.doDropBrowserTile = '+ + tileTabs.doDropBrowserTile.toSource().replace( + /(tab = gBrowser.loadOneTab)/g, + 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' + ) + ); + if ('menuActions' in window.tileTabs) + eval('tileTabs.menuActions = '+ + tileTabs.menuActions.toSource().replace( + /(tab = gBrowser.loadOneTab)/g, + 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' + ) + ); + if ('applyLayoutString' in window.tileTabs) + eval('tileTabs.applyLayoutString = '+ + tileTabs.applyLayoutString.toSource().replace( + /(tab = gBrowser.loadOneTab)/g, + 'TreeStyleTabService.readyToOpenNextSiblingTabNow(), $1' + ) + ); + } + + window.setTimeout(function(aSelf) { + aSelf.overrideExtensionsDelayed(); + }, 0, this); +}; + + +TreeStyleTabWindowHelper.overrideExtensionsDelayed = function TSTWH_overrideExtensionsDelayed() { + var sv = this.service; + + // Tab Mix Plus + if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && + 'TabmixTabbar' in window) { + // correct broken appearance of the first tab + var t = gBrowser.treeStyleTab.getFirstTab(gBrowser); + gBrowser.treeStyleTab.initTabAttributes(t); + gBrowser.treeStyleTab.initTabContentsOrder(t); + + eval('gBrowser.openInverseLink = '+ + gBrowser.openInverseLink.toSource().replace( + /(var newTab)/, + 'TreeStyleTabService.readyToOpenChildTab(aTab); $1' + ) + ); + + eval('gBrowser.TMP_openTabNext = '+ + gBrowser.TMP_openTabNext.toSource().replace( + 'this.mCurrentTab._tPos + this.tabContainer.nextTab', + ' (function() {\n' + + ' var tabs = this.treeStyleTab.getDescendantTabs(this.mCurrentTab);\n' + + ' if (tabs.length) {\n' + + ' var index = TreeStyleTabUtils.prefs.getPref("extensions.tabmix.openTabNextInverse") ?\n' + + ' tabs[tabs.length - 1]._tPos :\n' + + ' this.mCurrentTab._tPos ;\n' + + ' if (index < aTab._tPos) index++;\n' + + ' return index;\n' + + ' }\n' + + ' else {\n' + + ' return ($&);\n' + + ' }\n' + + ' }).call(this)' + ) + ); + + gBrowser.treeStyleTab.internallyTabMovingCount--; + } + + // Multi Links + // https://addons.mozilla.org/firefox/addon/13494 + if ('MultiLinks_Wrapper' in window && + 'LinksManager' in MultiLinks_Wrapper && + 'OpenInNewTabs' in MultiLinks_Wrapper.LinksManager && + TreeStyleTabUtils.getTreePref('compatibility.MultiLinks')) { + eval('MultiLinks_Wrapper.LinksManager.OpenInNewTabs = '+ + MultiLinks_Wrapper.LinksManager.OpenInNewTabs.toSource().replace( + '{', + '{\n' + + ' if (!TreeStyleTabService.checkToOpenChildTab(getBrowser()))\n' + + ' TreeStyleTabService.readyToOpenChildTab(getBrowser(), true);' + ).replace( + /(\}\)?)$/, + ' if (TreeStyleTabService.checkToOpenChildTab(getBrowser()))\n' + + ' TreeStyleTabService.stopToOpenChildTab(getBrowser());\n' + + '$1' + ) + ); + } + + // DomainTab + // https://addons.mozilla.org/firefox/addon/13906/ + if ('domaintab' in window && + 'TMP_howToOpen' in domaintab && + TreeStyleTabUtils.getTreePref('compatibility.DomainTab')) { + eval('domaintab.TMP_howToOpen = '+ + domaintab.TMP_howToOpen.toSource().replace( + /(domaintab.DT_openNewTabWith\()/g, + 'TreeStyleTabService.readyToOpenChildTab(); $1' + ) + ); + } + + // Personal Titlebar + // https://addons.mozilla.org/irefox/addon/personal-titlebar/ + if (document.getElementById('personal-titlebar') && + TreeStyleTabUtils.getTreePref('compatibility.PersonalTitlebar')) { + let titlebar = document.getElementById('titlebar'); + let personalTitlebar = document.getElementById('personal-titlebar'); + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case 'beforecustomization': + titlebar.removeEventListener('DOMAttrModified', this, true); + gBrowser.treeStyleTab.destroyTabStrip(personalTitlebar); + break; + + case 'aftercustomization': + titlebar.addEventListener('DOMAttrModified', this, true); + break; + + case 'DOMAttrModified': + if ( + aEvent.attrName == 'hidden' && + gBrowser.tabContainer.parentNode.id == (aEvent.newValue == 'true' ? 'toolbar-menubar' : 'personal-titlebar' ) + ) { + TreeStyleTabService.stopRendering(); + gBrowser.treeStyleTab.syncDestroyTabbar(); + window.setTimeout(function() { + gBrowser.treeStyleTab.syncReinitTabbar(); + TreeStyleTabService.startRendering(); + }, 0); + } + break; + + case 'unload': + titlebar.removeEventListener('DOMAttrModified', this, true); + document.removeEventListener('beforecustomization', this, false); + document.removeEventListener('aftercustomization', this, false); + document.removeEventListener('unload', this, false); + personalTitlebar = null; + break; + } + } + }; + document.addEventListener('beforecustomization', listener, false); + document.addEventListener('aftercustomization', listener, false); + document.addEventListener('unload', listener, false); + titlebar.addEventListener('DOMAttrModified', listener, true); + } + + // TotalToolbar + // http://totaltoolbar.mozdev.org/ + let (menu = document.getElementById('tt-toolbar-properties') && + TreeStyleTabUtils.getTreePref('compatibility.TotalToolbar')) { + if (menu) { + let tabbarToolboxes = ['tt-toolbox-tabright', 'tt-toolbox-tableft'] + .map(document.getElementById, document) + .filter(function(aToolbox) { return aToolbox; }); + let listener = { + handleEvent : function(aEvent) + { + var sv = TreeStyleTabService; + switch (aEvent.type) + { + case 'command': + gBrowser.treeStyleTab.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_WINDOW_RESIZE); + break; + + case 'beforecustomization': + for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) + { + tabbarToolboxes[i].removeAttribute('collapsed'); + } + break; + + case 'aftercustomization': + for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) + { + let toolbox = tabbarToolboxes[i]; + if (!toolbox.firstChild.hasChildNodes()) + toolbox.setAttribute('collapsed', true); + } + break; + + case 'unload': + menu.removeEventListener('command', this, true); + document.removeEventListener('beforecustomization', listener, true); + document.removeEventListener('aftercustomization', listener, false); + document.removeEventListener('unload', this, false); + menu = null; + break; + } + } + }; + menu.addEventListener('command', listener, false); + document.addEventListener('beforecustomization', listener, true); + document.addEventListener('aftercustomization', listener, false); + document.addEventListener('unload', listener, false); + for (let i = 0, maxi = tabbarToolboxes.length; i < maxi; i++) + { + let toolbox = tabbarToolboxes[i]; + if (!toolbox.firstChild.hasChildNodes()) + toolbox.setAttribute('collapsed', true); + } + } + } + + // Tab Control + // https://addons.mozilla.org/firefox/addon/tab-control/ + if ( + TreeStyleTabUtils.getTreePref('compatibility.TabControl') && + 'gTabControl' in window + ) { + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case sv.kEVENT_TYPE_FOCUS_NEXT_TAB: + if (TreeStyleTabUtils.prefs.getPref('tabcontrol.focusLeftOnClose')) + aEvent.preventDefault(); + break; + + case 'unload': + document.removeEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); + break; + } + } + }; + document.addEventListener(sv.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); + document.addEventListener('unload', listener, false); + } + + // Firefox Sync (Weave) + // http://www.mozilla.com/en-US/firefox/sync/ + if ( + ( + 'gFxWeaveGlue' in window || // addon + 'gSyncUI' in window // Firefox 4 built-in + ) && + TreeStyleTabUtils.getTreePref('compatibility.FirefoxSync') + ) { + let ns = {}; + try { // 1.4 + Components.utils.import('resource://services-sync/service.js', ns); + } + catch(e) { // 1.3 + Components.utils.import('resource://weave/service.js', ns); + } + let listener = { + handleEvent : function(aEvent) + { + switch (aEvent.type) + { + case 'TabOpen': + let tab = aEvent.originalTarget + let b = TreeStyleTabService.getTabBrowserFromChild(tab); + if (b.selectedTab.linkedBrowser.currentURI.spec != 'about:sync-tabs') + return; + + let service = ns.Service || ns.Weave /* old name */; + let manager = service.engineManager || service.Engines /* old name */; + let engine = manager.get('tabs'); + + let uri = tab.getAttribute('label'); + if (engine.locallyOpenTabMatchesURL(uri)) + return; + + for (let [guid, client] in Iterator(engine.getAllClients())) + { + if (client.tabs.some(function({ urlHistory }) { + return urlHistory[0] == uri; + })) { + let parent = b.selectedTab; + window.setTimeout(function() { + if (tab.parentNode && !b.treeStyleTab.getParentTab(tab)) + b.treeStyleTab.attachTabTo(tab, parent); + }, 0); + return; + } + } + return; + + case 'unload': + document.removeEventListener('TabOpen', this, true); + document.removeEventListener('unload', this, false); + return; + } + } + }; + document.addEventListener('TabOpen', listener, true); + document.addEventListener('unload', listener, false); + } + +}; diff --git a/modules/autoHide.js b/modules/autoHide.js index 3889c1bc..edd858b7 100644 --- a/modules/autoHide.js +++ b/modules/autoHide.js @@ -1,1404 +1,1404 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['AutoHideBrowser', 'AutoHideWindow']; - -const DEBUG = false; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://gre/modules/Services.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); - - -function AutoHideBrowser(aTabBrowser) -{ - this.init(aTabBrowser); -} -AutoHideBrowser.prototype = { - - kMODE : 'treestyletab-tabbar-autohide-mode', - kMODE_DISABLED : 0, - kMODE_HIDE : 1, - kMODE_SHRINK : 2, - - kAUTOHIDE : 'treestyletab-tabbar-autohide', - - kSTATE : 'treestyletab-tabbar-autohide-state', - kSTATE_HIDDEN : 'hidden', - kSTATE_EXPANDED : 'expanded', - kSTATE_SHRUNKEN : 'shrunken', - - kSHOWN_BY_UNKNOWN : 0, - kSHOWN_BY_SHORTCUT : 1 << 0, - kSHOWN_BY_MOUSEMOVE : 1 << 1, - kSHOWN_BY_FEEDBACK : 1 << 2, - kSHOWN_BY_ANY_REASON : (1 << 0) | (1 << 1) | (1 << 2), - kSHOWHIDE_BY_START : 1 << 3, - kSHOWHIDE_BY_END : 1 << 4, - kSHOWHIDE_BY_POSITION_CHANGE : 1 << 5, - kSHOWHIDE_BY_RESIZE : 1 << 6, - kSHOWHIDE_BY_API : 1 << 8, - kHIDDEN_BY_CLICK : 1 << 7, - - get mode() /* PUBLIC API */ - { - var mode = this.browser.getAttribute(this.kMODE); - return mode ? parseInt(mode) : this.kMODE_DISABLED ; - }, - set mode(aValue) - { - this.browser.setAttribute(this.kMODE, aValue); - return aValue; - }, - - getMode : function AHB_getMode(aTabBrowser) - { - var b = aTabBrowser || this.browser; - var mode = b.getAttribute(this.kMODE); - return mode ? parseInt(mode) : this.kMODE_DISABLED ; - }, - getModeForNormal : function AHB_getModeForNormal(aTabBrowser) - { - var b = aTabBrowser || this.browser; - return parseInt(b.getAttribute(this.kMODE+'-normal') || utils.getTreePref('tabbar.autoHide.mode')); - }, - getModeForFullScreen : function AHB_getModeForFullScreen(aTabBrowser) - { - var b = aTabBrowser || this.browser; - return parseInt(b.getAttribute(this.kMODE+'-fullscreen') || utils.getTreePref('tabbar.autoHide.mode.fullscreen')); - }, - - get state() - { - return this.browser.getAttribute(this.kSTATE) || this.kSTATE_EXPANDED; - }, - get expanded() - { - return this.state == this.kSTATE_EXPANDED; - }, - get shrunken() - { - return this.state == this.kSTATE_SHRUNKEN; - }, - get hidden() - { - return this.state == this.kSTATE_HIDDEN; - }, - - updateMode : function AHB_updateMode() - { - this.end(); - // update internal property after the appearance of the tab bar is updated. - var w = this.window; - w.setTimeout(function(aSelf) { - aSelf.mode = (w.fullScreen && prefs.getPref('browser.fullscreen.autohide')) ? - aSelf.getModeForFullScreen() : - aSelf.getModeForNormal() ; - if (aSelf.mode != aSelf.kMODE_DISABLED) - aSelf.start(); - }, 0, this); - }, - - togglerSize : 0, - sensitiveArea : 7, - contentAreaScreenEnabled : true, - - closeButtonsMode : -1, - CLOSE_BUTTONS_ONLY_ON_CURRENT_TAB : 0, - CLOSE_BUTTONS_ON_ALL_TABS : 1, - CLOSE_BUTTONS_DISABLED : 2, - CLOSE_BUTTONS_ON_TABBAR : 3, - - get XOffset() - { - var sv = this.treeStyleTab; - switch (this.mode) - { - case this.kMODE_DISABLED: - return 0; - - case this.kMODE_HIDE: - let offset = this.width + this.splitterWidth; - let resizer = this.resizer; - if (sv.position == 'left') { - offset += this.togglerSize; - if (resizer) - offset += resizer.boxObject.width; - } - else if (sv.position == 'right') { - offset -= this.togglerSize; - if (resizer) - offset -= resizer.boxObject.width; - } - return offset; - - default: - case this.kMODE_SHRINK: - return utils.getTreePref('tabbar.width') - - utils.getTreePref('tabbar.shrunkenWidth'); - } - }, - get YOffset() - { - return this.height; - }, - extraXOffset : 0, - extraYOffset : 0, - - get currentXOffset() - { - var sv = this.treeStyleTab; - return ( - sv.position == 'left' && - this.mode != this.kMODE_DISABLED && - this.expanded - ) ? this.XOffset : 0 ; - }, - get currentYOffset() - { - var sv = this.treeStyleTab; - return ( - sv.position == 'top' && - this.mode != this.kMODE_DISABLED && - this.expanded - ) ? this.YOffset : 0 ; - }, - - get screen() - { - return this.document.getElementById('treestyletab-autohide-content-area-screen'); - }, - get resizer() - { - return this.document.getElementById('treestyletab-tabbar-resizer-splitter'); - }, - - start : function AHB_start(aReason) - { - if (this.enabled) return; - this.enabled = true; - aReason = aReason || 0; - - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - this.screen.hidePopup(); - - sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_EXPANDED); - - if (!(aReason & this.kSHOWHIDE_BY_API)) { - b.addEventListener('mousedown', this, true); - b.addEventListener('mouseup', this, true); - b.addEventListener('dragover', this, true); - b.addEventListener('dragleave', this, true); - sv.tabStrip.addEventListener('mousedown', this, true); - sv.tabStrip.addEventListener('mouseup', this, true); - if (this.shouldListenMouseMove) - this.startListenMouseMove(); - if (b == w.gBrowser && sv.shouldListenKeyEventsForAutoHide) - w.TreeStyleTabService.startListenKeyEventsFor(sv.LISTEN_FOR_AUTOHIDE); - this.userActionListening = true; - } - w.addEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); - w.addEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); - - this.updateTransparency(); - - this.showHideInternal(this.kSHOWHIDE_BY_START | aReason); - - b.treeStyleTab.fixTooNarrowTabbar(); - }, - - end : function AHB_end() - { - if (!this.enabled) return; - this.enabled = false; - - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - this.show(this.kSHOWHIDE_BY_END); - - this.screen.hidePopup(); - - if (this.userActionListening) { - b.removeEventListener('mousedown', this, true); - b.removeEventListener('mouseup', this, true); - b.removeEventListener('dragover', this, true); - b.removeEventListener('dragleave', this, true); - sv.tabStrip.removeEventListener('mousedown', this, true); - sv.tabStrip.removeEventListener('mouseup', this, true); - this.endListenMouseMove(); - if (b == w.gBrowser) - w.TreeStyleTabService.endListenKeyEventsFor(sv.LISTEN_FOR_AUTOHIDE); - this.userActionListening = false; - } - w.removeEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); - w.removeEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); - - this.updateTransparency(); - - sv.removeTabbrowserAttribute(this.kAUTOHIDE); - sv.removeTabbrowserAttribute(this.kSTATE); - - if (sv.isVertical) - sv.setTabStripAttribute('width', this.widthFromMode); - }, - - // fullscreen - - startForFullScreen : function AHB_startForFullScreen() - { - this.mode = this.getMode(); - this.end(); - this.mode = prefs.getPref('browser.fullscreen.autohide') ? - this.getModeForFullScreen() : - this.kMODE_DISABLED ; - if (this.mode != this.kMODE_DISABLED) { - this.start(); - this.treeStyleTab.removeTabbrowserAttribute('moz-collapsed'); - } - }, - - endForFullScreen : function AHB_endForFullScreen() - { - this.mode = this.getModeForFullScreen(); - this.end(); - this.mode = utils.getTreePref('tabbar.autoHide.mode'); - this.treeStyleTab.checkTabsIndentOverflow(); - if (this.mode != this.kMODE_DISABLED) - this.start(); - }, - - // mousemove - - startListenMouseMove : function AHB_startListenMouseMove() - { - if (this.mouseMoveListening) return; - - this.browser.addEventListener('mousemove', this, true); - this.screen.addEventListener('mousemove', this, true); - this.treeStyleTab.tabStrip.addEventListener('mousemove', this, true); - - this.mouseMoveListening = true; - }, - - endListenMouseMove : function AHB_endListenMouseMove() - { - if (!this.mouseMoveListening) return; - - this.browser.removeEventListener('mousemove', this, true); - this.screen.removeEventListener('mousemove', this, true); - this.treeStyleTab.tabStrip.removeEventListener('mousemove', this, true); - - this.mouseMoveListening = false; - }, - - get shouldListenMouseMove() - { - return utils.getTreePref('tabbar.autoShow.mousemove'); - }, - - get shouldListenKeyEventsForAutoHide() - { - return utils.getTreePref('tabbar.autoShow.accelKeyDown') || - utils.getTreePref('tabbar.autoShow.tabSwitch'); - }, - - showHideOnMouseMove : function AHB_showHideOnMouseMove(aEvent) - { - var position = this.getMousePosition(aEvent); - if (position == this.MOUSE_POSITION_UNKNOWN) - return; - - this.cancelShowHideOnMouseMove(); - this.showHideContentsAreaScreen(); - - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - var shouldShow = position & this.MOUSE_POSITION_SENSITIVE; - if (this.expanded) { // currently shown, let's hide it. - if (shouldShow) { - this.show(this.kSHOWN_BY_MOUSEMOVE); - this.cancelDelayedShowForShortcut(); - } - else if ( - !shouldShow && - utils.getTreePref('tabbar.autoShow.mousemove') - ) { - this.showHideOnMouseMoveTimer = w.setTimeout( - function(aSelf) { - aSelf.cancelDelayedShowForShortcut(); - aSelf.hide(aSelf.kSHOWN_BY_MOUSEMOVE); - }, - utils.getTreePref('tabbar.autoHide.delay'), - this - ); - } - } - else if (shouldShow) { // currently shown, let's show it. - this.showHideOnMouseMoveTimer = w.setTimeout( - function(aSelf) { - aSelf.cancelDelayedShowForShortcut(); - aSelf.show(aSelf.kSHOWN_BY_MOUSEMOVE); - }, - utils.getTreePref('tabbar.autoHide.delay'), - this - ); - } - - b = null; - }, - getMousePosition : function AHB_getMousePosition(aEvent) - { - var w = this.window; - if ('gestureInProgress' in w && w.gestureInProgress) - return this.MOUSE_POSITION_UNKNOWN; - - var sv = this.treeStyleTab; - var b = this.browser; - var pos = sv.position; - var box = this.getContentsAreaBox(); - - var sensitiveArea = this.sensitiveArea; - if (this.shrunken) { - let clickable; - let resizable = !sv.fixed; - if (resizable & - this.widthFromMode > 24 && - (clickable = this.getNearestClickableBox(aEvent))) { - /* For resizing of shrunken tab bar and clicking closeboxes, - we have to shrink sensitive area. */ - sensitiveArea = -(clickable.width + clickable.padding); - } - else if (resizable && this.resizer) - sensitiveArea = -this.resizer.boxObject.width; - else - sensitiveArea = 0; - } - - if ( - pos == 'left' ? - (aEvent.screenX > box.screenX + sensitiveArea) : - pos == 'right' ? - (aEvent.screenX < box.screenX + box.width - sensitiveArea) : - pos == 'bottom' ? - (aEvent.screenY < box.screenY + box.height - sensitiveArea) : - (aEvent.screenY > box.screenY + sensitiveArea) - ) { - return this.MOUSE_POSITION_OUTSIDE; - } - - if ( - pos == 'left' ? - (aEvent.screenX <= box.screenX - sensitiveArea) : - pos == 'right' ? - (aEvent.screenX >= box.screenX + box.width + sensitiveArea) : - pos == 'bottom' ? - (aEvent.screenY >= box.screenY + box.height + sensitiveArea) : - (aEvent.screenY <= box.screenY - sensitiveArea) - ) { - return this.MOUSE_POSITION_INSIDE; - } - - return this.MOUSE_POSITION_NEAR; - }, - getContentsAreaBox : function AHB_getContentsAreaBox() - { - var sv = this.treeStyleTab; - var b = this.browser; - var box = b.mCurrentBrowser.boxObject; - var xoffset = (this.shrunken || this.hidden) ? 0 : this.XOffset ; - box = { - screenX : box.screenX + (sv.position == 'left' ? xoffset : 0 ), - screenY : box.screenY, - width : box.width - xoffset, - height : box.height - }; - return box; - }, - MOUSE_POSITION_UNKNOWN : 0, - MOUSE_POSITION_OUTSIDE : (1 << 0), - MOUSE_POSITION_INSIDE : (1 << 1), - MOUSE_POSITION_NEAR : (1 << 2), - MOUSE_POSITION_SENSITIVE : (1 << 1) | (1 << 2), - getNearestClickableBox : function AHB_getNearestClickableBox(aEvent) - { - var sv = this.treeStyleTab; - var tab = sv.getTabFromCoordinates(aEvent); - if (!tab) - return null; - - var position = sv.invertedScreenPositionProp; - var size = sv.invertedSizeProp; - var coordinate = aEvent[sv.invertedScreenPositionProp]; - var tabbox = tab.boxObject; - - var closebox; - if (this.closeButtonsMode != this.CLOSE_BUTTONS_DISABLED && - this.closeButtonsMode != this.CLOSE_BUTTONS_ON_TABBAR && - (closebox = sv.getTabClosebox(tab)) && - (closebox = closebox.boxObject) && - closebox.width && closebox.height) { - let padding = Math.min( - closebox[position] - tabbox[position], - (tabbox[position] + tabbox[size]) - (closebox[position] + closebox[size]) - ); - if (closebox[position] - padding <= coordinate && - closebox[position] + closebox[size] + padding >= coordinate) - return this.cloneBoxObject(closebox, { padding : padding }); - } - - var twisty; - if (sv.canCollapseSubtree(tab) && - (twisty = sv.getTabTwisty(tab)) && - (twisty = twisty.boxObject) && - twisty.width && twisty.height) { - let padding = Math.min( - twisty[position] - tabbox[position], - (tabbox[position] + tabbox[size]) - (twisty[position] + twisty[size]) - ); - if (twisty[position] - padding <= coordinate && - twisty[position] + twisty[size] + padding >= coordinate) - return this.cloneBoxObject(twisty, { padding : padding }); - } - - return null; - }, - cloneBoxObject : function AHB_cloneBoxObject(aBoxObject, aOverride) - { - var box = {}; - for (let i in aBoxObject) - { - if (typeof aBoxObject[i] != 'function') - box[i] = aBoxObject[i]; - } - Object.keys(aOverride).forEach(function(aKey) { - box[aKey] = aOverride[aKey]; - }); - return box; - }, - - cancelShowHideOnMouseMove : function AHB_cancelShowHideOnMouseMove() - { - if (this.showHideOnMouseMoveTimer) { - this.window.clearTimeout(this.showHideOnMouseMoveTimer); - this.showHideOnMouseMoveTimer = null; - } - }, - - // feedback - - showForFeedback : function AHB_showForFeedback() - { - if (!this.enabled || - !utils.getTreePref('tabbar.autoShow.feedback')) - return; - - var w = this.window; - if (this.delayedShowForFeedbackTimer) { - w.clearTimeout(this.delayedShowForFeedbackTimer); - this.delayedShowForFeedbackTimer = null; - } - this.cancelHideForFeedback(); - this.delayedShowForFeedbackTimer = w.setTimeout( - function(aSelf) { - aSelf.delayedShowForFeedbackTimer = null; - aSelf.delayedShowForFeedback(); - }, - 100, - this - ); - }, - - delayedShowForFeedback : function AHB_delayedShowForFeedback() - { - this.show(this.kSHOWN_BY_FEEDBACK); - this.cancelHideForFeedback(); - this.delayedHideTabbarForFeedbackTimer = this.window.setTimeout( - function(aSelf) { - aSelf.delayedHideTabbarForFeedbackTimer = null; - aSelf.hide(aSelf.kSHOWN_BY_FEEDBACK); - }, - utils.getTreePref('tabbar.autoShow.feedback.delay'), - this - ); - }, - - cancelHideForFeedback : function AHB_cancelHideForFeedback() - { - if (this.delayedHideTabbarForFeedbackTimer) { - this.window.clearTimeout(this.delayedHideTabbarForFeedbackTimer); - this.delayedHideTabbarForFeedbackTimer = null; - } - }, - - setWidth : function AHB_setWidth(aWidth, aForceExpanded) - { - if (aForceExpanded || - this.expanded || - this.mode != this.kMODE_SHRINK) - utils.setTreePref('tabbar.width', this.treeStyleTab.maxTabbarWidth(aWidth)); - else - utils.setTreePref('tabbar.shrunkenWidth', this.treeStyleTab.maxTabbarWidth(aWidth)); - }, - - updateMenuItem : function AHB_updateMenuItem(aNode) - { - if (!aNode) return; - - if (this.mode != this.kMODE_DISABLED) - aNode.setAttribute('checked', true); - else - aNode.removeAttribute('checked'); - }, - - // show/hide tabbar - - get width() - { - if (this.expanded) { - this._width = this.treeStyleTab.tabStrip.boxObject.width || this._width; - } - return this._width; - }, - set width(aNewWidth) - { - this._width = aNewWidth; - return this._width; - }, - _width : 0, - - get widthFromMode() - { - return (this.shrunken) ? - utils.getTreePref('tabbar.shrunkenWidth') : - utils.getTreePref('tabbar.width') ; - }, - get placeHolderWidthFromMode() - { - return (this.mode == this.kMODE_SHRINK) ? - utils.getTreePref('tabbar.shrunkenWidth') : - utils.getTreePref('tabbar.width') ; - }, - - get height() - { - if (this.expanded) { - this._height = this.treeStyleTab.tabStrip.boxObject.height; - } - return this._height; - }, - set height(aNewHeight) - { - this._height = aNewHeight; - return this._height; - }, - _height : 0, - - get splitterWidth() - { - if (this.expanded) { - var splitter = this.document.getAnonymousElementByAttribute(this.browser, 'class', this.treeStyleTab.kSPLITTER); - this._splitterWidth = (splitter ? splitter.boxObject.width : 0 ); - } - return this._splitterWidth; - }, - set splitterWidth(aNewWidth) - { - this._splitterWidth = aNewWidth; - return this._splitterWidth; - }, - _splitterWidth : 0, - - showHideInternal : function AHB_showHideInternal(aReason) - { - var sv = this.treeStyleTab; - var b = this.browser; - var pos = sv.position; - - aReason = aReason || 0; - - if (this.expanded) { // to be hidden or shrunken - let reason = this.kSHOWN_BY_UNKNOWN; - if (aReason & this.kSHOWHIDE_BY_API) - reason = aReason; - this.onHiding(); - this.showHideReason = reason; - } - else { // to be shown or expanded - this.onShowing(); - this.showHideReason = aReason || this.showHideReason || this.kSHOWN_BY_UNKNOWN; - } - - if (DEBUG) { - let humanReadableReason = - (aReason & this.kSHOWN_BY_SHORTCUT ? 'shortcut ' : '' ) + - (aReason & this.kSHOWN_BY_MOUSEMOVE ? 'mousemove ' : '' ) + - (aReason & this.kSHOWN_BY_FEEDBACK ? 'feedback ' : '' ) + - (aReason & this.kSHOWHIDE_BY_START ? 'start ' : '' ) + - (aReason & this.kSHOWHIDE_BY_END ? 'end ' : '' ) + - (aReason & this.kSHOWHIDE_BY_POSITION_CHANGE ? 'positionchange ' : '' ) + - (aReason & this.kSHOWHIDE_BY_RESIZE ? 'resize ' : '' ) + - (aReason & this.kHIDDEN_BY_CLICK ? 'click ' : '' ); - if (this.expanded) - dump('autoHide: show by ' + humanReadableReason + '\n'); - else - dump('autoHide: hide by ' + humanReadableReason + '\n'); - } - - this.fireStateChangingEvent(); - - if (this.expanded) - sv.setTabbrowserAttribute(this.kAUTOHIDE, 'show'); - b.mTabContainer.adjustTabstrip(); - sv.checkTabsIndentOverflow(); - - this.window.setTimeout(function(aSelf) { - aSelf.fireStateChangeEvent(); - aSelf.showHideContentsAreaScreen(); - }, 0, this); - }, - showHideContentsAreaScreen : function AHB_showHideContentsAreaScreen() - { - // this.browser.contentWindow doesn't currently work in e10s - // mode, use this.browser.mCurrentBrowser.contentWindow as a - // workaround until bug 1042680 is fixed - if ( - this.expanded && - this.contentAreaScreenEnabled && - Services.focus.activeWindow && - Services.focus.activeWindow.top == this.window && - this.findPluginArea(this.browser.mCurrentBrowser.contentWindow) - ) { - let box = this.getContentsAreaBox(); - let style = this.screen.style; - let width = Math.min(box.width, this.window.screen.availWidth - box.screenX); - let height = Math.min(box.height, this.window.screen.availHeight - box.screenY); - style.width = width+'px'; - style.height = height+'px'; - if (this.screen.state == 'open') - this.screen.moveTo(box.screenX, box.screenY); - else - this.screen.openPopupAtScreen(box.screenX, box.screenY, false); - this.screen.setAttribute('popup-shown', true); - } - else { - this.screen.removeAttribute('popup-shown'); - if (this.screen.state != 'close') - this.screen.hidePopup(); - } - }, - findPluginArea : function AHB_findPluginArea(aFrame) - { - return aFrame.document.querySelector('embed, object') || - Array.some(aFrame.frames, AHB_findPluginArea); - }, - - show : function AHB_show(aReason) /* PUBLIC API */ - { - if (this.showHideReason & this.kSHOWHIDE_BY_API) { - this.end(); - return; - } - - if (aReason) { - this.showHideReason |= aReason; - } - if (!this.expanded) - this.showHideInternal(); - }, - - hide : function AHB_hide(aReason) /* PUBLIC API */ - { - if (!this.enabled) { - this.start(aReason | this.kSHOWHIDE_BY_API); - return; - } - - if (aReason) { - if (aReason == this.kSHOWN_BY_ANY_REASON) - this.showHideReason &= ~this.kSHOWN_BY_ANY_REASON; - else if (this.showHideReason & aReason) - this.showHideReason ^= aReason; - - if (this.showHideReason & this.kSHOWN_BY_ANY_REASON) - return; - } - if (this.expanded) - this.showHideInternal(); - }, - - onShowing : function AHB_onShowing() - { - var sv = this.treeStyleTab; - var b = this.browser; - var pos = sv.position; - - sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_EXPANDED); - - switch (this.mode) - { - case this.kMODE_DISABLED: - break; - - case this.kMODE_HIDE: - sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); - break; - - default: - case this.kMODE_SHRINK: - if (pos == 'left' || pos == 'right') { - let width = sv.maxTabbarWidth(utils.getTreePref('tabbar.width')); - sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); - } - break; - } - }, - - onHiding : function AHB_onHiding() - { - var sv = this.treeStyleTab; - var b = this.browser; - var pos = sv.position; - - var box = (sv.tabStripPlaceHolder || sv.tabStrip).boxObject; - - this.tabbarHeight = box.height; - this.width = box.width || this.width; - var splitter = this.document.getAnonymousElementByAttribute(b, 'class', sv.kSPLITTER); - this.splitterWidth = (splitter ? splitter.boxObject.width : 0 ); - - switch (this.mode) - { - case this.kMODE_DISABLED: - sv.setTabbrowserAttribute(this.kAUTOHIDE, 'hidden'); - sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_HIDDEN); - break; - - case this.kMODE_HIDE: - sv.updateLastScrollPosition(); - sv.setTabbrowserAttribute(this.kAUTOHIDE, 'hidden'); - sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_HIDDEN); - sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); - break; - - default: - case this.kMODE_SHRINK: - sv.setTabbrowserAttribute(this.kAUTOHIDE, 'show'); - sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_SHRUNKEN); - if (pos == 'left' || pos == 'right') - sv.setTabStripAttribute('width', utils.getTreePref('tabbar.shrunkenWidth')); - sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); - break; - } - }, - - fireStateChangingEvent : function AHB_fireStateChangingEvent() - { - var data = { - shown : this.expanded, - state : this.state - }; - - /* PUBLIC API */ - this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING, this.browser, true, false, data); - // for backward compatibility - this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING.replace(/^nsDOM/, ''), this.browser, true, false, data); - }, - - fireStateChangeEvent : function AHB_fireStateChangeEvent() - { - var data = { - shown : this.expanded, - state : this.state, - xOffset : this.XOffset, - yOffset : this.YOffset - }; - - /* PUBLIC API */ - this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE, this.browser, true, false, data); - // for backward compatibility - this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE.replace(/^nsDOM/, ''), this.browser, true, false, data); - }, - - - - - updateTransparency : function AHB_updateTransparency() - { - var sv = this.treeStyleTab; - sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); - }, - - // event handling - - observe : function AHB_observe(aSubject, aTopic, aData) - { - switch (aTopic) - { - case 'nsPref:changed': - this.onPrefChange(aData); - break; - - default: - break; - } - }, - - domains : [ - 'extensions.treestyletab.', - 'browser.fullscreen.autohide', - 'browser.tabs.closeButtons' - ], - - onPrefChange : function AHB_onPrefChange(aPrefName) - { - // ignore after destruction - if (!this.window || !this.window.TreeStyleTabService) - return; - - var value = prefs.getPref(aPrefName); - switch (aPrefName) - { - case 'extensions.treestyletab.tabbar.autoHide.mode': - if (!this.window.TreeStyleTabService.shouldApplyNewPref) return; - this.browser.setAttribute(this.kMODE+'-normal', value); - this.updateMode(); - return; - - case 'extensions.treestyletab.tabbar.autoHide.mode.fullscreen': - if (!this.window.TreeStyleTabService.shouldApplyNewPref) return; - this.browser.setAttribute(this.kMODE+'-fullscreen', value); - this.updateMode(); - return; - - case 'extensions.treestyletab.tabbar.autoShow.mousemove': - case 'extensions.treestyletab.tabbar.autoShow.accelKeyDown': - case 'extensions.treestyletab.tabbar.autoShow.feedback': - if (this.enabled && this.shouldListenMouseMove) - this.startListenMouseMove(); - else - this.endListenMouseMove(); - return; - - case 'extensions.treestyletab.tabbar.autoHide.area': - this.sensitiveArea = value; - return; - - case 'extensions.treestyletab.tabbar.togglerSize': - this.togglerSize = value; - var toggler = this.document.getAnonymousElementByAttribute(this.browser, 'class', this.treeStyleTab.kTABBAR_TOGGLER); - toggler.style.minWidth = toggler.style.minHeight = value+'px'; - if (this.togglerSize <= 0) - toggler.setAttribute('collapsed', true); - else - toggler.removeAttribute('collapsed'); - return; - - case 'extensions.treestyletab.tabbar.autoHide.contentAreaScreen.enabled': - return this.contentAreaScreenEnabled = value; - - case 'browser.fullscreen.autohide': - if (!this.window.fullScreen) return; - this.end(); - this.mode = value ? - this.getModeForFullScreen() : - this.kMODE_DISABLED ; - if (this.mode != this.kMODE_DISABLED) - this.start(); - return; - - case 'browser.tabs.closeButtons': - return this.closeButtonsMode = value; - - default: - return; - } - }, - - handleEvent : function AHB_handleEvent(aEvent) - { - switch (aEvent.type) - { - case 'mousedown': - return this.onMouseDown(aEvent); - - case 'mouseup': - return this.onMouseUp(aEvent); - - case 'mousemove': - return this.handleMouseMove(aEvent); - - case 'TabOpen': - case 'TabClose': - return this.showForFeedback(); - - case 'TabMove': - if (!this.treeStyleTab.subTreeMovingCount && !this.treeStyleTab.internallyTabMovingCount) - this.showForFeedback(); - return; - - case 'select': - if (!this.window.TreeStyleTabService.accelKeyPressed) - this.showForFeedback(); - return; - - case 'dragover': - return this.onDragOver(aEvent); - - case 'dragleave': - return this.onDragLeave(aEvent); - - case this.treeStyleTab.kEVENT_TYPE_TABBAR_POSITION_CHANGING: - this.isResizing = false; - return; - - case this.treeStyleTab.kEVENT_TYPE_TABBAR_POSITION_CHANGED: - if (this.enabled) - this.window.setTimeout(function(aSelf) { - aSelf.show(this.kSHOWHIDE_BY_POSITION_CHANGE); - aSelf.hide(this.kSHOWHIDE_BY_POSITION_CHANGE); - }, 0, this); - this.updateTransparency(); - return; - - case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN: - return this.onKeyDown(aEvent.detail.sourceEvent); - - case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START: - this.cancelDelayedShowForShortcut(); - if (this.enabled && - utils.getTreePref('tabbar.autoShow.tabSwitch') && - ( - aEvent.detail.scrollDown || - aEvent.detail.scrollUp || - ( // when you release "shift" key - this.expanded && - aEvent.detail.standBy && - aEvent.detail.onlyShiftKey - ) - )) - this.show(this.kSHOWN_BY_SHORTCUT); - return; - - case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END: - this.cancelDelayedShowForShortcut(); - if (this.enabled) - this.hide(this.kSHOWN_BY_SHORTCUT); - return; - - case this.treeStyleTab.kEVENT_TYPE_PRINT_PREVIEW_ENTERED: - this.hide(this.kSHOWHIDE_BY_END); - this.endListenMouseMove(); - return; - - case this.treeStyleTab.kEVENT_TYPE_PRINT_PREVIEW_EXITED: - if (this.enabled && this.shouldListenMouseMove) - this.startListenMouseMove(); - return; - } - }, - - onMouseDown : function AHB_onMouseDown(aEvent) - { - var sv = this.treeStyleTab; - var w = this.window; - if ( - !this.isResizing && - sv.evaluateXPath( - 'ancestor-or-self::*[@class="'+sv.kSPLITTER+'"]', - aEvent.originalTarget || aEvent.target, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue - ) { - this.isResizing = true; - sv.setTabbrowserAttribute(sv.kRESIZING, true); - } - this.cancelShowHideOnMouseMove(); - if ( - this.enabled && - this.expanded && - ( - aEvent.originalTarget.ownerDocument != this.document || - !sv.getTabBrowserFromChild(aEvent.originalTarget) - ) - ) - this.hide(this.kHIDDEN_BY_CLICK); - this.lastMouseDownTarget = aEvent.originalTarget.localName; - }, - - onMouseUp : function AHB_onMouseUp(aEvent) - { - var sv = this.treeStyleTab; - if (aEvent.originalTarget && - sv.evaluateXPath( - 'ancestor-or-self::*[@class="'+sv.kSPLITTER+'"]', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue) { - this.isResizing = false; - sv.removeTabbrowserAttribute(sv.kRESIZING); - } - this.cancelShowHideOnMouseMove(); - this.lastMouseDownTarget = null; - }, - - handleMouseMove : function AHB_handleMouseMove(aEvent) - { - var sv = this.treeStyleTab; - if (this.isResizing && - /^(scrollbar|thumb|slider|scrollbarbutton)$/i.test(this.lastMouseDownTarget)) - return true; - - if ( - !aEvent.shiftKey && - !sv.isPopupShown() && - ( - !this.expanded || - this.showHideReason & this.kSHOWN_BY_ANY_REASON - ) && - !this.lastMouseDownTarget - ) - this.showHideOnMouseMove(aEvent); - return true; - }, - - onDragOver : function AHB_onDragOver(aEvent) - { - if (this.expanded) - return; - - var position = this.getMousePosition(aEvent); - if (!(position & this.MOUSE_POSITION_SENSITIVE)) - return; - - var draggedTabs = this.window['piro.sakura.ne.jp'].tabsDragUtils.getSelectedTabs(aEvent); - if ( - draggedTabs.length || - this.treeStyleTab.tabbarDNDObserver.retrieveURLsFromDataTransfer(aEvent.dataTransfer).length - ) { - this.show(this.kSHOWN_BY_MOUSEMOVE); - - if (this._autoHideOnDragLeaveTimer) { - this.window.clearTimeout(this._autoHideOnDragLeaveTimer); - delete this._autoHideOnDragLeaveTimer; - } - } - }, - - onDragLeave : function AHB_onDragLeave(aEvent) - { - if (!this.expanded) - return; - - if (this._autoHideOnDragLeaveTimer) - this.window.clearTimeout(this._autoHideOnDragLeaveTimer); - - var position = this.getMousePosition(aEvent); - if (position & this.MOUSE_POSITION_SENSITIVE) - return; - - this._autoHideOnDragLeaveTimer = this.window.setTimeout(function(aSelf) { - delete aSelf._autoHideOnDragLeaveTimer; - aSelf.hide(aSelf.kSHOWN_BY_MOUSEMOVE); - }, 100, this); - }, - - onKeyDown : function AHB_onKeyDown(aEvent) - { - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - if (this.delayedShowForShortcutDone) - this.cancelDelayedShowForShortcut(); - - if ( - sv.getTabs(b).length > 1 && - !aEvent.altKey && - w.TreeStyleTabService.accelKeyPressed - ) { - if (this.enabled && - utils.getTreePref('tabbar.autoShow.accelKeyDown') && - !this.delayedAutoShowTimer && - !this.delayedShowForShortcutTimer) { - this.delayedShowForShortcutTimer = w.setTimeout( - function(aSelf) { - aSelf.delayedShowForShortcutDone = true; - aSelf.show(aSelf.kSHOWN_BY_SHORTCUT); - sv = null; - b = null; - }, - utils.getTreePref('tabbar.autoShow.accelKeyDown.delay'), - this - ); - this.delayedShowForShortcutDone = false; - } - } - else { - if (this.enabled) - this.hide(this.kSHOWN_BY_SHORTCUT); - } - }, - - cancelDelayedShowForShortcut : function AHB_cancelDelayedShowForShortcut() - { - if (this.delayedShowForShortcutTimer) { - this.window.clearTimeout(this.delayedShowForShortcutTimer); - this.delayedShowForShortcutTimer = null; - } - }, - - delayedShowForShortcutTimer : null, - delayedShowForShortcutDone : true, - - init : function AHB_init(aTabBrowser) - { - this.browser = aTabBrowser; - this.document = aTabBrowser.ownerDocument; - this.window = this.document.defaultView; - this.treeStyleTab = aTabBrowser.treeStyleTab; - - var sv = this.treeStyleTab; - var b = this.browser; - - this.enabled = false; - this.mouseMoveListening = false; - this.showHideReason = this.kSHOWN_BY_UNKNOWN; - this.lastMouseDownTarget = null; - this.isResizing = false; - - this.showHideOnMouseMoveTimer = null; - this.delayedShowForFeedbackTimer = null; - - b.setAttribute(this.kMODE+'-normal', utils.getTreePref('tabbar.autoHide.mode')); - b.setAttribute(this.kMODE+'-fullscreen', utils.getTreePref('tabbar.autoHide.mode.fullscreen')); - prefs.addPrefListener(this); - this.onPrefChange('browser.tabs.closeButtons'); - this.onPrefChange('extensions.treestyletab.tabbar.autoHide.area'); - this.onPrefChange('extensions.treestyletab.tabbar.togglerSize'); - this.onPrefChange('extensions.treestyletab.tabbar.autoHide.contentAreaScreen.enabled'); - this.window.setTimeout(function(aSelf) { - aSelf.onPrefChange('extensions.treestyletab.tabbar.autoHide.mode'); - }, 0, this); - - b.mTabContainer.addEventListener('TabOpen', this, false); - b.mTabContainer.addEventListener('TabClose', this, false); - b.mTabContainer.addEventListener('TabMove', this, false); - b.mTabContainer.addEventListener('select', this, false); - b.addEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGING, this, false); - b.addEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); - b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, this, false); - b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, this, false); - b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); - }, - - destroy : function AHB_destroy() - { - this.end(); - prefs.removePrefListener(this); - - var sv = this.treeStyleTab; - var b = this.browser; - b.mTabContainer.removeEventListener('TabOpen', this, false); - b.mTabContainer.removeEventListener('TabClose', this, false); - b.mTabContainer.removeEventListener('TabMove', this, false); - b.mTabContainer.removeEventListener('select', this, false); - b.removeEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGING, this, false); - b.removeEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); - b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, this, false); - b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, this, false); - b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); - - delete this.treeStyleTab; - delete this.browser; - delete this.document; - delete this.window; - }, - - saveCurrentState : function AHB_saveCurrentState() - { - var b = this.browser; - var prefs = { - 'tabbar.autoHide.mode' : this.getModeForNormal(b), - 'tabbar.autoHide.mode.fullscreen' : this.getModeForFullScreen(b), - }; - for (var i in prefs) - { - if (utils.getTreePref(i) != prefs[i]) - utils.setTreePref(i, prefs[i]); - } - } - -}; - -function AutoHideWindow(aWindow) -{ - this.init(aWindow); -} -AutoHideWindow.prototype = { - get browser() - { - return this.treeStyleTab.browser; - }, - -// mode - - getMode : function AHW_getMode(aTabBrowser) - { - var b = aTabBrowser || this.browser; - var mode = b.getAttribute(AutoHideBrowser.prototype.kMODE); - return mode ? parseInt(mode) : AutoHideBrowser.prototype.kMODE_DISABLED ; - }, - - get mode() /* PUBLIC API */ - { - var mode = this.getMode(); - if (mode == AutoHideBrowser.prototype.kMODE_SHRINK && - this.treeStyleTab.position != 'left' && - this.treeStyleTab.position != 'right') - return AutoHideBrowser.prototype.kMODE_HIDE; - return mode; - }, - - set mode(aValue) - { - var b = aTabBrowser || this.browser; - b.setAttribute(AutoHideBrowser.prototype.kMODE, aValue); - return aValue; - }, - - toggleMode : function AHW_toggleMode(aTabBrowser) /* PUBLIC API */ - { - var b = aTabBrowser || this.browser; - var w = this.window; - - var key = 'tabbar.autoHide.mode'; - var toggleKey = 'tabbar.autoHide.mode.toggle'; - if (w.fullScreen) { - key += '.fullscreen'; - toggleKey += '.fullscreen'; - } - - var mode = this.getMode(b) == AutoHideBrowser.prototype.kMODE_DISABLED ? - utils.getTreePref(toggleKey) : - AutoHideBrowser.prototype.kMODE_DISABLED ; - - utils.setTreePref(key, mode); - b.setAttribute(AutoHideBrowser.prototype.kMODE+'-'+(w.fullScreen ? 'fullscreen' : 'normal' ), mode); - b.treeStyleTab.autoHide.updateMode(); - }, - -// for shortcuts - - updateKeyListeners : function AHW_updateKeyListeners() - { - // ignore after destruction - if (!this.window || !this.window.TreeStyleTabService) - return; - - if ( - this.getMode() && - this.shouldListenKeyEvents - ) { - this.treeStyleTab.startListenKeyEventsFor(this.treeStyleTab.LISTEN_FOR_AUTOHIDE); - } - else { - this.treeStyleTab.endListenKeyEventsFor(this.treeStyleTab.LISTEN_FOR_AUTOHIDE); - } - var w = this.window; - w.setTimeout(function() { - if (w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) - return; - var count = 0; - var resizeTimer = w.setInterval(function(){ - if (w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) - return w.clearInterval(resizeTimer); - - if (++count > 100 || w.innerHeight > 0) { - w.clearInterval(resizeTimer); - w.resizeBy(-1,-1); - w.resizeBy(1,1); - } - }, 250); - }, 0); - }, - - get shouldListenKeyEvents() - { - return !this.treeStyleTab.ctrlTabPreviewsEnabled && - ( - utils.getTreePref('tabbar.autoShow.accelKeyDown') || - utils.getTreePref('tabbar.autoShow.tabSwitch') || - utils.getTreePref('tabbar.autoShow.feedback') - ); - }, - - init : function AHB_init(aWindow) - { - this.window = aWindow; - this.document = aWindow.document; - this.treeStyleTab = aWindow.TreeStyleTabService; - }, - - destroy : function AHB_destroy() - { - delete this.treeStyleTab; - delete this.document; - delete this.window; - } - -}; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['AutoHideBrowser', 'AutoHideWindow']; + +const DEBUG = false; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); + + +function AutoHideBrowser(aTabBrowser) +{ + this.init(aTabBrowser); +} +AutoHideBrowser.prototype = { + + kMODE : 'treestyletab-tabbar-autohide-mode', + kMODE_DISABLED : 0, + kMODE_HIDE : 1, + kMODE_SHRINK : 2, + + kAUTOHIDE : 'treestyletab-tabbar-autohide', + + kSTATE : 'treestyletab-tabbar-autohide-state', + kSTATE_HIDDEN : 'hidden', + kSTATE_EXPANDED : 'expanded', + kSTATE_SHRUNKEN : 'shrunken', + + kSHOWN_BY_UNKNOWN : 0, + kSHOWN_BY_SHORTCUT : 1 << 0, + kSHOWN_BY_MOUSEMOVE : 1 << 1, + kSHOWN_BY_FEEDBACK : 1 << 2, + kSHOWN_BY_ANY_REASON : (1 << 0) | (1 << 1) | (1 << 2), + kSHOWHIDE_BY_START : 1 << 3, + kSHOWHIDE_BY_END : 1 << 4, + kSHOWHIDE_BY_POSITION_CHANGE : 1 << 5, + kSHOWHIDE_BY_RESIZE : 1 << 6, + kSHOWHIDE_BY_API : 1 << 8, + kHIDDEN_BY_CLICK : 1 << 7, + + get mode() /* PUBLIC API */ + { + var mode = this.browser.getAttribute(this.kMODE); + return mode ? parseInt(mode) : this.kMODE_DISABLED ; + }, + set mode(aValue) + { + this.browser.setAttribute(this.kMODE, aValue); + return aValue; + }, + + getMode : function AHB_getMode(aTabBrowser) + { + var b = aTabBrowser || this.browser; + var mode = b.getAttribute(this.kMODE); + return mode ? parseInt(mode) : this.kMODE_DISABLED ; + }, + getModeForNormal : function AHB_getModeForNormal(aTabBrowser) + { + var b = aTabBrowser || this.browser; + return parseInt(b.getAttribute(this.kMODE+'-normal') || utils.getTreePref('tabbar.autoHide.mode')); + }, + getModeForFullScreen : function AHB_getModeForFullScreen(aTabBrowser) + { + var b = aTabBrowser || this.browser; + return parseInt(b.getAttribute(this.kMODE+'-fullscreen') || utils.getTreePref('tabbar.autoHide.mode.fullscreen')); + }, + + get state() + { + return this.browser.getAttribute(this.kSTATE) || this.kSTATE_EXPANDED; + }, + get expanded() + { + return this.state == this.kSTATE_EXPANDED; + }, + get shrunken() + { + return this.state == this.kSTATE_SHRUNKEN; + }, + get hidden() + { + return this.state == this.kSTATE_HIDDEN; + }, + + updateMode : function AHB_updateMode() + { + this.end(); + // update internal property after the appearance of the tab bar is updated. + var w = this.window; + w.setTimeout(function(aSelf) { + aSelf.mode = (w.fullScreen && prefs.getPref('browser.fullscreen.autohide')) ? + aSelf.getModeForFullScreen() : + aSelf.getModeForNormal() ; + if (aSelf.mode != aSelf.kMODE_DISABLED) + aSelf.start(); + }, 0, this); + }, + + togglerSize : 0, + sensitiveArea : 7, + contentAreaScreenEnabled : true, + + closeButtonsMode : -1, + CLOSE_BUTTONS_ONLY_ON_CURRENT_TAB : 0, + CLOSE_BUTTONS_ON_ALL_TABS : 1, + CLOSE_BUTTONS_DISABLED : 2, + CLOSE_BUTTONS_ON_TABBAR : 3, + + get XOffset() + { + var sv = this.treeStyleTab; + switch (this.mode) + { + case this.kMODE_DISABLED: + return 0; + + case this.kMODE_HIDE: + let offset = this.width + this.splitterWidth; + let resizer = this.resizer; + if (sv.position == 'left') { + offset += this.togglerSize; + if (resizer) + offset += resizer.boxObject.width; + } + else if (sv.position == 'right') { + offset -= this.togglerSize; + if (resizer) + offset -= resizer.boxObject.width; + } + return offset; + + default: + case this.kMODE_SHRINK: + return utils.getTreePref('tabbar.width') + - utils.getTreePref('tabbar.shrunkenWidth'); + } + }, + get YOffset() + { + return this.height; + }, + extraXOffset : 0, + extraYOffset : 0, + + get currentXOffset() + { + var sv = this.treeStyleTab; + return ( + sv.position == 'left' && + this.mode != this.kMODE_DISABLED && + this.expanded + ) ? this.XOffset : 0 ; + }, + get currentYOffset() + { + var sv = this.treeStyleTab; + return ( + sv.position == 'top' && + this.mode != this.kMODE_DISABLED && + this.expanded + ) ? this.YOffset : 0 ; + }, + + get screen() + { + return this.document.getElementById('treestyletab-autohide-content-area-screen'); + }, + get resizer() + { + return this.document.getElementById('treestyletab-tabbar-resizer-splitter'); + }, + + start : function AHB_start(aReason) + { + if (this.enabled) return; + this.enabled = true; + aReason = aReason || 0; + + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + this.screen.hidePopup(); + + sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_EXPANDED); + + if (!(aReason & this.kSHOWHIDE_BY_API)) { + b.addEventListener('mousedown', this, true); + b.addEventListener('mouseup', this, true); + b.addEventListener('dragover', this, true); + b.addEventListener('dragleave', this, true); + sv.tabStrip.addEventListener('mousedown', this, true); + sv.tabStrip.addEventListener('mouseup', this, true); + if (this.shouldListenMouseMove) + this.startListenMouseMove(); + if (b == w.gBrowser && sv.shouldListenKeyEventsForAutoHide) + w.TreeStyleTabService.startListenKeyEventsFor(sv.LISTEN_FOR_AUTOHIDE); + this.userActionListening = true; + } + w.addEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); + w.addEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); + + this.updateTransparency(); + + this.showHideInternal(this.kSHOWHIDE_BY_START | aReason); + + b.treeStyleTab.fixTooNarrowTabbar(); + }, + + end : function AHB_end() + { + if (!this.enabled) return; + this.enabled = false; + + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + this.show(this.kSHOWHIDE_BY_END); + + this.screen.hidePopup(); + + if (this.userActionListening) { + b.removeEventListener('mousedown', this, true); + b.removeEventListener('mouseup', this, true); + b.removeEventListener('dragover', this, true); + b.removeEventListener('dragleave', this, true); + sv.tabStrip.removeEventListener('mousedown', this, true); + sv.tabStrip.removeEventListener('mouseup', this, true); + this.endListenMouseMove(); + if (b == w.gBrowser) + w.TreeStyleTabService.endListenKeyEventsFor(sv.LISTEN_FOR_AUTOHIDE); + this.userActionListening = false; + } + w.removeEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); + w.removeEventListener(sv.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); + + this.updateTransparency(); + + sv.removeTabbrowserAttribute(this.kAUTOHIDE); + sv.removeTabbrowserAttribute(this.kSTATE); + + if (sv.isVertical) + sv.setTabStripAttribute('width', this.widthFromMode); + }, + + // fullscreen + + startForFullScreen : function AHB_startForFullScreen() + { + this.mode = this.getMode(); + this.end(); + this.mode = prefs.getPref('browser.fullscreen.autohide') ? + this.getModeForFullScreen() : + this.kMODE_DISABLED ; + if (this.mode != this.kMODE_DISABLED) { + this.start(); + this.treeStyleTab.removeTabbrowserAttribute('moz-collapsed'); + } + }, + + endForFullScreen : function AHB_endForFullScreen() + { + this.mode = this.getModeForFullScreen(); + this.end(); + this.mode = utils.getTreePref('tabbar.autoHide.mode'); + this.treeStyleTab.checkTabsIndentOverflow(); + if (this.mode != this.kMODE_DISABLED) + this.start(); + }, + + // mousemove + + startListenMouseMove : function AHB_startListenMouseMove() + { + if (this.mouseMoveListening) return; + + this.browser.addEventListener('mousemove', this, true); + this.screen.addEventListener('mousemove', this, true); + this.treeStyleTab.tabStrip.addEventListener('mousemove', this, true); + + this.mouseMoveListening = true; + }, + + endListenMouseMove : function AHB_endListenMouseMove() + { + if (!this.mouseMoveListening) return; + + this.browser.removeEventListener('mousemove', this, true); + this.screen.removeEventListener('mousemove', this, true); + this.treeStyleTab.tabStrip.removeEventListener('mousemove', this, true); + + this.mouseMoveListening = false; + }, + + get shouldListenMouseMove() + { + return utils.getTreePref('tabbar.autoShow.mousemove'); + }, + + get shouldListenKeyEventsForAutoHide() + { + return utils.getTreePref('tabbar.autoShow.accelKeyDown') || + utils.getTreePref('tabbar.autoShow.tabSwitch'); + }, + + showHideOnMouseMove : function AHB_showHideOnMouseMove(aEvent) + { + var position = this.getMousePosition(aEvent); + if (position == this.MOUSE_POSITION_UNKNOWN) + return; + + this.cancelShowHideOnMouseMove(); + this.showHideContentsAreaScreen(); + + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + var shouldShow = position & this.MOUSE_POSITION_SENSITIVE; + if (this.expanded) { // currently shown, let's hide it. + if (shouldShow) { + this.show(this.kSHOWN_BY_MOUSEMOVE); + this.cancelDelayedShowForShortcut(); + } + else if ( + !shouldShow && + utils.getTreePref('tabbar.autoShow.mousemove') + ) { + this.showHideOnMouseMoveTimer = w.setTimeout( + function(aSelf) { + aSelf.cancelDelayedShowForShortcut(); + aSelf.hide(aSelf.kSHOWN_BY_MOUSEMOVE); + }, + utils.getTreePref('tabbar.autoHide.delay'), + this + ); + } + } + else if (shouldShow) { // currently shown, let's show it. + this.showHideOnMouseMoveTimer = w.setTimeout( + function(aSelf) { + aSelf.cancelDelayedShowForShortcut(); + aSelf.show(aSelf.kSHOWN_BY_MOUSEMOVE); + }, + utils.getTreePref('tabbar.autoHide.delay'), + this + ); + } + + b = null; + }, + getMousePosition : function AHB_getMousePosition(aEvent) + { + var w = this.window; + if ('gestureInProgress' in w && w.gestureInProgress) + return this.MOUSE_POSITION_UNKNOWN; + + var sv = this.treeStyleTab; + var b = this.browser; + var pos = sv.position; + var box = this.getContentsAreaBox(); + + var sensitiveArea = this.sensitiveArea; + if (this.shrunken) { + let clickable; + let resizable = !sv.fixed; + if (resizable & + this.widthFromMode > 24 && + (clickable = this.getNearestClickableBox(aEvent))) { + /* For resizing of shrunken tab bar and clicking closeboxes, + we have to shrink sensitive area. */ + sensitiveArea = -(clickable.width + clickable.padding); + } + else if (resizable && this.resizer) + sensitiveArea = -this.resizer.boxObject.width; + else + sensitiveArea = 0; + } + + if ( + pos == 'left' ? + (aEvent.screenX > box.screenX + sensitiveArea) : + pos == 'right' ? + (aEvent.screenX < box.screenX + box.width - sensitiveArea) : + pos == 'bottom' ? + (aEvent.screenY < box.screenY + box.height - sensitiveArea) : + (aEvent.screenY > box.screenY + sensitiveArea) + ) { + return this.MOUSE_POSITION_OUTSIDE; + } + + if ( + pos == 'left' ? + (aEvent.screenX <= box.screenX - sensitiveArea) : + pos == 'right' ? + (aEvent.screenX >= box.screenX + box.width + sensitiveArea) : + pos == 'bottom' ? + (aEvent.screenY >= box.screenY + box.height + sensitiveArea) : + (aEvent.screenY <= box.screenY - sensitiveArea) + ) { + return this.MOUSE_POSITION_INSIDE; + } + + return this.MOUSE_POSITION_NEAR; + }, + getContentsAreaBox : function AHB_getContentsAreaBox() + { + var sv = this.treeStyleTab; + var b = this.browser; + var box = b.mCurrentBrowser.boxObject; + var xoffset = (this.shrunken || this.hidden) ? 0 : this.XOffset ; + box = { + screenX : box.screenX + (sv.position == 'left' ? xoffset : 0 ), + screenY : box.screenY, + width : box.width - xoffset, + height : box.height + }; + return box; + }, + MOUSE_POSITION_UNKNOWN : 0, + MOUSE_POSITION_OUTSIDE : (1 << 0), + MOUSE_POSITION_INSIDE : (1 << 1), + MOUSE_POSITION_NEAR : (1 << 2), + MOUSE_POSITION_SENSITIVE : (1 << 1) | (1 << 2), + getNearestClickableBox : function AHB_getNearestClickableBox(aEvent) + { + var sv = this.treeStyleTab; + var tab = sv.getTabFromCoordinates(aEvent); + if (!tab) + return null; + + var position = sv.invertedScreenPositionProp; + var size = sv.invertedSizeProp; + var coordinate = aEvent[sv.invertedScreenPositionProp]; + var tabbox = tab.boxObject; + + var closebox; + if (this.closeButtonsMode != this.CLOSE_BUTTONS_DISABLED && + this.closeButtonsMode != this.CLOSE_BUTTONS_ON_TABBAR && + (closebox = sv.getTabClosebox(tab)) && + (closebox = closebox.boxObject) && + closebox.width && closebox.height) { + let padding = Math.min( + closebox[position] - tabbox[position], + (tabbox[position] + tabbox[size]) - (closebox[position] + closebox[size]) + ); + if (closebox[position] - padding <= coordinate && + closebox[position] + closebox[size] + padding >= coordinate) + return this.cloneBoxObject(closebox, { padding : padding }); + } + + var twisty; + if (sv.canCollapseSubtree(tab) && + (twisty = sv.getTabTwisty(tab)) && + (twisty = twisty.boxObject) && + twisty.width && twisty.height) { + let padding = Math.min( + twisty[position] - tabbox[position], + (tabbox[position] + tabbox[size]) - (twisty[position] + twisty[size]) + ); + if (twisty[position] - padding <= coordinate && + twisty[position] + twisty[size] + padding >= coordinate) + return this.cloneBoxObject(twisty, { padding : padding }); + } + + return null; + }, + cloneBoxObject : function AHB_cloneBoxObject(aBoxObject, aOverride) + { + var box = {}; + for (let i in aBoxObject) + { + if (typeof aBoxObject[i] != 'function') + box[i] = aBoxObject[i]; + } + Object.keys(aOverride).forEach(function(aKey) { + box[aKey] = aOverride[aKey]; + }); + return box; + }, + + cancelShowHideOnMouseMove : function AHB_cancelShowHideOnMouseMove() + { + if (this.showHideOnMouseMoveTimer) { + this.window.clearTimeout(this.showHideOnMouseMoveTimer); + this.showHideOnMouseMoveTimer = null; + } + }, + + // feedback + + showForFeedback : function AHB_showForFeedback() + { + if (!this.enabled || + !utils.getTreePref('tabbar.autoShow.feedback')) + return; + + var w = this.window; + if (this.delayedShowForFeedbackTimer) { + w.clearTimeout(this.delayedShowForFeedbackTimer); + this.delayedShowForFeedbackTimer = null; + } + this.cancelHideForFeedback(); + this.delayedShowForFeedbackTimer = w.setTimeout( + function(aSelf) { + aSelf.delayedShowForFeedbackTimer = null; + aSelf.delayedShowForFeedback(); + }, + 100, + this + ); + }, + + delayedShowForFeedback : function AHB_delayedShowForFeedback() + { + this.show(this.kSHOWN_BY_FEEDBACK); + this.cancelHideForFeedback(); + this.delayedHideTabbarForFeedbackTimer = this.window.setTimeout( + function(aSelf) { + aSelf.delayedHideTabbarForFeedbackTimer = null; + aSelf.hide(aSelf.kSHOWN_BY_FEEDBACK); + }, + utils.getTreePref('tabbar.autoShow.feedback.delay'), + this + ); + }, + + cancelHideForFeedback : function AHB_cancelHideForFeedback() + { + if (this.delayedHideTabbarForFeedbackTimer) { + this.window.clearTimeout(this.delayedHideTabbarForFeedbackTimer); + this.delayedHideTabbarForFeedbackTimer = null; + } + }, + + setWidth : function AHB_setWidth(aWidth, aForceExpanded) + { + if (aForceExpanded || + this.expanded || + this.mode != this.kMODE_SHRINK) + utils.setTreePref('tabbar.width', this.treeStyleTab.maxTabbarWidth(aWidth)); + else + utils.setTreePref('tabbar.shrunkenWidth', this.treeStyleTab.maxTabbarWidth(aWidth)); + }, + + updateMenuItem : function AHB_updateMenuItem(aNode) + { + if (!aNode) return; + + if (this.mode != this.kMODE_DISABLED) + aNode.setAttribute('checked', true); + else + aNode.removeAttribute('checked'); + }, + + // show/hide tabbar + + get width() + { + if (this.expanded) { + this._width = this.treeStyleTab.tabStrip.boxObject.width || this._width; + } + return this._width; + }, + set width(aNewWidth) + { + this._width = aNewWidth; + return this._width; + }, + _width : 0, + + get widthFromMode() + { + return (this.shrunken) ? + utils.getTreePref('tabbar.shrunkenWidth') : + utils.getTreePref('tabbar.width') ; + }, + get placeHolderWidthFromMode() + { + return (this.mode == this.kMODE_SHRINK) ? + utils.getTreePref('tabbar.shrunkenWidth') : + utils.getTreePref('tabbar.width') ; + }, + + get height() + { + if (this.expanded) { + this._height = this.treeStyleTab.tabStrip.boxObject.height; + } + return this._height; + }, + set height(aNewHeight) + { + this._height = aNewHeight; + return this._height; + }, + _height : 0, + + get splitterWidth() + { + if (this.expanded) { + var splitter = this.document.getAnonymousElementByAttribute(this.browser, 'class', this.treeStyleTab.kSPLITTER); + this._splitterWidth = (splitter ? splitter.boxObject.width : 0 ); + } + return this._splitterWidth; + }, + set splitterWidth(aNewWidth) + { + this._splitterWidth = aNewWidth; + return this._splitterWidth; + }, + _splitterWidth : 0, + + showHideInternal : function AHB_showHideInternal(aReason) + { + var sv = this.treeStyleTab; + var b = this.browser; + var pos = sv.position; + + aReason = aReason || 0; + + if (this.expanded) { // to be hidden or shrunken + let reason = this.kSHOWN_BY_UNKNOWN; + if (aReason & this.kSHOWHIDE_BY_API) + reason = aReason; + this.onHiding(); + this.showHideReason = reason; + } + else { // to be shown or expanded + this.onShowing(); + this.showHideReason = aReason || this.showHideReason || this.kSHOWN_BY_UNKNOWN; + } + + if (DEBUG) { + let humanReadableReason = + (aReason & this.kSHOWN_BY_SHORTCUT ? 'shortcut ' : '' ) + + (aReason & this.kSHOWN_BY_MOUSEMOVE ? 'mousemove ' : '' ) + + (aReason & this.kSHOWN_BY_FEEDBACK ? 'feedback ' : '' ) + + (aReason & this.kSHOWHIDE_BY_START ? 'start ' : '' ) + + (aReason & this.kSHOWHIDE_BY_END ? 'end ' : '' ) + + (aReason & this.kSHOWHIDE_BY_POSITION_CHANGE ? 'positionchange ' : '' ) + + (aReason & this.kSHOWHIDE_BY_RESIZE ? 'resize ' : '' ) + + (aReason & this.kHIDDEN_BY_CLICK ? 'click ' : '' ); + if (this.expanded) + dump('autoHide: show by ' + humanReadableReason + '\n'); + else + dump('autoHide: hide by ' + humanReadableReason + '\n'); + } + + this.fireStateChangingEvent(); + + if (this.expanded) + sv.setTabbrowserAttribute(this.kAUTOHIDE, 'show'); + b.mTabContainer.adjustTabstrip(); + sv.checkTabsIndentOverflow(); + + this.window.setTimeout(function(aSelf) { + aSelf.fireStateChangeEvent(); + aSelf.showHideContentsAreaScreen(); + }, 0, this); + }, + showHideContentsAreaScreen : function AHB_showHideContentsAreaScreen() + { + // this.browser.contentWindow doesn't currently work in e10s + // mode, use this.browser.mCurrentBrowser.contentWindow as a + // workaround until bug 1042680 is fixed + if ( + this.expanded && + this.contentAreaScreenEnabled && + Services.focus.activeWindow && + Services.focus.activeWindow.top == this.window && + this.findPluginArea(this.browser.mCurrentBrowser.contentWindow) + ) { + let box = this.getContentsAreaBox(); + let style = this.screen.style; + let width = Math.min(box.width, this.window.screen.availWidth - box.screenX); + let height = Math.min(box.height, this.window.screen.availHeight - box.screenY); + style.width = width+'px'; + style.height = height+'px'; + if (this.screen.state == 'open') + this.screen.moveTo(box.screenX, box.screenY); + else + this.screen.openPopupAtScreen(box.screenX, box.screenY, false); + this.screen.setAttribute('popup-shown', true); + } + else { + this.screen.removeAttribute('popup-shown'); + if (this.screen.state != 'close') + this.screen.hidePopup(); + } + }, + findPluginArea : function AHB_findPluginArea(aFrame) + { + return aFrame.document.querySelector('embed, object') || + Array.some(aFrame.frames, AHB_findPluginArea); + }, + + show : function AHB_show(aReason) /* PUBLIC API */ + { + if (this.showHideReason & this.kSHOWHIDE_BY_API) { + this.end(); + return; + } + + if (aReason) { + this.showHideReason |= aReason; + } + if (!this.expanded) + this.showHideInternal(); + }, + + hide : function AHB_hide(aReason) /* PUBLIC API */ + { + if (!this.enabled) { + this.start(aReason | this.kSHOWHIDE_BY_API); + return; + } + + if (aReason) { + if (aReason == this.kSHOWN_BY_ANY_REASON) + this.showHideReason &= ~this.kSHOWN_BY_ANY_REASON; + else if (this.showHideReason & aReason) + this.showHideReason ^= aReason; + + if (this.showHideReason & this.kSHOWN_BY_ANY_REASON) + return; + } + if (this.expanded) + this.showHideInternal(); + }, + + onShowing : function AHB_onShowing() + { + var sv = this.treeStyleTab; + var b = this.browser; + var pos = sv.position; + + sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_EXPANDED); + + switch (this.mode) + { + case this.kMODE_DISABLED: + break; + + case this.kMODE_HIDE: + sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); + break; + + default: + case this.kMODE_SHRINK: + if (pos == 'left' || pos == 'right') { + let width = sv.maxTabbarWidth(utils.getTreePref('tabbar.width')); + sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); + } + break; + } + }, + + onHiding : function AHB_onHiding() + { + var sv = this.treeStyleTab; + var b = this.browser; + var pos = sv.position; + + var box = (sv.tabStripPlaceHolder || sv.tabStrip).boxObject; + + this.tabbarHeight = box.height; + this.width = box.width || this.width; + var splitter = this.document.getAnonymousElementByAttribute(b, 'class', sv.kSPLITTER); + this.splitterWidth = (splitter ? splitter.boxObject.width : 0 ); + + switch (this.mode) + { + case this.kMODE_DISABLED: + sv.setTabbrowserAttribute(this.kAUTOHIDE, 'hidden'); + sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_HIDDEN); + break; + + case this.kMODE_HIDE: + sv.updateLastScrollPosition(); + sv.setTabbrowserAttribute(this.kAUTOHIDE, 'hidden'); + sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_HIDDEN); + sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); + break; + + default: + case this.kMODE_SHRINK: + sv.setTabbrowserAttribute(this.kAUTOHIDE, 'show'); + sv.setTabbrowserAttribute(this.kSTATE, this.kSTATE_SHRUNKEN); + if (pos == 'left' || pos == 'right') + sv.setTabStripAttribute('width', utils.getTreePref('tabbar.shrunkenWidth')); + sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_AUTOHIDE); + break; + } + }, + + fireStateChangingEvent : function AHB_fireStateChangingEvent() + { + var data = { + shown : this.expanded, + state : this.state + }; + + /* PUBLIC API */ + this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING, this.browser, true, false, data); + // for backward compatibility + this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING.replace(/^nsDOM/, ''), this.browser, true, false, data); + }, + + fireStateChangeEvent : function AHB_fireStateChangeEvent() + { + var data = { + shown : this.expanded, + state : this.state, + xOffset : this.XOffset, + yOffset : this.YOffset + }; + + /* PUBLIC API */ + this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE, this.browser, true, false, data); + // for backward compatibility + this.treeStyleTab.fireCustomEvent(this.treeStyleTab.kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE.replace(/^nsDOM/, ''), this.browser, true, false, data); + }, + + + + + updateTransparency : function AHB_updateTransparency() + { + var sv = this.treeStyleTab; + sv.updateFloatingTabbar(sv.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); + }, + + // event handling + + observe : function AHB_observe(aSubject, aTopic, aData) + { + switch (aTopic) + { + case 'nsPref:changed': + this.onPrefChange(aData); + break; + + default: + break; + } + }, + + domains : [ + 'extensions.treestyletab.', + 'browser.fullscreen.autohide', + 'browser.tabs.closeButtons' + ], + + onPrefChange : function AHB_onPrefChange(aPrefName) + { + // ignore after destruction + if (!this.window || !this.window.TreeStyleTabService) + return; + + var value = prefs.getPref(aPrefName); + switch (aPrefName) + { + case 'extensions.treestyletab.tabbar.autoHide.mode': + if (!this.window.TreeStyleTabService.shouldApplyNewPref) return; + this.browser.setAttribute(this.kMODE+'-normal', value); + this.updateMode(); + return; + + case 'extensions.treestyletab.tabbar.autoHide.mode.fullscreen': + if (!this.window.TreeStyleTabService.shouldApplyNewPref) return; + this.browser.setAttribute(this.kMODE+'-fullscreen', value); + this.updateMode(); + return; + + case 'extensions.treestyletab.tabbar.autoShow.mousemove': + case 'extensions.treestyletab.tabbar.autoShow.accelKeyDown': + case 'extensions.treestyletab.tabbar.autoShow.feedback': + if (this.enabled && this.shouldListenMouseMove) + this.startListenMouseMove(); + else + this.endListenMouseMove(); + return; + + case 'extensions.treestyletab.tabbar.autoHide.area': + this.sensitiveArea = value; + return; + + case 'extensions.treestyletab.tabbar.togglerSize': + this.togglerSize = value; + var toggler = this.document.getAnonymousElementByAttribute(this.browser, 'class', this.treeStyleTab.kTABBAR_TOGGLER); + toggler.style.minWidth = toggler.style.minHeight = value+'px'; + if (this.togglerSize <= 0) + toggler.setAttribute('collapsed', true); + else + toggler.removeAttribute('collapsed'); + return; + + case 'extensions.treestyletab.tabbar.autoHide.contentAreaScreen.enabled': + return this.contentAreaScreenEnabled = value; + + case 'browser.fullscreen.autohide': + if (!this.window.fullScreen) return; + this.end(); + this.mode = value ? + this.getModeForFullScreen() : + this.kMODE_DISABLED ; + if (this.mode != this.kMODE_DISABLED) + this.start(); + return; + + case 'browser.tabs.closeButtons': + return this.closeButtonsMode = value; + + default: + return; + } + }, + + handleEvent : function AHB_handleEvent(aEvent) + { + switch (aEvent.type) + { + case 'mousedown': + return this.onMouseDown(aEvent); + + case 'mouseup': + return this.onMouseUp(aEvent); + + case 'mousemove': + return this.handleMouseMove(aEvent); + + case 'TabOpen': + case 'TabClose': + return this.showForFeedback(); + + case 'TabMove': + if (!this.treeStyleTab.subTreeMovingCount && !this.treeStyleTab.internallyTabMovingCount) + this.showForFeedback(); + return; + + case 'select': + if (!this.window.TreeStyleTabService.accelKeyPressed) + this.showForFeedback(); + return; + + case 'dragover': + return this.onDragOver(aEvent); + + case 'dragleave': + return this.onDragLeave(aEvent); + + case this.treeStyleTab.kEVENT_TYPE_TABBAR_POSITION_CHANGING: + this.isResizing = false; + return; + + case this.treeStyleTab.kEVENT_TYPE_TABBAR_POSITION_CHANGED: + if (this.enabled) + this.window.setTimeout(function(aSelf) { + aSelf.show(this.kSHOWHIDE_BY_POSITION_CHANGE); + aSelf.hide(this.kSHOWHIDE_BY_POSITION_CHANGE); + }, 0, this); + this.updateTransparency(); + return; + + case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN: + return this.onKeyDown(aEvent.detail.sourceEvent); + + case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START: + this.cancelDelayedShowForShortcut(); + if (this.enabled && + utils.getTreePref('tabbar.autoShow.tabSwitch') && + ( + aEvent.detail.scrollDown || + aEvent.detail.scrollUp || + ( // when you release "shift" key + this.expanded && + aEvent.detail.standBy && + aEvent.detail.onlyShiftKey + ) + )) + this.show(this.kSHOWN_BY_SHORTCUT); + return; + + case this.treeStyleTab.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END: + this.cancelDelayedShowForShortcut(); + if (this.enabled) + this.hide(this.kSHOWN_BY_SHORTCUT); + return; + + case this.treeStyleTab.kEVENT_TYPE_PRINT_PREVIEW_ENTERED: + this.hide(this.kSHOWHIDE_BY_END); + this.endListenMouseMove(); + return; + + case this.treeStyleTab.kEVENT_TYPE_PRINT_PREVIEW_EXITED: + if (this.enabled && this.shouldListenMouseMove) + this.startListenMouseMove(); + return; + } + }, + + onMouseDown : function AHB_onMouseDown(aEvent) + { + var sv = this.treeStyleTab; + var w = this.window; + if ( + !this.isResizing && + sv.evaluateXPath( + 'ancestor-or-self::*[@class="'+sv.kSPLITTER+'"]', + aEvent.originalTarget || aEvent.target, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue + ) { + this.isResizing = true; + sv.setTabbrowserAttribute(sv.kRESIZING, true); + } + this.cancelShowHideOnMouseMove(); + if ( + this.enabled && + this.expanded && + ( + aEvent.originalTarget.ownerDocument != this.document || + !sv.getTabBrowserFromChild(aEvent.originalTarget) + ) + ) + this.hide(this.kHIDDEN_BY_CLICK); + this.lastMouseDownTarget = aEvent.originalTarget.localName; + }, + + onMouseUp : function AHB_onMouseUp(aEvent) + { + var sv = this.treeStyleTab; + if (aEvent.originalTarget && + sv.evaluateXPath( + 'ancestor-or-self::*[@class="'+sv.kSPLITTER+'"]', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue) { + this.isResizing = false; + sv.removeTabbrowserAttribute(sv.kRESIZING); + } + this.cancelShowHideOnMouseMove(); + this.lastMouseDownTarget = null; + }, + + handleMouseMove : function AHB_handleMouseMove(aEvent) + { + var sv = this.treeStyleTab; + if (this.isResizing && + /^(scrollbar|thumb|slider|scrollbarbutton)$/i.test(this.lastMouseDownTarget)) + return true; + + if ( + !aEvent.shiftKey && + !sv.isPopupShown() && + ( + !this.expanded || + this.showHideReason & this.kSHOWN_BY_ANY_REASON + ) && + !this.lastMouseDownTarget + ) + this.showHideOnMouseMove(aEvent); + return true; + }, + + onDragOver : function AHB_onDragOver(aEvent) + { + if (this.expanded) + return; + + var position = this.getMousePosition(aEvent); + if (!(position & this.MOUSE_POSITION_SENSITIVE)) + return; + + var draggedTabs = this.window['piro.sakura.ne.jp'].tabsDragUtils.getSelectedTabs(aEvent); + if ( + draggedTabs.length || + this.treeStyleTab.tabbarDNDObserver.retrieveURLsFromDataTransfer(aEvent.dataTransfer).length + ) { + this.show(this.kSHOWN_BY_MOUSEMOVE); + + if (this._autoHideOnDragLeaveTimer) { + this.window.clearTimeout(this._autoHideOnDragLeaveTimer); + delete this._autoHideOnDragLeaveTimer; + } + } + }, + + onDragLeave : function AHB_onDragLeave(aEvent) + { + if (!this.expanded) + return; + + if (this._autoHideOnDragLeaveTimer) + this.window.clearTimeout(this._autoHideOnDragLeaveTimer); + + var position = this.getMousePosition(aEvent); + if (position & this.MOUSE_POSITION_SENSITIVE) + return; + + this._autoHideOnDragLeaveTimer = this.window.setTimeout(function(aSelf) { + delete aSelf._autoHideOnDragLeaveTimer; + aSelf.hide(aSelf.kSHOWN_BY_MOUSEMOVE); + }, 100, this); + }, + + onKeyDown : function AHB_onKeyDown(aEvent) + { + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + if (this.delayedShowForShortcutDone) + this.cancelDelayedShowForShortcut(); + + if ( + sv.getTabs(b).length > 1 && + !aEvent.altKey && + w.TreeStyleTabService.accelKeyPressed + ) { + if (this.enabled && + utils.getTreePref('tabbar.autoShow.accelKeyDown') && + !this.delayedAutoShowTimer && + !this.delayedShowForShortcutTimer) { + this.delayedShowForShortcutTimer = w.setTimeout( + function(aSelf) { + aSelf.delayedShowForShortcutDone = true; + aSelf.show(aSelf.kSHOWN_BY_SHORTCUT); + sv = null; + b = null; + }, + utils.getTreePref('tabbar.autoShow.accelKeyDown.delay'), + this + ); + this.delayedShowForShortcutDone = false; + } + } + else { + if (this.enabled) + this.hide(this.kSHOWN_BY_SHORTCUT); + } + }, + + cancelDelayedShowForShortcut : function AHB_cancelDelayedShowForShortcut() + { + if (this.delayedShowForShortcutTimer) { + this.window.clearTimeout(this.delayedShowForShortcutTimer); + this.delayedShowForShortcutTimer = null; + } + }, + + delayedShowForShortcutTimer : null, + delayedShowForShortcutDone : true, + + init : function AHB_init(aTabBrowser) + { + this.browser = aTabBrowser; + this.document = aTabBrowser.ownerDocument; + this.window = this.document.defaultView; + this.treeStyleTab = aTabBrowser.treeStyleTab; + + var sv = this.treeStyleTab; + var b = this.browser; + + this.enabled = false; + this.mouseMoveListening = false; + this.showHideReason = this.kSHOWN_BY_UNKNOWN; + this.lastMouseDownTarget = null; + this.isResizing = false; + + this.showHideOnMouseMoveTimer = null; + this.delayedShowForFeedbackTimer = null; + + b.setAttribute(this.kMODE+'-normal', utils.getTreePref('tabbar.autoHide.mode')); + b.setAttribute(this.kMODE+'-fullscreen', utils.getTreePref('tabbar.autoHide.mode.fullscreen')); + prefs.addPrefListener(this); + this.onPrefChange('browser.tabs.closeButtons'); + this.onPrefChange('extensions.treestyletab.tabbar.autoHide.area'); + this.onPrefChange('extensions.treestyletab.tabbar.togglerSize'); + this.onPrefChange('extensions.treestyletab.tabbar.autoHide.contentAreaScreen.enabled'); + this.window.setTimeout(function(aSelf) { + aSelf.onPrefChange('extensions.treestyletab.tabbar.autoHide.mode'); + }, 0, this); + + b.mTabContainer.addEventListener('TabOpen', this, false); + b.mTabContainer.addEventListener('TabClose', this, false); + b.mTabContainer.addEventListener('TabMove', this, false); + b.mTabContainer.addEventListener('select', this, false); + b.addEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGING, this, false); + b.addEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); + b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, this, false); + b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, this, false); + b.addEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); + }, + + destroy : function AHB_destroy() + { + this.end(); + prefs.removePrefListener(this); + + var sv = this.treeStyleTab; + var b = this.browser; + b.mTabContainer.removeEventListener('TabOpen', this, false); + b.mTabContainer.removeEventListener('TabClose', this, false); + b.mTabContainer.removeEventListener('TabMove', this, false); + b.mTabContainer.removeEventListener('select', this, false); + b.removeEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGING, this, false); + b.removeEventListener(sv.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); + b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, this, false); + b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, this, false); + b.removeEventListener(sv.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); + + delete this.treeStyleTab; + delete this.browser; + delete this.document; + delete this.window; + }, + + saveCurrentState : function AHB_saveCurrentState() + { + var b = this.browser; + var prefs = { + 'tabbar.autoHide.mode' : this.getModeForNormal(b), + 'tabbar.autoHide.mode.fullscreen' : this.getModeForFullScreen(b), + }; + for (var i in prefs) + { + if (utils.getTreePref(i) != prefs[i]) + utils.setTreePref(i, prefs[i]); + } + } + +}; + +function AutoHideWindow(aWindow) +{ + this.init(aWindow); +} +AutoHideWindow.prototype = { + get browser() + { + return this.treeStyleTab.browser; + }, + +// mode + + getMode : function AHW_getMode(aTabBrowser) + { + var b = aTabBrowser || this.browser; + var mode = b.getAttribute(AutoHideBrowser.prototype.kMODE); + return mode ? parseInt(mode) : AutoHideBrowser.prototype.kMODE_DISABLED ; + }, + + get mode() /* PUBLIC API */ + { + var mode = this.getMode(); + if (mode == AutoHideBrowser.prototype.kMODE_SHRINK && + this.treeStyleTab.position != 'left' && + this.treeStyleTab.position != 'right') + return AutoHideBrowser.prototype.kMODE_HIDE; + return mode; + }, + + set mode(aValue) + { + var b = aTabBrowser || this.browser; + b.setAttribute(AutoHideBrowser.prototype.kMODE, aValue); + return aValue; + }, + + toggleMode : function AHW_toggleMode(aTabBrowser) /* PUBLIC API */ + { + var b = aTabBrowser || this.browser; + var w = this.window; + + var key = 'tabbar.autoHide.mode'; + var toggleKey = 'tabbar.autoHide.mode.toggle'; + if (w.fullScreen) { + key += '.fullscreen'; + toggleKey += '.fullscreen'; + } + + var mode = this.getMode(b) == AutoHideBrowser.prototype.kMODE_DISABLED ? + utils.getTreePref(toggleKey) : + AutoHideBrowser.prototype.kMODE_DISABLED ; + + utils.setTreePref(key, mode); + b.setAttribute(AutoHideBrowser.prototype.kMODE+'-'+(w.fullScreen ? 'fullscreen' : 'normal' ), mode); + b.treeStyleTab.autoHide.updateMode(); + }, + +// for shortcuts + + updateKeyListeners : function AHW_updateKeyListeners() + { + // ignore after destruction + if (!this.window || !this.window.TreeStyleTabService) + return; + + if ( + this.getMode() && + this.shouldListenKeyEvents + ) { + this.treeStyleTab.startListenKeyEventsFor(this.treeStyleTab.LISTEN_FOR_AUTOHIDE); + } + else { + this.treeStyleTab.endListenKeyEventsFor(this.treeStyleTab.LISTEN_FOR_AUTOHIDE); + } + var w = this.window; + w.setTimeout(function() { + if (w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) + return; + var count = 0; + var resizeTimer = w.setInterval(function(){ + if (w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) + return w.clearInterval(resizeTimer); + + if (++count > 100 || w.innerHeight > 0) { + w.clearInterval(resizeTimer); + w.resizeBy(-1,-1); + w.resizeBy(1,1); + } + }, 250); + }, 0); + }, + + get shouldListenKeyEvents() + { + return !this.treeStyleTab.ctrlTabPreviewsEnabled && + ( + utils.getTreePref('tabbar.autoShow.accelKeyDown') || + utils.getTreePref('tabbar.autoShow.tabSwitch') || + utils.getTreePref('tabbar.autoShow.feedback') + ); + }, + + init : function AHB_init(aWindow) + { + this.window = aWindow; + this.document = aWindow.document; + this.treeStyleTab = aWindow.TreeStyleTabService; + }, + + destroy : function AHB_destroy() + { + delete this.treeStyleTab; + delete this.document; + delete this.window; + } + +}; + diff --git a/modules/base.js b/modules/base.js index 9d7249cc..77762ea0 100644 --- a/modules/base.js +++ b/modules/base.js @@ -1,2575 +1,2575 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TreeStyleTabBase']; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://gre/modules/Services.jsm'); -Cu.import('resource://gre/modules/Timer.jsm'); -Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); -Cu.import('resource://treestyletab-modules/constants.js'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); -XPCOMUtils.defineLazyGetter(this, 'extensions', function() { - Cu.import('resource://treestyletab-modules/lib/extensions.js', {}); - return window['piro.sakura.ne.jp'].extensions; -}); -XPCOMUtils.defineLazyGetter(this, 'animationManager', function() { - Cu.import('resource://treestyletab-modules/lib/animationManager.js', {}); - return window['piro.sakura.ne.jp'].animationManager; -}); -XPCOMUtils.defineLazyGetter(this, 'autoScroll', function() { - Cu.import('resource://treestyletab-modules/lib/autoScroll.js', {}); - return window['piro.sakura.ne.jp'].autoScroll; -}); -XPCOMUtils.defineLazyModuleGetter(this, 'UninstallationListener', - 'resource://treestyletab-modules/lib/UninstallationListener.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'Deferred', - 'resource://treestyletab-modules/lib/jsdeferred.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'confirmWithPopup', 'resource://treestyletab-modules/lib/confirmWithPopup.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); - -XPCOMUtils.defineLazyServiceGetter(this, 'SessionStore', - '@mozilla.org/browser/sessionstore;1', 'nsISessionStore'); - -if (Services.appinfo.OS === 'WINNT') { - XPCOMUtils.defineLazyModuleGetter(this, 'AeroPeek', - 'resource:///modules/WindowsPreviewPerTab.jsm', 'AeroPeek'); -} -else { - this.AeroPeek = null; -} - -var TreeStyleTabBase = inherit(TreeStyleTabConstants, { - - tabsHash : null, - inWindowDestoructionProcess : false, - -/* base variables */ - baseIndentVertical : 12, - baseIndentHorizontal : 4, - shouldDetectClickOnIndentSpaces : true, - - smoothScrollEnabled : true, - smoothScrollDuration : 150, - - animationEnabled : true, - indentDuration : 200, - collapseDuration : 150, - - shouldExpandTwistyArea : true, - - scrollToNewTabMode : false, - - counterRoleHorizontal : -1, - counterRoleVertical : -1, - - get SessionStore() { - return SessionStore; - }, - - get extensions() { return extensions; }, - get animationManager() { return animationManager; }, - get autoScroll() { return autoScroll; }, - get Deferred() { return Deferred; }, - get AeroPeek() { return AeroPeek; }, // for Windows - - init : function TSTBase_init() - { - if (this._initialized) - return; - - this.isMac = Services.appinfo.OS == 'Darwin'; - - this.applyPlatformDefaultPrefs(); - utils.migratePrefs(); - - prefs.addPrefListener(this); - - this.initUninstallationListener(); - - this.onPrefChange('extensions.treestyletab.indent.vertical'); - this.onPrefChange('extensions.treestyletab.indent.horizontal'); - this.onPrefChange('extensions.treestyletab.clickOnIndentSpaces.enabled'); - this.onPrefChange('browser.tabs.insertRelatedAfterCurrent.override'); - this.onPrefChange('extensions.stm.tabBarMultiRows.override'); // Super Tab Mode - this.onPrefChange('extensions.treestyletab.tabbar.scroll.smooth'); - this.onPrefChange('extensions.treestyletab.tabbar.scroll.duration'); - this.onPrefChange('extensions.treestyletab.tabbar.scrollToNewTab.mode'); - this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar.size'); - this.onPrefChange('browser.tabs.animate'); - this.onPrefChange('extensions.treestyletab.animation.indent.duration'); - this.onPrefChange('extensions.treestyletab.animation.collapse.duration'); - this.onPrefChange('extensions.treestyletab.twisty.expandSensitiveArea'); - this.onPrefChange('extensions.treestyletab.counter.role.horizontal'); - this.onPrefChange('extensions.treestyletab.counter.role.vertical'); - - try { - this.overrideExtensions(); - } - catch(e) { - dump(e+'\n'); - } - }, - _initialized : false, - - applyPlatformDefaultPrefs : function TSTBase_applyPlatformDefaultPrefs() - { - var OS = Services.appinfo.OS; - var processed = {}; - var originalKeys = prefs.getDescendant('extensions.treestyletab.platform.'+OS); - for (let i = 0, maxi = originalKeys.length; i < maxi; i++) - { - let originalKey = originalKeys[i]; - let key = originalKey.replace('platform.'+OS+'.', ''); - prefs.setDefaultPref(key, prefs.getPref(originalKey)); - processed[key] = true; - } - originalKeys = prefs.getDescendant('extensions.treestyletab.platform.default'); - for (let i = 0, maxi = originalKeys.length; i < maxi; i++) - { - let originalKey = originalKeys[i]; - let key = originalKey.replace('platform.default.', ''); - if (!(key in processed)) - prefs.setDefaultPref(key, prefs.getPref(originalKey)); - } - }, - - initUninstallationListener : function TSTWindow_initUninstallationListener() - { - var restorePrefs = function() { - // Remove pref listener before restore backuped prefs. - prefs.removePrefListener(this); - - let restorePrefs = [ - 'browser.tabs.insertRelatedAfterCurrent', - 'extensions.stm.tabBarMultiRows' // Super Tab Mode - ]; - for (let i = 0, maxi = restorePrefs.length; i < maxi; i++) - { - let pref = restorePrefs[i]; - let backup = prefs.getPref(pref+'.backup'); - if (backup === null) - continue; - // restore user preference. - prefs.setPref(pref, backup); - // clear backup pref. - prefs.clearPref(pref+'.backup'); - } - }.bind(this); - new UninstallationListener({ - id : 'treestyletab@piro.sakura.ne.jp', - onuninstalled : restorePrefs, - ondisabled : restorePrefs - }); - }, - - overrideExtensions : function TSTBase_overrideExtensions() - { - // Scriptish - // https://addons.mozilla.org/firefox/addon/scriptish/ - if (utils.getTreePref('compatibility.Scriptish')) { - try { - let tabModule = Cu.import('resource://scriptish/utils/Scriptish_openInTab.js', {}); - let Scriptish_openInTab = tabModule.Scriptish_openInTab; - tabModule.Scriptish_openInTab = function(aURL, aLoadInBackground, aReuse, aChromeWin, ...aExtraArgs) { - try { - aChromeWin.TreeStyleTabService.readyToOpenChildTabNow(aChromeWin.gBrowser); - } - catch(e) { - Cu.reportError(e); - } - var allArgs = [aURL, aLoadInBackground, aReuse, aChromeWin].concat(aExtraArgs); - return Scriptish_openInTab.apply(this, allArgs); - }; - } - catch(e) { - } - } - }, - - updateNarrowScrollbarStyle : function TSTBase_updateNarrowScrollbarStyle() - { - const SSS = Cc['@mozilla.org/content/style-sheet-service;1'] - .getService(Ci.nsIStyleSheetService); - - if (this.lastAgentSheet && - SSS.sheetRegistered(this.lastAgentSheet, SSS.AGENT_SHEET)) - SSS.unregisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET); - - const style = 'data:text/css,'+encodeURIComponent( - ('@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");' + - - 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + - ' .tabbrowser-arrowscrollbox' + - ' > scrollbox' + - ' > scrollbar[orient="vertical"],' + - 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + - ' .tabbrowser-arrowscrollbox' + - ' > scrollbox' + - ' > scrollbar[orient="vertical"] * {' + - ' max-width: %SIZE%;' + - ' min-width: %SIZE%;' + - '}' + - - 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + - ' .tabbrowser-arrowscrollbox' + - ' > scrollbox' + - ' > scrollbar[orient="vertical"] {' + - ' font-size: %SIZE%;' + - '}' + - - 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + - ' .tabbrowser-arrowscrollbox' + - ' > scrollbox' + - ' > scrollbar[orient="vertical"] * {' + - ' padding-left: 0;' + - ' padding-right: 0;' + - ' margin-left: 0;' + - ' margin-right: 0;' + - '}' + - - '%FORCE_NARROW_SCROLLBAR%') - .replace(/%FORCE_NARROW_SCROLLBAR%/g, - utils.getTreePref('tabbar.narrowScrollbar.overrideSystemAppearance') ? - this.kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE : '' ) - .replace(/%MODE%/g, this.kMODE) - .replace(/%NARROW%/g, this.kNARROW_SCROLLBAR) - .replace(/%SIZE%/g, utils.getTreePref('tabbar.narrowScrollbar.size')) - ); - this.lastAgentSheet = this.makeURIFromSpec(style); - SSS.loadAndRegisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET); - }, - kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE : - 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + - ' .tabbrowser-arrowscrollbox' + - ' > scrollbox' + - ' > scrollbar[orient="vertical"] {' + - ' appearance: none;' + - ' -moz-appearance: none;' + - ' background: ThreeDFace;' + - ' border: 1px solid ThreeDShadow;' + - '}', - lastAgentSheet : null, - -/* references to the owner */ - - get browserWindow() - { - return this.topBrowserWindow; - }, - get topBrowserWindow() - { - return Services.wm.getMostRecentWindow('navigator:browser'); - }, - - get browserWindows() - { - var windows = []; - - var targets = Services.wm.getZOrderDOMWindowEnumerator('navigator:browser', true); - // By the bug 156333, we cannot find windows by their Z order on Linux. - // https://bugzilla.mozilla.org/show_bug.cgi?id=156333 - if (!targets.hasMoreElements()) - targets = Services.wm.getEnumerator('navigator:browser'); - - while (targets.hasMoreElements()) - { - let target = targets.getNext() - .QueryInterface(Ci.nsIDOMWindow); - windows.push(target); - } - - return windows; - }, - - get browser() - { - var w = this.browserWindow; - return !w ? null : - 'SplitBrowser' in w ? w.SplitBrowser.activeBrowser : - w.gBrowser ; - }, - - get window() - { - return this.browser.ownerDocument.defaultView; - }, - - get currentDragSession() - { - return Cc['@mozilla.org/widget/dragservice;1'] - .getService(Ci.nsIDragService) - .getCurrentSession(); - }, - -/* calculated behaviors */ - - dropLinksOnTabBehavior : function TSTBase_dropLinksOnTabBehavior() - { - var behavior = utils.getTreePref('dropLinksOnTab.behavior'); - if (behavior & this.kDROPLINK_FIXED) - return behavior; - - var checked = { value : false }; - var newChildTab = Services.prompt.confirmEx(this.browserWindow, - utils.treeBundle.getString('dropLinkOnTab.title'), - utils.treeBundle.getString('dropLinkOnTab.text'), - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), - utils.treeBundle.getString('dropLinkOnTab.openNewChildTab'), - utils.treeBundle.getString('dropLinkOnTab.loadInTheTab'), - null, - utils.treeBundle.getString('dropLinkOnTab.never'), - checked - ) == 0; - - behavior = newChildTab ? this.kDROPLINK_NEWTAB : this.kDROPLINK_LOAD ; - if (checked.value) - utils.setTreePref('dropLinksOnTab.behavior', behavior); - - return behavior - }, - kDROPLINK_ASK : 0, - kDROPLINK_FIXED : 1 + 2, - kDROPLINK_LOAD : 1, - kDROPLINK_NEWTAB : 2, - - openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior() - { - var behavior = utils.getTreePref('openGroupBookmark.behavior'); - if (behavior & this.kGROUP_BOOKMARK_FIXED) - return behavior; - - var dummyTabFlag = behavior & this.kGROUP_BOOKMARK_USE_DUMMY; - - var checked = { value : false }; - var button = Services.prompt.confirmEx(this.browserWindow, - utils.treeBundle.getString('openGroupBookmarkBehavior.title'), - utils.treeBundle.getString('openGroupBookmarkBehavior.text'), - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), - utils.treeBundle.getString('openGroupBookmarkBehavior.subTree'), - utils.treeBundle.getString('openGroupBookmarkBehavior.separate'), - null, - utils.treeBundle.getString('openGroupBookmarkBehavior.never'), - checked - ); - - if (button < 0) - button = 1; - var behaviors = [ - this.kGROUP_BOOKMARK_SUBTREE | dummyTabFlag, - this.kGROUP_BOOKMARK_SEPARATE - ]; - behavior = behaviors[button]; - - if (checked.value) { - utils.setTreePref('openGroupBookmark.behavior', behavior); - } - return behavior; - }, - kGROUP_BOOKMARK_ASK : 0, - kGROUP_BOOKMARK_FIXED : 1 + 2 + 4, - kGROUP_BOOKMARK_SUBTREE : 1, - kGROUP_BOOKMARK_SEPARATE : 2, - kGROUP_BOOKMARK_USE_DUMMY : 256, - kGROUP_BOOKMARK_USE_DUMMY_FORCE : 1024, - kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE : 512, - kGROUP_BOOKMARK_EXPAND_ALL_TREE : 2048, - - bookmarkDroppedTabsBehavior : function TSTBase_bookmarkDroppedTabsBehavior() - { - var behavior = utils.getTreePref('bookmarkDroppedTabs.behavior'); - if (behavior & this.kBOOKMARK_DROPPED_TABS_FIXED) - return behavior; - - var checked = { value : false }; - var button = Services.prompt.confirmEx(this.browserWindow, - utils.treeBundle.getString('bookmarkDroppedTabs.title'), - utils.treeBundle.getString('bookmarkDroppedTabs.text'), - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), - utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkAll'), - utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkOnlyParent'), - null, - utils.treeBundle.getString('bookmarkDroppedTabs.never'), - checked - ); - - if (button < 0) - button = 1; - var behaviors = [ - this.kBOOKMARK_DROPPED_TABS_ALL, - this.kBOOKMARK_DROPPED_TABS_ONLY_PARENT - ]; - behavior = behaviors[button]; - - if (checked.value) - utils.setTreePref('bookmarkDroppedTabs.behavior', behavior); - - return behavior; - }, - kBOOKMARK_DROPPED_TABS_ASK : 0, - kBOOKMARK_DROPPED_TABS_FIXED : 1 | 2, - kBOOKMARK_DROPPED_TABS_ALL : 1, - kBOOKMARK_DROPPED_TABS_ONLY_PARENT : 2, - - askUndoCloseTabSetBehavior : function TSTBase_askUndoCloseTabSetBehavior(aRestoredTab, aCount) - { - var behavior = this.undoCloseTabSetBehavior; - if (behavior & this.kUNDO_CLOSE_SET) - behavior ^= this.kUNDO_CLOSE_SET; - - var self = this; - return confirmWithPopup({ - browser : aRestoredTab.linkedBrowser, - label : utils.treeBundle.getFormattedString('undoCloseTabSetBehavior.label', [aCount]), - value : 'treestyletab-undo-close-tree', - image : 'chrome://treestyletab/content/res/icon.png', - buttons : [ - utils.treeBundle.getString('undoCloseTabSetBehavior.restoreOnce'), - utils.treeBundle.getString('undoCloseTabSetBehavior.restoreForever'), - utils.treeBundle.getString('undoCloseTabSetBehavior.ignoreForever') - ], - persistence : -1 // don't hide even if the tab is restored after the panel is shown. - }) - .next(function(aButtonIndex) { - if (aButtonIndex < 2) { - behavior |= self.kUNDO_CLOSE_SET; - } - if (aButtonIndex > 0) { - behavior ^= self.kUNDO_ASK; - utils.setTreePref('undoCloseTabSet.behavior', behavior); - } - return behavior; - }); - }, - get undoCloseTabSetBehavior() - { - return utils.getTreePref('undoCloseTabSet.behavior'); - }, - kUNDO_ASK : 1, - kUNDO_CLOSE_SET : 2, - kUNDO_CLOSE_FULL_SET : 256, - -/* utilities */ - - doAndWaitDOMEvent : function TSTBase_doAndWaitDOMEvent(...aArgs) - { - var type, target, delay, task; - for (let i = 0, maxi = aArgs.length; i < maxi; i++) - { - let arg = aArgs[i]; - switch(typeof arg) - { - case 'string': - type = arg; - continue; - - case 'number': - delay = arg; - continue; - - case 'function': - task = arg; - continue; - - default: - target = arg; - continue; - } - } - - if (!target || !type) { - if (task) - task(); - return; - } - - var done = false; - var listener = function(aEvent) { - setTimeout(function() { - done = true; - }, delay || 0); - target.removeEventListener(type, listener, false); - }; - - if (task) - Deferred.next(function() { - try { - task(); - } - catch(e) { - dump(e+'\n'); - target.removeEventListener(type, listener, false); - done = true; - } - }).error(this.defaultDeferredErrorHandler); - - target.addEventListener(type, listener, false); - - var thread = Components - .classes['@mozilla.org/thread-manager;1'] - .getService() - .mainThread; - while (!done) - { - //dump('WAIT '+type+' '+Date.now()+'\n'); - thread.processNextEvent(true); - } - }, - - findOffsetParent : function TSTBase_findOffsetParent(aNode) - { - var parent = aNode.parentNode; - var doc = aNode.ownerDocument || aNode; - var view = doc.defaultView; - while (parent && parent instanceof Ci.nsIDOMElement) - { - let position = view.getComputedStyle(parent, null).getPropertyValue('position'); - if (position != 'static') - return parent; - parent = parent.parentNode; - } - return doc.documentElement; - }, - - assertBeforeDestruction : function TSTBase_assertBeforeDestruction(aNotDestructed) - { - if (aNotDestructed) - return; - - var message = 'ERROR: accessed after destruction!'; - var error = new Error(message); - dump(message+'\n'+error.stack+'\n'); - throw error; - }, - - defaultDeferredErrorHandler : function TSTBase_defaultDeferredErrorHandler(aError) - { - if (aError.stack) - Cu.reportError(aError.message+'\n'+aError.stack); - else - Cu.reportError(aError); - }, - -// event - - isNewTabAction : function TSTBase_isNewTabAction(aEvent) - { - return aEvent.button == 1 || (aEvent.button == 0 && this.isAccelKeyPressed(aEvent)); - }, - - isAccelKeyPressed : function TSTBase_isAccelKeyPressed(aEvent) - { - if ( // this is releasing of the accel key! - (aEvent.type == 'keyup') && - (aEvent.keyCode == (this.isMac ? Ci.nsIDOMKeyEvent.DOM_VK_META : Ci.nsIDOMKeyEvent.DOM_VK_CONTROL )) - ) { - return false; - } - return this.isMac ? - (aEvent.metaKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_META)) : - (aEvent.ctrlKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL)) ; - }, - - isCopyAction : function TSTBase_isCopyAction(aEvent) - { - return this.isAccelKeyPressed(aEvent) || - (aEvent.dataTransfer && aEvent.dataTransfer.dropEffect == 'copy'); - }, - - isEventFiredOnClosebox : function TSTBase_isEventFiredOnClosebox(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tab-close-button ")]', - aEvent.originalTarget || aEvent.target, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue; - }, - - isEventFiredOnClickable : function TSTBase_isEventFiredOnClickable(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::*[contains(" button toolbarbutton scrollbar nativescrollbar popup menupopup panel tooltip splitter textbox ", concat(" ", local-name(), " "))]', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue; - }, - - isEventFiredOnScrollbar : function TSTBase_isEventFiredOnScrollbar(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::*[local-name()="scrollbar" or local-name()="nativescrollbar"]', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue; - }, - - isEventFiredOnTwisty : function TSTBase_isEventFiredOnTwisty(aEvent) - { - var tab = this.getTabFromEvent(aEvent); - if (!tab || - !this.hasChildTabs(tab) || - !this.canCollapseSubtree(tab)) - return false; - - var twisty = tab.ownerDocument.getAnonymousElementByAttribute(tab, 'class', this.kTWISTY); - if (!twisty) - return false; - - var box = twisty.boxObject; - var left = box.screenX; - var top = box.screenY; - var right = left + box.width; - var bottom = top + box.height; - var favicon = this.getFaviconRect(tab); - if (!box.width || !box.height) { - left = favicon.left; - top = favicon.top; - right = favicon.right; - bottom = favicon.bottom; - } - else if ( - this.shouldExpandTwistyArea && - !this._expandTwistyAreaBlockers.length - ) { - left = Math.min(left, favicon.left); - top = Math.min(top, favicon.top); - right = Math.max(right, favicon.right); - bottom = Math.max(bottom, favicon.bottom); - } - - var x = aEvent.screenX; - var y = aEvent.screenY; - return (x >= left && x <= right && y >= top && y <= bottom); - }, - getFaviconRect : function TSTBase_getFaviconRect(aTab) - { - var icon = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon-image'); - var iconBox = icon.boxObject; - var iconRect = { - left : iconBox.screenX, - top : iconBox.screenY, - right : iconBox.screenX + iconBox.width, - bottom : iconBox.screenY + iconBox.height - }; - - var throbber = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber'); - var throbberBox = throbber.boxObject; - var throbberRect = { - left : throbberBox.screenX, - top : throbberBox.screenY, - right : throbberBox.screenX + throbberBox.width, - bottom : throbberBox.screenY + throbberBox.height - }; - - if (!iconBox.width && !iconBox.height) - return throbberRect; - - if (!throbberBox.width && !throbberBox.height) - return iconRect; - - return { - left : Math.min(throbberRect.left, iconRect.left), - right : Math.max(throbberRect.right, iconRect.right), - top : Math.min(throbberRect.top, iconRect.top), - bottom : Math.max(throbberRect.bottom, iconRect.bottom) - }; - }, - - // called with target(nsIDOMEventTarget), document(nsIDOMDocument), type(string) and data(object) - fireCustomEvent : function TSTBase_fireCustomEvent(...aArgs) - { - var target, document, type, data, canBubble, cancelable; - for (let i = 0, maxi = aArgs.length; i < maxi; i++) - { - let arg = aArgs[i]; - if (typeof arg == 'boolean') { - if (canBubble === void(0)) - canBubble = arg; - else - cancelable = arg; - } - else if (typeof arg == 'string') - type = arg; - else if (arg instanceof Ci.nsIDOMDocument) - document = arg; - else if (arg instanceof Ci.nsIDOMEventTarget) - target = arg; - else - data = arg; - } - if (!target) - target = document; - if (!document) - document = target.ownerDocument || target; - - var event = new this.window.CustomEvent(type, { - bubbles : canBubble, - cancelable : cancelable, - detail : data - }); - return target.dispatchEvent(event); - }, - - registerExpandTwistyAreaBlocker : function TSTBase_registerExpandTwistyAreaBlocker(aBlocker) /* PUBLIC API */ - { - if (this._expandTwistyAreaBlockers.indexOf(aBlocker) < 0) - this._expandTwistyAreaBlockers.push(aBlocker); - }, - _expandTwistyAreaBlockers : [], - - registerExpandTwistyAreaAllowance : function TSTBase_registerExpandTwistyAreaAllowance(aAllowance) /* PUBLIC API, obsolete, for backward compatibility */ - { - this.registerExpandTwistyAreaBlocker(aAllowance.toSource()); - }, - -// string - - makeNewId : function TSTBase_makeNewId() - { - return 'tab-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>'; - }, - - makeNewClosedSetId : function TSTBase_makeNewId() - { - return 'tabs-closed-set-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>'; - }, - - makeURIFromSpec : function TSTBase_makeURIFromSpec(aURI) - { - var newURI; - aURI = aURI || ''; - if (aURI && String(aURI).indexOf('file:') == 0) { - var fileHandler = Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler); - var tempLocalFile = fileHandler.getFileFromURLSpec(aURI); - newURI = Services.io.newFileURI(tempLocalFile); - } - else { - if (!/^\w+\:/.test(aURI)) - aURI = 'http://'+aURI; - newURI = Services.io.newURI(aURI, null, null); - } - return newURI; - }, - - getGroupTabURI : function TSTBase_getGroupTabURI(aOptions) - { - aOptions = aOptions || {}; - var parameters = []; - parameters.push('title=' + encodeURIComponent(aOptions.title || '')); - parameters.push('temporary=' + !!aOptions.temporary); - return 'about:treestyletab-group?' + parameters.join('&'); - }, - -// xpath - - NSResolver : { - lookupNamespaceURI : function(aPrefix) - { - switch (aPrefix) - { - case 'xul': - return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; - case 'html': - case 'xhtml': - return 'http://www.w3.org/1999/xhtml'; - case 'xlink': - return 'http://www.w3.org/1999/xlink'; - default: - return ''; - } - } - }, - - evaluateXPath : function TSTBase_evaluateXPath(aExpression, aContext, aType) - { - if (!aType) - aType = Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE; - try { - var XPathResult = (aContext.ownerDocument || aContext).evaluate( - aExpression, - (aContext || document), - this.NSResolver, - aType, - null - ); - } - catch(e) { - return { - singleNodeValue : null, - snapshotLength : 0, - snapshotItem : function() { - return null - } - }; - } - return XPathResult; - }, - - getArrayFromXPathResult : function TSTBase_getArrayFromXPathResult(aXPathResult) - { - var max = aXPathResult.snapshotLength; - var array = new Array(max); - if (!max) - return array; - - for (var i = 0; i < max; i++) - { - array[i] = aXPathResult.snapshotItem(i); - } - - return array; - }, - -/* Session Store API */ - - getTabValue : function TSTBase_getTabValue(aTab, aKey) - { - var value = ''; - try { - value = SessionStore.getTabValue(aTab, aKey); - } - catch(e) { - } - - if (this.useTMPSessionAPI) { - let TMPValue = aTab.getAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey); - if (TMPValue) - value = TMPValue; - } - - return value; - }, - - setTabValue : function TSTBase_setTabValue(aTab, aKey, aValue) - { - if (!aValue) - return this.deleteTabValue(aTab, aKey); - - aTab.setAttribute(aKey, aValue); - try { - this.checkCachedSessionDataExpiration(aTab); - SessionStore.setTabValue(aTab, aKey, String(aValue)); - } - catch(e) { - } - - if (this.useTMPSessionAPI) - aTab.setAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey, aValue); - - return aValue; - }, - - deleteTabValue : function TSTBase_deleteTabValue(aTab, aKey) - { - aTab.removeAttribute(aKey); - try { - this.checkCachedSessionDataExpiration(aTab); - SessionStore.setTabValue(aTab, aKey, ''); - SessionStore.deleteTabValue(aTab, aKey); - } - catch(e) { - } - - if (this.useTMPSessionAPI) - aTab.removeAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey); - }, - - // workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm - // This is obsolete for lately Firefox and no need to be updated. See: https://github.com/piroor/treestyletab/issues/508#issuecomment-17526429 - checkCachedSessionDataExpiration : function TSTBase_checkCachedSessionDataExpiration(aTab) - { - var data = aTab.linkedBrowser.__SS_data; - if (data && - data._tabStillLoading && - aTab.getAttribute('busy') != 'true' && - !utils.isTabNeedToBeRestored(aTab)) - data._tabStillLoading = false; - }, - - markAsClosedSet : function TSTBase_markAsClosedSet(aTabs) /* PUBLIC API */ - { - if (!aTabs || aTabs.length <= 1) - return; - var id = this.makeNewClosedSetId() + '::' + aTabs.length; - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - this.setTabValue(aTabs[i], this.kCLOSED_SET_ID, id); - } - }, - - unmarkAsClosedSet : function TSTBase_unmarkAsClosedSet(aTabs) /* PUBLIC API */ - { - if (!aTabs || !aTabs.length) - return; - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - this.deleteTabValue(aTabs[i], this.kCLOSED_SET_ID); - } - }, - - useTMPSessionAPI : false, - - kTMP_SESSION_DATA_PREFIX : 'tmp-session-data-', - -// tab - - getTabStrip : function TSTBase_getTabStrip(aTabBrowser) - { - if (!(aTabBrowser instanceof Ci.nsIDOMElement)) - return null; - - var strip = aTabBrowser.mStrip; - return (strip && strip instanceof Ci.nsIDOMElement) ? - strip : - this.evaluateXPath( - 'ancestor::xul:toolbar[1]', - aTabBrowser.tabContainer, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue || aTabBrowser.tabContainer.parentNode; - }, - get tabStrip() - { - return this.getTabStrip(this.browser); - }, - - getTabContainerBox : function TSTBase_getTabContainerBox(aTabBrowser) - { - if (!(aTabBrowser instanceof Ci.nsIDOMElement)) - return null; - - var strip = this.getTabStrip(aTabBrowser); - return strip.treeStyleTabToolbarInnerBox || aTabBrowser.tabContainer; - }, - get tabContainerBox() - { - return this.getTabContainerBox(this.browser); - }, - - setTabbrowserAttribute : function TSTBase_setTabbrowserAttribute(aName, aValue, aTabBrowser) - { - aTabBrowser = aTabBrowser || this.mTabBrowser || this.browser; - if (aValue) { - aTabBrowser.setAttribute(aName, aValue); - aTabBrowser.mTabContainer.setAttribute(aName, aValue); - aTabBrowser.treeStyleTab.setTabStripAttribute(aName, aValue); - } - else { - aTabBrowser.removeAttribute(aName); - aTabBrowser.mTabContainer.removeAttribute(aName); - aTabBrowser.treeStyleTab.removeTabStripAttribute(aName); - } - }, - - removeTabbrowserAttribute : function TSTBase_removeTabbrowserAttribute(aName, aTabBrowser) - { - this.setTabbrowserAttribute(aName, null, aTabBrowser); - }, - - setTabStripAttribute : function TSTBase_setTabStripAttribute(aAttr, aValue) - { - var strip = this.tabStrip; - if (!strip) - return; - var isFeatureAttribute = aAttr.indexOf('treestyletab-') == 0; - if (aValue) { - if (this._tabStripPlaceHolder) - this._tabStripPlaceHolder.setAttribute(aAttr, aValue); - if (!this._tabStripPlaceHolder || aAttr != 'ordinal') - strip.setAttribute(aAttr, aValue); - if (strip.treeStyleTabToolbarInnerBox) - strip.treeStyleTabToolbarInnerBox.setAttribute(aAttr, aValue); - if (isFeatureAttribute) { - // Only attributes for TST's feature are applied to the root element. - // (width, height, and other general attributes have to be ignored!) - strip.ownerDocument.defaultView.setTimeout(function(aSelf) { - strip.ownerDocument.documentElement.setAttribute(aAttr, aValue); - }, 10, this); - } - } - else { - if (this._tabStripPlaceHolder) - this._tabStripPlaceHolder.removeAttribute(aAttr); - if (!this._tabStripPlaceHolder || aAttr != 'ordinal') - strip.removeAttribute(aAttr); - if (strip.treeStyleTabToolbarInnerBox) - strip.treeStyleTabToolbarInnerBox.removeAttribute(aAttr); - if (isFeatureAttribute) { - strip.ownerDocument.defaultView.setTimeout(function(aSelf) { - strip.ownerDocument.documentElement.removeAttribute(aAttr); - }, 10, this); - } - } - }, - - removeTabStripAttribute : function TSTBase_removeTabStripAttribute(aAttr) - { - this.setTabStripAttribute(aAttr, null); - }, - - getTabFromChild : function TSTBase_getTabFromChild(aTab) - { - return this.evaluateXPath( - 'ancestor-or-self::xul:tab', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getTabFromEvent : function TSTBase_getTabFromEvent(aEvent) - { - return this.getTabFromChild(aEvent.originalTarget || aEvent.target); - }, - - getNewTabButtonFromEvent : function TSTBase_getNewTabButtonFromEvent(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::*[' - +'@id="new-tab-button" or ' - +'contains(concat(" ", normalize-space(@class), " "), " tabs-newtab-button ")' - +'][1]', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getSplitterFromEvent : function TSTBase_getSplitterFromEvent(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::xul:splitter[contains(concat(" ", normalize-space(@class), " "), " '+this.kSPLITTER+' ")]', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - isEventFiredOnGrippy : function TSTBase_isEventFiredOnGrippy(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::xul:grippy', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue; - }, - - getTabFromBrowser : function TSTBase_getTabFromBrowser(aBrowser, aTabBrowser) - { - var b = aTabBrowser || this.browser; - var tabs = this.getAllTabs(b); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - if (tab.linkedBrowser == aBrowser) - return tab; - } - return null; - }, - - getTabFromFrame : function TSTBase_getTabFromFrame(aFrame, aTabBrowser) - { - var b = aTabBrowser || this.browser; - var top = aFrame.top; - var tabs = this.getAllTabs(b); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - if (tab.linkedBrowser.contentWindow == top) - return tab; - } - return null; - }, - - getTabbarFromChild : function TSTBase_getTabbarFromChild(aNode) - { - return this.evaluateXPath( - 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' + - 'ancestor-or-self::xul:tabs[@tabbrowser] | ' + - 'ancestor-or-self::xul:toolbar/child::xul:tabs[@tabbrowser]', - aNode, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - getAncestorTabbarFromChild : function TSTBase_getAncestorTabbarFromChild(aNode) - { - return this.evaluateXPath( - 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' + - 'ancestor-or-self::xul:tabs[@tabbrowser]', - aNode, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getTabbarFromEvent : function TSTBase_getTabbarFromEvent(aEvent) - { - return this.getTabbarFromChild(aEvent.originalTarget || aEvent.target); - }, - getAncestorTabbarFromEvent : function TSTBase_getAncestorTabbarFromEvent(aEvent) - { - return this.getAncestorTabbarFromChild(aEvent.originalTarget || aEvent.target); - }, - - cleanUpTabsArray : function TSTBase_cleanUpTabsArray(aTabs) - { - var newTabs = []; - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (!tab || !tab.parentNode) - continue; // ignore removed tabs - if (newTabs.indexOf(tab) < 0) - newTabs.push(tab); - } - newTabs.sort(this.sortTabsByOrder); - return newTabs; - }, - - sortTabsByOrder : function TSTBase_sortTabsByOrder(aA, aB) - { - return aA._tPos - aB._tPos; - }, - - gatherSubtreeMemberTabs : function TSTBase_gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren) - { - var tabs = aTabOrTabs; - if (!(tabs instanceof Array)) { - tabs = [aTabOrTabs]; - } - - var b = this.getTabBrowserFromChild(tabs[0]); - var descendant = []; - for (var i = 0, maxi = tabs.length; i < maxi; i++) - { - descendant = descendant.concat(b.treeStyleTab.getDescendantTabs(tabs[i])); - } - - return this.cleanUpTabsArray(aOnlyChildren ? descendant : tabs.concat(descendant)); - }, - - splitTabsToSubtrees : function TSTBase_splitTabsToSubtrees(aTabs) /* PUBLIC API */ - { - var groups = []; - - var group = []; - aTabs = this.cleanUpTabsArray(aTabs); - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - let parent = this.getParentTab(tab); - if (!parent || group.indexOf(parent) < 0) { - if (group.length) - groups.push(group); - group = [tab]; - } - else { - group.push(tab); - } - } - if (group.length) - groups.push(group); - return groups; - }, - -// tabbrowser - - getTabBrowserFromChild : function TSTBase_getTabBrowserFromChild(aTabBrowserChild) - { - if (!aTabBrowserChild) - return null; - - if (aTabBrowserChild.__treestyletab__linkedTabBrowser) // tab - return aTabBrowserChild.__treestyletab__linkedTabBrowser; - - if (aTabBrowserChild.localName == 'tabbrowser') // itself - return aTabBrowserChild; - - if (aTabBrowserChild.tabbrowser) // tabs - return aTabBrowserChild.tabbrowser; - - if (aTabBrowserChild.localName == 'toolbar') // tabs toolbar - return aTabBrowserChild.getElementsByTagName('tabs')[0].tabbrowser; - - // tab context menu - var popup = this.evaluateXPath( - 'ancestor-or-self::xul:menupopup[@id="tabContextMenu"]', - aTabBrowserChild, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - if (popup && 'TabContextMenu' in aTabBrowserChild.ownerDocument.defaultView) - return this.getTabBrowserFromChild(aTabBrowserChild.ownerDocument.defaultView.TabContextMenu.contextTab); - - var b = this.evaluateXPath( - 'ancestor::xul:tabbrowser | '+ - 'ancestor::xul:tabs[@tabbrowser] |'+ - 'ancestor::xul:toolbar/descendant::xul:tabs', - aTabBrowserChild, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - return (b && b.tabbrowser) || b; - }, - - getTabBrowserFromFrame : function TSTBase_getTabBrowserFromFrame(aFrame) - { - var w = this.browserWindow; - return !w ? null : - ('SplitBrowser' in w) ? this.getTabBrowserFromChild(w.SplitBrowser.getSubBrowserAndBrowserFromFrame(aFrame.top).browser) : - this.browser ; - }, - - getFrameFromTabBrowserElements : function TSTBase_getFrameFromTabBrowserElements(aFrameOrTabBrowser) - { - var frame = aFrameOrTabBrowser; - if (frame == '[object XULElement]') { - if (frame.localName == 'tab') { - frame = frame.linkedBrowser.contentWindow; - } - else if (frame.localName == 'browser') { - frame = frame.contentWindow; - } - else { - frame = this.getTabBrowserFromChild(frame); - if (!frame) - return null; - frame = frame.contentWindow; - } - } - if (!frame) - frame = this.browser.contentWindow; - - return frame; - }, - -/* get tab(s) */ - - getTabById : function TSTBase_getTabById(aId, aTabBrowserChildren) - { - if (!aId) - return null; - - if (aTabBrowserChildren && !(aTabBrowserChildren instanceof Ci.nsIDOMNode)) - aTabBrowserChildren = null; - - var b = this.getTabBrowserFromChild(aTabBrowserChildren) || this.browser; - - if (this.tabsHash) // XPath-less implementation - return this.tabsHash[aId] || null; - - return b.mTabContainer.querySelector('tab['+this.kID+'="'+aId+'"]'); - }, - - isTabDuplicated : function TSTBase_isTabDuplicated(aTab) - { - if (!aTab) - return false; - var id = this.getTabValue(aTab, this.kID); - var b = this.getTabBrowserFromChild(aTab) || this.browser; - var tabs = b.mTabContainer.querySelectorAll('tab['+this.kID+'="'+id+'"], tab['+this.kID_RESTORING+'="'+id+'"]'); - return tabs.length > 1; - }, - - /** - * Returns all tabs in the current group as an array. - * It includes tabs hidden by Tab Panorama. - */ - getAllTabs : function TSTBase_getTabs(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - this.assertBeforeDestruction(b && b.mTabContainer); - return Array.slice(b.mTabContainer.querySelectorAll('tab')); - }, - - /** - * Returns all tabs in the current group as an array. - * It excludes tabs hidden by Tab Panorama. - */ - getTabs : function TSTBase_getTabs(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - this.assertBeforeDestruction(b && b.mTabContainer); - return Array.slice(b.mTabContainer.querySelectorAll('tab:not([hidden="true"])')); - }, - - getAllTabsArray : function TSTBase_getAllTabsArray(aTabBrowserChild) /* for backward compatibility */ - { - return this.getAllTabs(aTabBrowserChild); - }, - - getTabsArray : function TSTBase_getTabsArray(aTabBrowserChild) /* for backward compatibility */ - { - return this.getTabs(aTabBrowserChild); - }, - - /** - * Returns the first tab in the current group. - */ - getFirstTab : function TSTBase_getFirstTab(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - this.assertBeforeDestruction(b && b.mTabContainer); - var tabs = b.visibleTabs; - return tabs ? tabs[0] : b.mTabContainer.firstChild; - }, - - /** - * Returns the first visible, not collapsed, and not pinned tab. - */ - getFirstNormalTab : function TSTBase_getFirstNormalTab(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - this.assertBeforeDestruction(b && b.mTabContainer); - return b.mTabContainer.querySelector('tab:not([pinned="true"]):not([hidden="true"])'); - }, - - /** - * Returns the last tab in the current group. - */ - getLastTab : function TSTBase_getLastTab(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - this.assertBeforeDestruction(b && b.mTabContainer); - var tabs = b.visibleTabs; - return tabs ? tabs[tabs.length-1] : b.mTabContainer.lastChild ; - }, - - /** - * Returns the next tab in the current group. - */ - getNextTab : function TSTBase_getNextTab(aTab) - { - if (!aTab) - return null; - var b = this.getTabBrowserFromChild(aTab); - this.assertBeforeDestruction(b && b.mTabContainer); - var tabs = b.visibleTabs; - if (tabs) { - let index = tabs.indexOf(aTab); - if (index > -1) - return tabs.length > index ? tabs[index+1] : null - } - var tab = aTab.nextSibling; - return (tab && tab.localName == 'tab') ? tab : null ; - }, - - /** - * Returns the previous tab in the current group. - */ - getPreviousTab : function TSTBase_getPreviousTab(aTab) - { - if (!aTab) - return null; - var b = this.getTabBrowserFromChild(aTab); - this.assertBeforeDestruction(b && b.mTabContainer); - var tabs = b.visibleTabs; - if (tabs) { - let index = tabs.indexOf(aTab); - if (index > -1) - return 0 < index ? tabs[index-1] : null - } - var tab = aTab.previousSibling; - return (tab && tab.localName == 'tab') ? tab : null ; - }, - - /** - * Returns the index of the specified tab, in the current group. - */ - getTabIndex : function TSTBase_getTabIndex(aTab) - { - if (!aTab) - return -1; - var b = this.getTabBrowserFromChild(aTab); - return this.getTabs(b).indexOf(aTab); - }, - - /** - * Returns the next not collapsed tab in the current group. - */ - getNextVisibleTab : function TSTBase_getNextVisibleTab(aTab) - { - if (!aTab) - return null; - - var b = this.getTabBrowserFromChild(aTab); - if (!this.canCollapseSubtree(b)) - return this.getNextTab(aTab); - - var tabs = this.getVisibleTabs(b); - if (tabs.indexOf(aTab) < 0) - tabs.push(aTab); - tabs.sort(this.sortTabsByOrder); - - var index = tabs.indexOf(aTab); - return (index < tabs.length-1) ? tabs[index+1] : null ; - }, - - /** - * Returns the previous not collapsed tab in the current group. - */ - getPreviousVisibleTab : function TSTBase_getPreviousVisibleTab(aTab) - { - if (!aTab) - return null; - - var b = this.getTabBrowserFromChild(aTab); - if (!this.canCollapseSubtree(b)) - return this.getPreviousTab(aTab); - - var tabs = this.getVisibleTabs(b); - if (tabs.indexOf(aTab) < 0) - tabs.push(aTab); - tabs.sort(this.sortTabsByOrder); - - var index = tabs.indexOf(aTab); - return (index > 0) ? tabs[index-1] : null ; - }, - - /** - * Returns the last not collapsed tab in the current group. - */ - getLastVisibleTab : function TSTBase_getLastVisibleTab(aTabBrowserChild) - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - if (!b) - return null; - var tabs = this.getVisibleTabs(b); - return tabs.length ? tabs[tabs.length-1] : null ; - }, - - /** - * Returns a XPathResult of not collapsed tabs in the current group. - */ - getVisibleTabs : function TSTBase_getVisibleTabs(aTabBrowserChild) /* OBSOLETE */ - { - var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); - if (!this.canCollapseSubtree(b)) - return this.getTabs(b); - return Array.slice(b.mTabContainer.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"]):not([hidden="true"])')); - }, - - getVisibleTabsArray : function TSTBase_getVisibleTabsArray(aTabBrowserChild) /* for backward compatibility */ - { - return this.getVisibleTabs(aTabBrowserChild); - }, - - /** - * Returns the index of the specified tab, in the array of not collapsed - * tabs in the current group. - */ - getVisibleIndex : function TSTBase_getVisibleIndex(aTab) - { - if (!aTab) - return -1; - var b = this.getTabBrowserFromChild(aTab); - return this.getVisibleTabs(b).indexOf(aTab); - }, - - /** - * Returns tabs which are newly opened in the given operation. - */ - getNewTabsWithOperation : function TSTBase_getNewTabsWithOperation(aOperation, aTabBrowser) - { - var previousTabs = this.getTabsInfo(aTabBrowser); - aOperation.call(this); - return this.getNewTabsFromPreviousTabsInfo(aTabBrowser, previousTabs); - }, - - /** - * Returns tabs which are newly opened. This requires the "previous state". - */ - getNewTabsFromPreviousTabsInfo : function TSTBase_getNewTabsFromPreviousTabsInfo(aTabBrowser, aTabsInfo) - { - var tabs = this.getTabs(aTabBrowser); - var currentTabsInfo = this.getTabsInfo(aTabBrowser); - return tabs.filter(function(aTab, aIndex) { - return aTabsInfo.indexOf(currentTabsInfo[aIndex]) < 0; - }); - }, - getTabsInfo : function TSTBase_getTabsInfo(aTabBrowser) - { - var tabs = this.getTabs(aTabBrowser); - return tabs.map(function(aTab) { - return aTab.getAttribute(this.kID)+'\n'+ - aTab.getAttribute('busy')+'\n'+ - aTab.linkedBrowser.currentURI.spec; - }, this); - }, - -/* notify "ready to open child tab(s)" */ - - readyToOpenChildTab : function TSTBase_readyToOpenChildTab(aFrameOrTabBrowser, aMultiple, aInsertBefore) /* PUBLIC API */ - { - if (!utils.getTreePref('autoAttach')) - return false; - - var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); - if (!frame) - return false; - - var ownerBrowser = this.getTabBrowserFromFrame(frame); - - var parentTab = this.getTabFromFrame(frame, ownerBrowser); - if (!parentTab || parentTab.getAttribute('pinned') == 'true') - return false; - - ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab); - var parentId = parentTab.getAttribute(this.kID); - - var refId = null; - if (aInsertBefore) { - ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab); - refId = aInsertBefore.getAttribute(this.kID); - } - - dump('Tree Style Tab: new child tab is requested.\n'+new Error().stack+'\n'); - - ownerBrowser.treeStyleTab.readiedToAttachNewTab = true; - ownerBrowser.treeStyleTab.readiedToAttachMultiple = aMultiple || false ; - ownerBrowser.treeStyleTab.multipleCount = aMultiple ? 0 : -1 ; - ownerBrowser.treeStyleTab.parentTab = parentId; - ownerBrowser.treeStyleTab.insertBefore = refId; - - return true; - }, - /** - * Extended version. If you don't know whether a new tab will be actually - * opened or not (by the command called after TST's API), then use this. - * This version automatically cancels the "ready" state with delay. - */ - readyToOpenChildTabNow : function TSTBase_readyToOpenChildTabNow(...aArgs) /* PUBLIC API */ - { - if (this.readyToOpenChildTab.apply(this, aArgs)) { - let self = this; - this.Deferred.next(function() { - self.stopToOpenChildTab(aArgs[0]); - }).error(this.defaultDeferredErrorHandler); - return true; - } - return false; - }, - - readyToOpenNextSiblingTab : function TSTBase_readyToOpenNextSiblingTab(aFrameOrTabBrowser) /* PUBLIC API */ - { - var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); - if (!frame) - return false; - - var ownerBrowser = this.getTabBrowserFromFrame(frame); - - var tab = this.getTabFromFrame(frame, ownerBrowser); - if (!tab || tab.getAttribute('pinned') == 'true') - return false; - - var parentTab = this.getParentTab(tab); - var nextTab = this.getNextSiblingTab(tab); - if (parentTab) { - /** - * If the base tab has a parent, open the new tab as a child of - * the parent tab. - */ - return this.readyToOpenChildTab(parentTab, false, nextTab); - } - else { - /** - * Otherwise, open the tab as a new root tab. If there is no - * tab next to the base tab (in other words, if the tab is the - * last tab), then do nothing. - */ - if (!nextTab) - return; - ownerBrowser.treeStyleTab.readiedToAttachNewTab = true; - ownerBrowser.treeStyleTab.parentTab = null; - ownerBrowser.treeStyleTab.insertBefore = nextTab.getAttribute(this.kID); - return true; - } - }, - /** - * Extended version. If you don't know whether a new tab will be actually - * opened or not (by the command called after TST's API), then use this. - * This version automatically cancels the "ready" state with delay. - */ - readyToOpenNextSiblingTabNow : function TSTBase_readyToOpenNextSiblingTabNow(...aArgs) /* PUBLIC API */ - { - if (this.readyToOpenNextSiblingTab.apply(this, aArgs)) { - let self = this; - this.Deferred.next(function() { - self.stopToOpenChildTab(aArgs[0]); - }).error(this.defaultDeferredErrorHandler); - return true; - } - return false; - }, - - readyToOpenNewTabGroup : function TSTBase_readyToOpenNewTabGroup(aFrameOrTabBrowser, aTreeStructure, aExpandAllTree) /* PUBLIC API */ - { - if (!utils.getTreePref('autoAttach')) - return false; - - var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); - if (!frame) - return false; - - this.stopToOpenChildTab(frame); - - var ownerBrowser = this.getTabBrowserFromFrame(frame); - ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = true; - ownerBrowser.treeStyleTab.readiedToAttachMultiple = true; - ownerBrowser.treeStyleTab.multipleCount = 0; - ownerBrowser.treeStyleTab.treeStructure = aTreeStructure; - ownerBrowser.treeStyleTab.shouldExpandAllTree = !!aExpandAllTree; - - return true; - }, - /** - * Extended version. If you don't know whether new tabs will be actually - * opened or not (by the command called after TST's API), then use this. - * This version automatically cancels the "ready" state with delay. - */ - readyToOpenNewTabGroupNow : function TSTBase_readyToOpenNewTabGroupNow(...aArgs) /* PUBLIC API */ - { - - if (this.readyToOpenNewTabGroup.apply(this, aArgs)) { - let self = this; - this.Deferred.next(function() { - self.stopToOpenChildTab(aArgs[0]); - }).error(this.defaultDeferredErrorHandler); - return true; - } - return false; - }, - - stopToOpenChildTab : function TSTBase_stopToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */ - { - var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); - if (!frame) - return false; - - var ownerBrowser = this.getTabBrowserFromFrame(frame); - ownerBrowser.treeStyleTab.readiedToAttachNewTab = false; - ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = false; - ownerBrowser.treeStyleTab.readiedToAttachMultiple = false; - ownerBrowser.treeStyleTab.multipleCount = -1; - ownerBrowser.treeStyleTab.parentTab = null; - ownerBrowser.treeStyleTab.insertBefore = null; - ownerBrowser.treeStyleTab.treeStructure = null; - ownerBrowser.treeStyleTab.shouldExpandAllTree = false; - - return true; - }, - - checkToOpenChildTab : function TSTBase_checkToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */ - { - var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); - if (!frame) - return false; - - var ownerBrowser = this.getTabBrowserFromFrame(frame); - return !!(ownerBrowser.treeStyleTab.readiedToAttachNewTab || ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup); - }, - - kNEWTAB_DO_NOTHING : -1, - kNEWTAB_OPEN_AS_ORPHAN : 0, - kNEWTAB_OPEN_AS_CHILD : 1, - kNEWTAB_OPEN_AS_SIBLING : 2, - kNEWTAB_OPEN_AS_NEXT_SIBLING : 3, - readyToOpenRelatedTabAs : function TSTBase_readyToOpenRelatedTabAs(aBaseTab, aBehavior) - { - var frame = this.getFrameFromTabBrowserElements(aBaseTab); - if (!frame) - return; - - aBaseTab = this.getTabFromFrame(frame, this.getTabBrowserFromFrame(frame)); - - switch (aBehavior) - { - case this.kNEWTAB_OPEN_AS_ORPHAN: - case this.kNEWTAB_DO_NOTHING: - default: - break; - case this.kNEWTAB_OPEN_AS_CHILD: - this.readyToOpenChildTabNow(aBaseTab); - break; - case this.kNEWTAB_OPEN_AS_SIBLING: - let (parentTab = this.getParentTab(aBaseTab)) { - if (parentTab) - this.readyToOpenChildTabNow(parentTab); - } - break; - case this.kNEWTAB_OPEN_AS_NEXT_SIBLING: - this.readyToOpenNextSiblingTabNow(aBaseTab); - break; - } - }, - - handleNewTabFromCurrent : function TSTBase_handleNewTabFromCurrent(aBaseTab) - { - this.readyToOpenRelatedTabAs(aBaseTab, utils.getTreePref('autoAttach.fromCurrent')); - }, - -/* tree manipulations */ - - get treeViewEnabled() /* PUBLIC API */ - { - return this._treeViewEnabled; - }, - set treeViewEnabled(aValue) - { - this._treeViewEnabled = !!aValue; - Services.obs.notifyObservers( - window, - this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, - this._treeViewEnabled - ); - return aValue; - }, - _treeViewEnabled : true, - - get rootTabs() /* PUBLIC API */ - { - return Array.slice(this.browser.mTabContainer.querySelectorAll('tab:not(['+this.kNEST+']), tab['+this.kNEST+'=""], tab['+this.kNEST+'="0"]')); - }, - - get allRootTabs() /* PUBLIC API */ - { - return this.rootTabs; - }, - - get visibleRootTabs() /* PUBLIC API */ - { - return this.rootTabs.filter(function(aTab) { - return !aTab.hidden; - }); - }, - - canCollapseSubtree : function TSTBase_canCollapseSubtree(aTabOrTabBrowser) /* PUBLIC API */ - { - if (aTabOrTabBrowser && - aTabOrTabBrowser.localName == 'tab' && - aTabOrTabBrowser.getAttribute(this.kALLOW_COLLAPSE) != 'true') - return false; - - var b = this.getTabBrowserFromChild(aTabOrTabBrowser) || this.browser; - return b && b.getAttribute(this.kALLOW_COLLAPSE) == 'true'; - }, - - isCollapsed : function TSTBase_isCollapsed(aTab) /* PUBLIC API */ - { - if (!aTab || - !this.canCollapseSubtree(this.getRootTab(aTab))) - return false; - - return aTab.getAttribute(this.kCOLLAPSED) == 'true'; - }, - - isSubtreeCollapsed : function TSTBase_isSubtreeCollapsed(aTab) /* PUBLIC API */ - { - if (!aTab || !this.canCollapseSubtree(aTab) || !this.hasChildTabs(aTab)) - return false; - - return aTab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true'; - }, - - shouldCloseTabSubtreeOf : function TSTBase_shouldCloseTabSubtreeOf(aTab) - { - return ( - this.hasChildTabs(aTab) && - ( - utils.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN || - this.isSubtreeCollapsed(aTab) - ) - ); - }, - shouldCloseTabSubTreeOf : function TSTBase_shouldCloseTabSubTreeOf(...aArgs) { - return this.shouldCloseTabSubtreeOf.apply(this, aArgs); - }, // obsolete, for backward compatibility - - shouldCloseLastTabSubtreeOf : function TSTBase_shouldCloseLastTabSubtreeOf(aTab) - { - var b = this.getTabBrowserFromChild(aTab); - return ( - b && - this.shouldCloseTabSubtreeOf(aTab) && - this.getDescendantTabs(aTab).length + 1 == this.getAllTabs(b).length - ); - }, - shouldCloseLastTabSubTreeOf : function TSTBase_shouldCloseLastTabSubTreeOf(...aArgs) { - return this.shouldCloseLastTabSubtreeOf.apply(this, aArgs); - }, // obsolete, for backward compatibility - - getParentTab : function TSTBase_getParentTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - var parent; - var id = aTab.getAttribute(this.kPARENT); - if (this.tabsHash) { // XPath-less implementation - parent = this.getTabById(id); - if (parent && !parent.parentNode && this.tabsHash) { - delete this.tabsHash[id]; - parent = null; - } - } - else { - parent = this.evaluateXPath( - 'preceding-sibling::xul:tab[@'+this.kID+'="'+id+'"][1]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - } - return (parent && parent != aTab) ? parent : null ; - }, - - getAncestorTabs : function TSTBase_getAncestorTabs(aTab) /* PUBLIC API */ - { - var tabs = [aTab]; - var parentTab = aTab; - while (parentTab = this.getParentTab(parentTab)) - { - if (tabs.indexOf(parentTab) > -1) { - let message = 'recursive tree detected!\n'+ - tabs.concat([parentTab]) - .reverse().map(function(aTab) { - return ' '+aTab._tPos+' : '+ - aTab.label+'\n '+ - aTab.getAttribute(this.kID); - }, this).join('\n'); - dump(message+'\n'); - break; - } - - if (aTab._tPos < parentTab._tPos) { - let message = 'broken tree detected!\n'+ - tabs.concat([parentTab]) - .reverse().map(function(aTab) { - return ' '+aTab._tPos+' : '+ - aTab.label+'\n '+ - aTab.getAttribute(this.kID); - }, this).join('\n'); - dump(message+'\n'); - } - - tabs.push(parentTab); - aTab = parentTab; - } - return tabs.slice(1); - }, - - getRootTab : function TSTBase_getRootTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let ancestors = this.getAncestorTabs(aTab); - return ancestors.length ? ancestors[ancestors.length-1] : aTab ; - } - - return this.evaluateXPath( - '(self::*[not(@'+this.kPARENT+')] | preceding-sibling::xul:tab[not(@'+this.kPARENT+')])[last()]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getNextSiblingTab : function TSTBase_getNextSiblingTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let parentTab = this.getParentTab(aTab); - - if (!parentTab) { - let next = aTab; - do { - next = next.nextSibling; - } - while (next && - next.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && - this.getParentTab(next)); - return next; - } - - let children = parentTab.getAttribute(this.kCHILDREN); - if (children) { - let list = ('|'+children).split('|'+aTab.getAttribute(this.kID)); - list = list.length > 1 ? list[1].split('|') : [] ; - for (let i = 0, maxi = list.length; i < maxi; i++) - { - let firstChild = this.getTabById(list[i], aTab); - if (firstChild) - return firstChild; - } - } - return null; - } - - var parent = aTab.getAttribute(this.kPARENT); - return this.evaluateXPath( - 'following-sibling::xul:tab['+ - (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ - '][1]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getPreviousSiblingTab : function TSTBase_getPreviousSiblingTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let parentTab = this.getParentTab(aTab); - - if (!parentTab) { - let prev = aTab; - do { - prev = prev.previousSibling; - } - while (prev && - prev.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && - this.getParentTab(prev)); - return prev; - } - - let children = parentTab.getAttribute(this.kCHILDREN); - if (children) { - let list = ('|'+children).split('|'+aTab.getAttribute(this.kID))[0].split('|'); - for (let i = list.length-1; i > -1; i--) - { - let lastChild = this.getTabById(list[i], aTab); - if (lastChild) - return lastChild; - } - } - return null; - } - - var parent = aTab.getAttribute(this.kPARENT); - return this.evaluateXPath( - 'preceding-sibling::xul:tab['+ - (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ - '][1]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getSiblingTabs : function TSTBase_getSiblingTabs(aTab) /* PUBLIC API */ - { - var parent = this.getParentTab(aTab); - - var siblings = parent && parent.parentNode ? this.getChildTabs(parent) : this.visibleRootTabs ; - return siblings.filter(function(aSiblingTab) { - return aSiblingTab != aTab; - }); - }, - - getChildTabs : function TSTBase_getChildTabs(aTab, aAllTabsArray) /* PUBLIC API */ - { - var tabs = []; - if (!aTab) - return tabs; - - var children = aTab.getAttribute(this.kCHILDREN); - if (!children) - return tabs; - - if (aAllTabsArray) - tabs = aAllTabsArray; - - var list = children.split('|'); - for (let i = 0, maxi = list.length; i < maxi; i++) - { - let tab = this.getTabById(list[i], aTab); - if (!tab || tab == aTab) - continue; - if (tabs.indexOf(tab) > -1) { - let message = 'broken (possible recursive) tree detected!\n'+ - tabs.map(function(aTab) { - return ' '+aTab._tPos+' : '+ - aTab.label+'\n '+ - aTab.getAttribute(this.kID); - }, this).join('\n'); - dump(message+'\n'); - continue; - } - tabs.push(tab); - if (aAllTabsArray) - this.getChildTabs(tab, tabs); - } - - return tabs; - }, - - hasChildTabs : function TSTBase_hasChildTabs(aTab) /* PUBLIC API */ - { - if (!aTab) - return false; - return aTab.hasAttribute(this.kCHILDREN); - }, - - getDescendantTabs : function TSTBase_getDescendantTabs(aTab) /* PUBLIC API */ - { - var tabs = []; - this.getChildTabs(aTab, tabs); - return tabs; - }, - - getFirstChildTab : function TSTBase_getFirstChildTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let children = aTab.getAttribute(this.kCHILDREN); - let firstChild = null; - if (children) { - let list = children.split('|'); - for (let i = 0, maxi = list.length; i < maxi; i++) - { - firstChild = this.getTabById(list[i], aTab); - if (firstChild && firstChild != aTab) - break; - } - } - return firstChild; - } - - return this.evaluateXPath( - 'following-sibling::xul:tab[@'+this.kPARENT+'="'+aTab.getAttribute(this.kID)+'"][1]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getLastChildTab : function TSTBase_getLastChildTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let children = aTab.getAttribute(this.kCHILDREN); - let lastChild = null; - if (children) { - let list = children.split('|'); - for (let i = list.length-1; i > -1; i--) - { - lastChild = this.getTabById(list[i], aTab); - if (lastChild && lastChild != aTab) - break; - } - } - return lastChild; - } - - return this.evaluateXPath( - 'following-sibling::xul:tab[@'+this.kPARENT+'="'+aTab.getAttribute(this.kID)+'"][last()]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - getLastDescendantTab : function TSTBase_getLastDescendantTab(aTab) /* PUBLIC API */ - { - if (!aTab) - return null; - - if (this.tabsHash) { // XPath-less implementation - let tabs = this.getDescendantTabs(aTab); - return tabs.length ? tabs[tabs.length-1] : null ; - } - - var parent = aTab.getAttribute(this.kPARENT); - return this.evaluateXPath( - 'following-sibling::xul:tab['+ - (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ - '][1]/preceding-sibling::xul:tab[1][not(@'+this.kID+'="'+aTab.getAttribute(this.kID)+'")]', - aTab, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - collectRootTabs : function TSTBase_collectRootTabs(aTabs) /* PUBLIC API */ - { - aTabs = Array.slice(aTabs); - return aTabs.filter(function(aTab) { - var parent = this.getParentTab(aTab); - return !parent || aTabs.indexOf(parent) < 0; - }, this); - }, - - getChildIndex : function TSTBase_getChildIndex(aTab, aParent) /* PUBLIC API */ - { - var parent = this.getParentTab(aTab); - if (!aParent || !parent || aParent != parent) { - let tabs = [aTab].concat(this.getAncestorTabs(aTab)); - parent = aTab; - for (let i = 0, maxi = tabs.length; i < maxi && parent != aParent; i++) - { - aTab = parent; - parent = i < maxi ? tabs[i+1] : null ; - } - if (parent != aParent) - return -1; - aParent = parent; - } - - if (aParent) { - let children = aParent.getAttribute(this.kCHILDREN); - let list = children.split('|'); - let id = aTab.getAttribute(this.kID); - for (let i = 0, maxi = list.length; i < maxi; i++) - { - if (list[i] == id) - return i; - } - return -1; - } - else { - let tabs = this.rootTabs; - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - if (tabs[i] == aTab) - return i; - } - } - return -1; - }, - - getXOffsetOfTab : function TSTBase_getXOffsetOfTab(aTab) - { - var extraCondition = this.canCollapseSubtree(aTab) ? - '[not(@'+this.kCOLLAPSED+'="true")]' : - '' ; - - return this.evaluateXPath( - 'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+ - '/attribute::'+this.kX_OFFSET+')', - aTab, - Ci.nsIDOMXPathResult.NUMBER_TYPE - ).numberValue; - }, - getYOffsetOfTab : function TSTBase_getYOffsetOfTab(aTab) - { - var extraCondition = this.canCollapseSubtree(aTab) ? - '[not(@'+this.kCOLLAPSED+'="true")]' : - ''; - - return this.evaluateXPath( - 'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+ - '/attribute::'+this.kY_OFFSET+')', - aTab, - Ci.nsIDOMXPathResult.NUMBER_TYPE - ).numberValue; - }, - getFutureBoxObject : function TSTBase_getFutureBoxObject(aTab) - { - var tabBox = aTab.boxObject; - var xOffset = this.getXOffsetOfTab(aTab); - var yOffset = this.getYOffsetOfTab(aTab); - return { - width : tabBox.width, - height : tabBox.height, - x : tabBox.x + xOffset, - y : tabBox.y + yOffset, - screenX : tabBox.screenX + xOffset, - screenY : tabBox.screenY + yOffset - }; - }, - getTabActualScreenPosition : function TSTBase_getTabActualScreenPosition(aTab, aOrient) - { - aOrient = aOrient || aTab.parentNode.orient; - return aOrient == 'vertical' ? - this.getTabActualScreenY(aTab) : - this.getTabActualScreenX(aTab) ; - }, - MATRIX_PATTERN : /matrix\((-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\)/, - getTabActualScreenX : function TSTBase_getTabActualScreenX(aTab) - { - var x = aTab.boxObject.screenX; - - var w = aTab.ownerDocument.defaultView; - var transform = w.getComputedStyle(aTab, null).transform; - var offset = transform && transform.match(this.MATRIX_PATTERN); - offset = offset ? parseFloat(offset[5]) : 0 ; - - return x + offset; - }, - getTabActualScreenY : function TSTBase_getTabActualScreenY(aTab) - { - var y = aTab.boxObject.screenY; - - var w = aTab.ownerDocument.defaultView; - var transform = w.getComputedStyle(aTab, null).transform; - var offset = transform && transform.match(this.MATRIX_PATTERN); - offset = offset ? parseFloat(offset[6]) : 0 ; - - return y + offset; - }, - - isGroupTab : function TSTBase_isGroupTab(aTab, aLazyCheck) - { - return ( - (aLazyCheck || aTab.linkedBrowser.sessionHistory.count == 1) && - aTab.linkedBrowser.currentURI.spec.indexOf('about:treestyletab-group') == 0 - ); - }, - - isTemporaryGroupTab : function TSTBase_isTemporaryGroupTab(aTab) - { - return ( - this.isGroupTab(aTab, true) && - /.*[\?&;]temporary=(?:1|yes|true)/i.test(aTab.linkedBrowser.currentURI.spec) - ); - }, - - get pinnedTabsCount() - { - return this.browser.mTabContainer.querySelectorAll('tab[pinned="true"]').length; - }, - - forceExpandTabs : function TSTBase_forceExpandTabs(aTabs) - { - var collapsedStates = aTabs.map(function(aTab) { - return this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; - }, this); - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - this.collapseExpandSubtree(tab, false, true); - this.collapseExpandTab(tab, false, true); - } - return collapsedStates; - }, - - getTreeStructureFromTabs : function TSTBase_getTreeStructureFromTabs(aTabs) - { - /* this returns... - [A] => -1 (parent is not in this tree) - [B] => 0 (parent is 1st item in this tree) - [C] => 0 (parent is 1st item in this tree) - [D] => 2 (parent is 2nd in this tree) - [E] => -1 (parent is not in this tree, and this creates another tree) - [F] => 0 (parent is 1st item in this another tree) - */ - return this.cleanUpTreeStructureArray( - aTabs.map(function(aTab, aIndex) { - let tab = this.getParentTab(aTab); - let index = tab ? aTabs.indexOf(tab) : -1 ; - return index >= aIndex ? -1 : index ; - }, this), - -1 - ); - }, - cleanUpTreeStructureArray : function TSTBase_cleanUpTreeStructureArray(aTreeStructure, aDefaultParent) - { - var offset = 0; - aTreeStructure = aTreeStructure - .map(function(aPosition, aIndex) { - return (aPosition == aIndex) ? -1 : aPosition ; - }) - .map(function(aPosition, aIndex) { - if (aPosition == -1) { - offset = aIndex; - return aPosition; - } - return aPosition - offset; - }); - - /* The final step, this validates all of values. - Smaller than -1 is invalid, so it becomes to -1. */ - aTreeStructure = aTreeStructure.map(function(aIndex) { - return aIndex < -1 ? aDefaultParent : aIndex ; - }, this); - return aTreeStructure; - }, - - applyTreeStructureToTabs : function TSTBase_applyTreeStructureToTabs(aTabs, aTreeStructure, aExpandStates) - { - var b = this.getTabBrowserFromChild(aTabs[0]); - if (!b) - return; - var sv = b.treeStyleTab; - - aTabs = aTabs.slice(0, aTreeStructure.length); - aTreeStructure = aTreeStructure.slice(0, aTabs.length); - - aExpandStates = (aExpandStates && typeof aExpandStates == 'object') ? - aExpandStates : - aTabs.map(function(aTab) { - return !!aExpandStates; - }); - aExpandStates = aExpandStates.slice(0, aTabs.length); - while (aExpandStates.length < aTabs.length) aExpandStates.push(-1); - - var parentTab = null; - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (sv.isCollapsed(tab)) - sv.collapseExpandTab(tab, false, true); - sv.detachTab(tab); - - let parentIndexInTree = aTreeStructure[i]; - if (parentIndexInTree < 0) // there is no parent, so this is a new parent! - parentTab = tab.getAttribute(sv.kID); - - let parent = sv.getTabById(parentTab); - if (parent) { - let tabs = [parent].concat(sv.getDescendantTabs(parent)); - parent = parentIndexInTree < tabs.length ? tabs[parentIndexInTree] : parent ; - } - if (parent) { - sv.attachTabTo(tab, parent, { - forceExpand : true, - dontMove : true - }); - } - } - - for (let i = aTabs.length-1; i > -1; i--) - { - sv.collapseExpandSubtree(aTabs[i], !sv.hasChildTabs(aTabs[i]) || !aExpandStates[i], true); - } - }, - - getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser) - { - return this.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser)); - }, - - applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree) - { - var tabs = this.getAllTabs(aTabBrowser); - return this.applyTreeStructureToTabs(tabs, aTreeStructure, aExpandAllTree); - }, - -/* tabbar position */ - - get position() /* PUBLIC API */ - { - return utils.getTreePref('tabbar.position') || 'top'; - }, - set position(aValue) - { - var position = String(aValue).toLowerCase(); - if (!position || !/^(top|bottom|left|right)$/.test(position)) - position = 'top'; - - if (position != utils.getTreePref('tabbar.position')) - utils.setTreePref('tabbar.position', position); - - return aValue; - }, - get currentTabbarPosition() /* for backward compatibility */ - { - return this.position; - }, - set currentTabbarPosition(aValue) - { - return this.position = aValue; - }, - - getPositionFlag : function TSTBase_getPositionFlag(aPosition) - { - aPosition = String(aPosition).toLowerCase(); - return (aPosition == 'left') ? this.kTABBAR_LEFT : - (aPosition == 'right') ? this.kTABBAR_RIGHT : - (aPosition == 'bottom') ? this.kTABBAR_BOTTOM : - this.kTABBAR_TOP; - }, - -/* Pref Listener */ - - domains : [ - 'extensions.treestyletab.', - 'browser.tabs.animate', - 'browser.tabs.insertRelatedAfterCurrent', - 'extensions.stm.tabBarMultiRows' // Super Tab Mode - ], - - observe : function TSTBase_observe(aSubject, aTopic, aData) - { - switch (aTopic) - { - case 'nsPref:changed': - this.onPrefChange(aData); - return; - } - }, - - onPrefChange : function TSTBase_onPrefChange(aPrefName) - { - var value = prefs.getPref(aPrefName); - switch (aPrefName) - { - case 'extensions.treestyletab.indent.vertical': - this.baseIndentVertical = value; - Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value); - return; - case 'extensions.treestyletab.indent.horizontal': - this.baseIndentHorizontal = value; - Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value); - return; - - case 'extensions.treestyletab.tabbar.width': - case 'extensions.treestyletab.tabbar.shrunkenWidth': - return this.updateTabWidthPrefs(aPrefName); - - case 'browser.tabs.insertRelatedAfterCurrent': - case 'extensions.stm.tabBarMultiRows': // Super Tab Mode - if (this.prefOverriding) - return; - aPrefName += '.override'; - prefs.setPref(aPrefName, value); - case 'browser.tabs.insertRelatedAfterCurrent.override': - case 'extensions.stm.tabBarMultiRows.override': // Super Tab Mode - if (prefs.getPref(aPrefName+'.force')) { - let defaultValue = prefs.getDefaultPref(aPrefName); - if (value != defaultValue) { - prefs.setPref(aPrefName, defaultValue); - return; - } - } - this.prefOverriding = true; - let (target = aPrefName.replace('.override', '')) { - let originalValue = prefs.getPref(target); - if (originalValue !== null && originalValue != value) - prefs.setPref(target+'.backup', originalValue); - prefs.setPref(target, prefs.getPref(aPrefName)); - } - this.prefOverriding = false; - return; - - case 'extensions.treestyletab.clickOnIndentSpaces.enabled': - return this.shouldDetectClickOnIndentSpaces = prefs.getPref(aPrefName); - - case 'extensions.treestyletab.tabbar.scroll.smooth': - return this.smoothScrollEnabled = value; - case 'extensions.treestyletab.tabbar.scroll.duration': - return this.smoothScrollDuration = value; - - case 'extensions.treestyletab.tabbar.scrollToNewTab.mode': - return this.scrollToNewTabMode = value; - - case 'extensions.treestyletab.tabbar.narrowScrollbar.size': - return this.updateNarrowScrollbarStyle(); - - case 'browser.tabs.animate': - return this.animationEnabled = value; - case 'extensions.treestyletab.animation.indent.duration': - return this.indentDuration = value; - case 'extensions.treestyletab.animation.collapse.duration': - return this.collapseDuration = value; - - case 'extensions.treestyletab.twisty.expandSensitiveArea': - return this.shouldExpandTwistyArea = value; - - case 'extensions.treestyletab.counter.role.horizontal': - return this.counterRoleHorizontal = value; - - case 'extensions.treestyletab.counter.role.vertical': - return this.counterRoleVertical = value; - - default: - return; - } - }, - - updateTabWidthPrefs : function TSTBase_updateTabWidthPrefs(aPrefName) - { - var expanded = utils.getTreePref('tabbar.width'); - var shrunken = utils.getTreePref('tabbar.shrunkenWidth'); - var originalExpanded = expanded; - var originalShrunken = shrunken; - if (aPrefName == 'extensions.treestyletab.tabbar.shrunkenWidth') { - if (expanded <= shrunken) - expanded = parseInt(shrunken / this.DEFAULT_SHRUNKEN_WIDTH_RATIO) - let w = this.browserWindow; - if (w && expanded > w.gBrowser.boxObject.width) { - expanded = w.gBrowser.boxObject.width * this.MAX_TABBAR_SIZE_RATIO; - if (expanded <= shrunken) - shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO) - } - } - else { - if (expanded <= shrunken) - shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO); - } - if (expanded != originalExpanded || - shrunken != originalShrunken) { - utils.setTreePref('tabbar.width', Math.max(0, expanded)); - utils.setTreePref('tabbar.shrunkenWidth', Math.max(0, shrunken)); - } - }, - - get shouldApplyNewPref() - { - return ( - !this.applyOnlyForActiveWindow || - this.window == this.topBrowserWindow - ) && - !this.inWindowDestoructionProcess; - }, - - applyOnlyForActiveWindow : false, - setPrefForActiveWindow : function TSTBase_setPrefForActiveWindow(aTask) { - TreeStyleTabBase.applyOnlyForActiveWindow = true; - try { - aTask.call(this); - } - finally { - TreeStyleTabBase.applyOnlyForActiveWindow = false; - } - } - -}); - -TreeStyleTabBase.init(); - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TreeStyleTabBase']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/Timer.jsm'); +Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); +Cu.import('resource://treestyletab-modules/constants.js'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); +XPCOMUtils.defineLazyGetter(this, 'extensions', function() { + Cu.import('resource://treestyletab-modules/lib/extensions.js', {}); + return window['piro.sakura.ne.jp'].extensions; +}); +XPCOMUtils.defineLazyGetter(this, 'animationManager', function() { + Cu.import('resource://treestyletab-modules/lib/animationManager.js', {}); + return window['piro.sakura.ne.jp'].animationManager; +}); +XPCOMUtils.defineLazyGetter(this, 'autoScroll', function() { + Cu.import('resource://treestyletab-modules/lib/autoScroll.js', {}); + return window['piro.sakura.ne.jp'].autoScroll; +}); +XPCOMUtils.defineLazyModuleGetter(this, 'UninstallationListener', + 'resource://treestyletab-modules/lib/UninstallationListener.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'Deferred', + 'resource://treestyletab-modules/lib/jsdeferred.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'confirmWithPopup', 'resource://treestyletab-modules/lib/confirmWithPopup.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + +XPCOMUtils.defineLazyServiceGetter(this, 'SessionStore', + '@mozilla.org/browser/sessionstore;1', 'nsISessionStore'); + +if (Services.appinfo.OS === 'WINNT') { + XPCOMUtils.defineLazyModuleGetter(this, 'AeroPeek', + 'resource:///modules/WindowsPreviewPerTab.jsm', 'AeroPeek'); +} +else { + this.AeroPeek = null; +} + +var TreeStyleTabBase = inherit(TreeStyleTabConstants, { + + tabsHash : null, + inWindowDestoructionProcess : false, + +/* base variables */ + baseIndentVertical : 12, + baseIndentHorizontal : 4, + shouldDetectClickOnIndentSpaces : true, + + smoothScrollEnabled : true, + smoothScrollDuration : 150, + + animationEnabled : true, + indentDuration : 200, + collapseDuration : 150, + + shouldExpandTwistyArea : true, + + scrollToNewTabMode : false, + + counterRoleHorizontal : -1, + counterRoleVertical : -1, + + get SessionStore() { + return SessionStore; + }, + + get extensions() { return extensions; }, + get animationManager() { return animationManager; }, + get autoScroll() { return autoScroll; }, + get Deferred() { return Deferred; }, + get AeroPeek() { return AeroPeek; }, // for Windows + + init : function TSTBase_init() + { + if (this._initialized) + return; + + this.isMac = Services.appinfo.OS == 'Darwin'; + + this.applyPlatformDefaultPrefs(); + utils.migratePrefs(); + + prefs.addPrefListener(this); + + this.initUninstallationListener(); + + this.onPrefChange('extensions.treestyletab.indent.vertical'); + this.onPrefChange('extensions.treestyletab.indent.horizontal'); + this.onPrefChange('extensions.treestyletab.clickOnIndentSpaces.enabled'); + this.onPrefChange('browser.tabs.insertRelatedAfterCurrent.override'); + this.onPrefChange('extensions.stm.tabBarMultiRows.override'); // Super Tab Mode + this.onPrefChange('extensions.treestyletab.tabbar.scroll.smooth'); + this.onPrefChange('extensions.treestyletab.tabbar.scroll.duration'); + this.onPrefChange('extensions.treestyletab.tabbar.scrollToNewTab.mode'); + this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar.size'); + this.onPrefChange('browser.tabs.animate'); + this.onPrefChange('extensions.treestyletab.animation.indent.duration'); + this.onPrefChange('extensions.treestyletab.animation.collapse.duration'); + this.onPrefChange('extensions.treestyletab.twisty.expandSensitiveArea'); + this.onPrefChange('extensions.treestyletab.counter.role.horizontal'); + this.onPrefChange('extensions.treestyletab.counter.role.vertical'); + + try { + this.overrideExtensions(); + } + catch(e) { + dump(e+'\n'); + } + }, + _initialized : false, + + applyPlatformDefaultPrefs : function TSTBase_applyPlatformDefaultPrefs() + { + var OS = Services.appinfo.OS; + var processed = {}; + var originalKeys = prefs.getDescendant('extensions.treestyletab.platform.'+OS); + for (let i = 0, maxi = originalKeys.length; i < maxi; i++) + { + let originalKey = originalKeys[i]; + let key = originalKey.replace('platform.'+OS+'.', ''); + prefs.setDefaultPref(key, prefs.getPref(originalKey)); + processed[key] = true; + } + originalKeys = prefs.getDescendant('extensions.treestyletab.platform.default'); + for (let i = 0, maxi = originalKeys.length; i < maxi; i++) + { + let originalKey = originalKeys[i]; + let key = originalKey.replace('platform.default.', ''); + if (!(key in processed)) + prefs.setDefaultPref(key, prefs.getPref(originalKey)); + } + }, + + initUninstallationListener : function TSTWindow_initUninstallationListener() + { + var restorePrefs = function() { + // Remove pref listener before restore backuped prefs. + prefs.removePrefListener(this); + + let restorePrefs = [ + 'browser.tabs.insertRelatedAfterCurrent', + 'extensions.stm.tabBarMultiRows' // Super Tab Mode + ]; + for (let i = 0, maxi = restorePrefs.length; i < maxi; i++) + { + let pref = restorePrefs[i]; + let backup = prefs.getPref(pref+'.backup'); + if (backup === null) + continue; + // restore user preference. + prefs.setPref(pref, backup); + // clear backup pref. + prefs.clearPref(pref+'.backup'); + } + }.bind(this); + new UninstallationListener({ + id : 'treestyletab@piro.sakura.ne.jp', + onuninstalled : restorePrefs, + ondisabled : restorePrefs + }); + }, + + overrideExtensions : function TSTBase_overrideExtensions() + { + // Scriptish + // https://addons.mozilla.org/firefox/addon/scriptish/ + if (utils.getTreePref('compatibility.Scriptish')) { + try { + let tabModule = Cu.import('resource://scriptish/utils/Scriptish_openInTab.js', {}); + let Scriptish_openInTab = tabModule.Scriptish_openInTab; + tabModule.Scriptish_openInTab = function(aURL, aLoadInBackground, aReuse, aChromeWin, ...aExtraArgs) { + try { + aChromeWin.TreeStyleTabService.readyToOpenChildTabNow(aChromeWin.gBrowser); + } + catch(e) { + Cu.reportError(e); + } + var allArgs = [aURL, aLoadInBackground, aReuse, aChromeWin].concat(aExtraArgs); + return Scriptish_openInTab.apply(this, allArgs); + }; + } + catch(e) { + } + } + }, + + updateNarrowScrollbarStyle : function TSTBase_updateNarrowScrollbarStyle() + { + const SSS = Cc['@mozilla.org/content/style-sheet-service;1'] + .getService(Ci.nsIStyleSheetService); + + if (this.lastAgentSheet && + SSS.sheetRegistered(this.lastAgentSheet, SSS.AGENT_SHEET)) + SSS.unregisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET); + + const style = 'data:text/css,'+encodeURIComponent( + ('@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");' + + + 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + + ' .tabbrowser-arrowscrollbox' + + ' > scrollbox' + + ' > scrollbar[orient="vertical"],' + + 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + + ' .tabbrowser-arrowscrollbox' + + ' > scrollbox' + + ' > scrollbar[orient="vertical"] * {' + + ' max-width: %SIZE%;' + + ' min-width: %SIZE%;' + + '}' + + + 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + + ' .tabbrowser-arrowscrollbox' + + ' > scrollbox' + + ' > scrollbar[orient="vertical"] {' + + ' font-size: %SIZE%;' + + '}' + + + 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + + ' .tabbrowser-arrowscrollbox' + + ' > scrollbox' + + ' > scrollbar[orient="vertical"] * {' + + ' padding-left: 0;' + + ' padding-right: 0;' + + ' margin-left: 0;' + + ' margin-right: 0;' + + '}' + + + '%FORCE_NARROW_SCROLLBAR%') + .replace(/%FORCE_NARROW_SCROLLBAR%/g, + utils.getTreePref('tabbar.narrowScrollbar.overrideSystemAppearance') ? + this.kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE : '' ) + .replace(/%MODE%/g, this.kMODE) + .replace(/%NARROW%/g, this.kNARROW_SCROLLBAR) + .replace(/%SIZE%/g, utils.getTreePref('tabbar.narrowScrollbar.size')) + ); + this.lastAgentSheet = this.makeURIFromSpec(style); + SSS.loadAndRegisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET); + }, + kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE : + 'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' + + ' .tabbrowser-arrowscrollbox' + + ' > scrollbox' + + ' > scrollbar[orient="vertical"] {' + + ' appearance: none;' + + ' -moz-appearance: none;' + + ' background: ThreeDFace;' + + ' border: 1px solid ThreeDShadow;' + + '}', + lastAgentSheet : null, + +/* references to the owner */ + + get browserWindow() + { + return this.topBrowserWindow; + }, + get topBrowserWindow() + { + return Services.wm.getMostRecentWindow('navigator:browser'); + }, + + get browserWindows() + { + var windows = []; + + var targets = Services.wm.getZOrderDOMWindowEnumerator('navigator:browser', true); + // By the bug 156333, we cannot find windows by their Z order on Linux. + // https://bugzilla.mozilla.org/show_bug.cgi?id=156333 + if (!targets.hasMoreElements()) + targets = Services.wm.getEnumerator('navigator:browser'); + + while (targets.hasMoreElements()) + { + let target = targets.getNext() + .QueryInterface(Ci.nsIDOMWindow); + windows.push(target); + } + + return windows; + }, + + get browser() + { + var w = this.browserWindow; + return !w ? null : + 'SplitBrowser' in w ? w.SplitBrowser.activeBrowser : + w.gBrowser ; + }, + + get window() + { + return this.browser.ownerDocument.defaultView; + }, + + get currentDragSession() + { + return Cc['@mozilla.org/widget/dragservice;1'] + .getService(Ci.nsIDragService) + .getCurrentSession(); + }, + +/* calculated behaviors */ + + dropLinksOnTabBehavior : function TSTBase_dropLinksOnTabBehavior() + { + var behavior = utils.getTreePref('dropLinksOnTab.behavior'); + if (behavior & this.kDROPLINK_FIXED) + return behavior; + + var checked = { value : false }; + var newChildTab = Services.prompt.confirmEx(this.browserWindow, + utils.treeBundle.getString('dropLinkOnTab.title'), + utils.treeBundle.getString('dropLinkOnTab.text'), + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), + utils.treeBundle.getString('dropLinkOnTab.openNewChildTab'), + utils.treeBundle.getString('dropLinkOnTab.loadInTheTab'), + null, + utils.treeBundle.getString('dropLinkOnTab.never'), + checked + ) == 0; + + behavior = newChildTab ? this.kDROPLINK_NEWTAB : this.kDROPLINK_LOAD ; + if (checked.value) + utils.setTreePref('dropLinksOnTab.behavior', behavior); + + return behavior + }, + kDROPLINK_ASK : 0, + kDROPLINK_FIXED : 1 + 2, + kDROPLINK_LOAD : 1, + kDROPLINK_NEWTAB : 2, + + openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior() + { + var behavior = utils.getTreePref('openGroupBookmark.behavior'); + if (behavior & this.kGROUP_BOOKMARK_FIXED) + return behavior; + + var dummyTabFlag = behavior & this.kGROUP_BOOKMARK_USE_DUMMY; + + var checked = { value : false }; + var button = Services.prompt.confirmEx(this.browserWindow, + utils.treeBundle.getString('openGroupBookmarkBehavior.title'), + utils.treeBundle.getString('openGroupBookmarkBehavior.text'), + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), + utils.treeBundle.getString('openGroupBookmarkBehavior.subTree'), + utils.treeBundle.getString('openGroupBookmarkBehavior.separate'), + null, + utils.treeBundle.getString('openGroupBookmarkBehavior.never'), + checked + ); + + if (button < 0) + button = 1; + var behaviors = [ + this.kGROUP_BOOKMARK_SUBTREE | dummyTabFlag, + this.kGROUP_BOOKMARK_SEPARATE + ]; + behavior = behaviors[button]; + + if (checked.value) { + utils.setTreePref('openGroupBookmark.behavior', behavior); + } + return behavior; + }, + kGROUP_BOOKMARK_ASK : 0, + kGROUP_BOOKMARK_FIXED : 1 + 2 + 4, + kGROUP_BOOKMARK_SUBTREE : 1, + kGROUP_BOOKMARK_SEPARATE : 2, + kGROUP_BOOKMARK_USE_DUMMY : 256, + kGROUP_BOOKMARK_USE_DUMMY_FORCE : 1024, + kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE : 512, + kGROUP_BOOKMARK_EXPAND_ALL_TREE : 2048, + + bookmarkDroppedTabsBehavior : function TSTBase_bookmarkDroppedTabsBehavior() + { + var behavior = utils.getTreePref('bookmarkDroppedTabs.behavior'); + if (behavior & this.kBOOKMARK_DROPPED_TABS_FIXED) + return behavior; + + var checked = { value : false }; + var button = Services.prompt.confirmEx(this.browserWindow, + utils.treeBundle.getString('bookmarkDroppedTabs.title'), + utils.treeBundle.getString('bookmarkDroppedTabs.text'), + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1), + utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkAll'), + utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkOnlyParent'), + null, + utils.treeBundle.getString('bookmarkDroppedTabs.never'), + checked + ); + + if (button < 0) + button = 1; + var behaviors = [ + this.kBOOKMARK_DROPPED_TABS_ALL, + this.kBOOKMARK_DROPPED_TABS_ONLY_PARENT + ]; + behavior = behaviors[button]; + + if (checked.value) + utils.setTreePref('bookmarkDroppedTabs.behavior', behavior); + + return behavior; + }, + kBOOKMARK_DROPPED_TABS_ASK : 0, + kBOOKMARK_DROPPED_TABS_FIXED : 1 | 2, + kBOOKMARK_DROPPED_TABS_ALL : 1, + kBOOKMARK_DROPPED_TABS_ONLY_PARENT : 2, + + askUndoCloseTabSetBehavior : function TSTBase_askUndoCloseTabSetBehavior(aRestoredTab, aCount) + { + var behavior = this.undoCloseTabSetBehavior; + if (behavior & this.kUNDO_CLOSE_SET) + behavior ^= this.kUNDO_CLOSE_SET; + + var self = this; + return confirmWithPopup({ + browser : aRestoredTab.linkedBrowser, + label : utils.treeBundle.getFormattedString('undoCloseTabSetBehavior.label', [aCount]), + value : 'treestyletab-undo-close-tree', + image : 'chrome://treestyletab/content/res/icon.png', + buttons : [ + utils.treeBundle.getString('undoCloseTabSetBehavior.restoreOnce'), + utils.treeBundle.getString('undoCloseTabSetBehavior.restoreForever'), + utils.treeBundle.getString('undoCloseTabSetBehavior.ignoreForever') + ], + persistence : -1 // don't hide even if the tab is restored after the panel is shown. + }) + .next(function(aButtonIndex) { + if (aButtonIndex < 2) { + behavior |= self.kUNDO_CLOSE_SET; + } + if (aButtonIndex > 0) { + behavior ^= self.kUNDO_ASK; + utils.setTreePref('undoCloseTabSet.behavior', behavior); + } + return behavior; + }); + }, + get undoCloseTabSetBehavior() + { + return utils.getTreePref('undoCloseTabSet.behavior'); + }, + kUNDO_ASK : 1, + kUNDO_CLOSE_SET : 2, + kUNDO_CLOSE_FULL_SET : 256, + +/* utilities */ + + doAndWaitDOMEvent : function TSTBase_doAndWaitDOMEvent(...aArgs) + { + var type, target, delay, task; + for (let i = 0, maxi = aArgs.length; i < maxi; i++) + { + let arg = aArgs[i]; + switch(typeof arg) + { + case 'string': + type = arg; + continue; + + case 'number': + delay = arg; + continue; + + case 'function': + task = arg; + continue; + + default: + target = arg; + continue; + } + } + + if (!target || !type) { + if (task) + task(); + return; + } + + var done = false; + var listener = function(aEvent) { + setTimeout(function() { + done = true; + }, delay || 0); + target.removeEventListener(type, listener, false); + }; + + if (task) + Deferred.next(function() { + try { + task(); + } + catch(e) { + dump(e+'\n'); + target.removeEventListener(type, listener, false); + done = true; + } + }).error(this.defaultDeferredErrorHandler); + + target.addEventListener(type, listener, false); + + var thread = Components + .classes['@mozilla.org/thread-manager;1'] + .getService() + .mainThread; + while (!done) + { + //dump('WAIT '+type+' '+Date.now()+'\n'); + thread.processNextEvent(true); + } + }, + + findOffsetParent : function TSTBase_findOffsetParent(aNode) + { + var parent = aNode.parentNode; + var doc = aNode.ownerDocument || aNode; + var view = doc.defaultView; + while (parent && parent instanceof Ci.nsIDOMElement) + { + let position = view.getComputedStyle(parent, null).getPropertyValue('position'); + if (position != 'static') + return parent; + parent = parent.parentNode; + } + return doc.documentElement; + }, + + assertBeforeDestruction : function TSTBase_assertBeforeDestruction(aNotDestructed) + { + if (aNotDestructed) + return; + + var message = 'ERROR: accessed after destruction!'; + var error = new Error(message); + dump(message+'\n'+error.stack+'\n'); + throw error; + }, + + defaultDeferredErrorHandler : function TSTBase_defaultDeferredErrorHandler(aError) + { + if (aError.stack) + Cu.reportError(aError.message+'\n'+aError.stack); + else + Cu.reportError(aError); + }, + +// event + + isNewTabAction : function TSTBase_isNewTabAction(aEvent) + { + return aEvent.button == 1 || (aEvent.button == 0 && this.isAccelKeyPressed(aEvent)); + }, + + isAccelKeyPressed : function TSTBase_isAccelKeyPressed(aEvent) + { + if ( // this is releasing of the accel key! + (aEvent.type == 'keyup') && + (aEvent.keyCode == (this.isMac ? Ci.nsIDOMKeyEvent.DOM_VK_META : Ci.nsIDOMKeyEvent.DOM_VK_CONTROL )) + ) { + return false; + } + return this.isMac ? + (aEvent.metaKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_META)) : + (aEvent.ctrlKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL)) ; + }, + + isCopyAction : function TSTBase_isCopyAction(aEvent) + { + return this.isAccelKeyPressed(aEvent) || + (aEvent.dataTransfer && aEvent.dataTransfer.dropEffect == 'copy'); + }, + + isEventFiredOnClosebox : function TSTBase_isEventFiredOnClosebox(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tab-close-button ")]', + aEvent.originalTarget || aEvent.target, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue; + }, + + isEventFiredOnClickable : function TSTBase_isEventFiredOnClickable(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::*[contains(" button toolbarbutton scrollbar nativescrollbar popup menupopup panel tooltip splitter textbox ", concat(" ", local-name(), " "))]', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue; + }, + + isEventFiredOnScrollbar : function TSTBase_isEventFiredOnScrollbar(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::*[local-name()="scrollbar" or local-name()="nativescrollbar"]', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue; + }, + + isEventFiredOnTwisty : function TSTBase_isEventFiredOnTwisty(aEvent) + { + var tab = this.getTabFromEvent(aEvent); + if (!tab || + !this.hasChildTabs(tab) || + !this.canCollapseSubtree(tab)) + return false; + + var twisty = tab.ownerDocument.getAnonymousElementByAttribute(tab, 'class', this.kTWISTY); + if (!twisty) + return false; + + var box = twisty.boxObject; + var left = box.screenX; + var top = box.screenY; + var right = left + box.width; + var bottom = top + box.height; + var favicon = this.getFaviconRect(tab); + if (!box.width || !box.height) { + left = favicon.left; + top = favicon.top; + right = favicon.right; + bottom = favicon.bottom; + } + else if ( + this.shouldExpandTwistyArea && + !this._expandTwistyAreaBlockers.length + ) { + left = Math.min(left, favicon.left); + top = Math.min(top, favicon.top); + right = Math.max(right, favicon.right); + bottom = Math.max(bottom, favicon.bottom); + } + + var x = aEvent.screenX; + var y = aEvent.screenY; + return (x >= left && x <= right && y >= top && y <= bottom); + }, + getFaviconRect : function TSTBase_getFaviconRect(aTab) + { + var icon = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon-image'); + var iconBox = icon.boxObject; + var iconRect = { + left : iconBox.screenX, + top : iconBox.screenY, + right : iconBox.screenX + iconBox.width, + bottom : iconBox.screenY + iconBox.height + }; + + var throbber = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber'); + var throbberBox = throbber.boxObject; + var throbberRect = { + left : throbberBox.screenX, + top : throbberBox.screenY, + right : throbberBox.screenX + throbberBox.width, + bottom : throbberBox.screenY + throbberBox.height + }; + + if (!iconBox.width && !iconBox.height) + return throbberRect; + + if (!throbberBox.width && !throbberBox.height) + return iconRect; + + return { + left : Math.min(throbberRect.left, iconRect.left), + right : Math.max(throbberRect.right, iconRect.right), + top : Math.min(throbberRect.top, iconRect.top), + bottom : Math.max(throbberRect.bottom, iconRect.bottom) + }; + }, + + // called with target(nsIDOMEventTarget), document(nsIDOMDocument), type(string) and data(object) + fireCustomEvent : function TSTBase_fireCustomEvent(...aArgs) + { + var target, document, type, data, canBubble, cancelable; + for (let i = 0, maxi = aArgs.length; i < maxi; i++) + { + let arg = aArgs[i]; + if (typeof arg == 'boolean') { + if (canBubble === void(0)) + canBubble = arg; + else + cancelable = arg; + } + else if (typeof arg == 'string') + type = arg; + else if (arg instanceof Ci.nsIDOMDocument) + document = arg; + else if (arg instanceof Ci.nsIDOMEventTarget) + target = arg; + else + data = arg; + } + if (!target) + target = document; + if (!document) + document = target.ownerDocument || target; + + var event = new this.window.CustomEvent(type, { + bubbles : canBubble, + cancelable : cancelable, + detail : data + }); + return target.dispatchEvent(event); + }, + + registerExpandTwistyAreaBlocker : function TSTBase_registerExpandTwistyAreaBlocker(aBlocker) /* PUBLIC API */ + { + if (this._expandTwistyAreaBlockers.indexOf(aBlocker) < 0) + this._expandTwistyAreaBlockers.push(aBlocker); + }, + _expandTwistyAreaBlockers : [], + + registerExpandTwistyAreaAllowance : function TSTBase_registerExpandTwistyAreaAllowance(aAllowance) /* PUBLIC API, obsolete, for backward compatibility */ + { + this.registerExpandTwistyAreaBlocker(aAllowance.toSource()); + }, + +// string + + makeNewId : function TSTBase_makeNewId() + { + return 'tab-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>'; + }, + + makeNewClosedSetId : function TSTBase_makeNewId() + { + return 'tabs-closed-set-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>'; + }, + + makeURIFromSpec : function TSTBase_makeURIFromSpec(aURI) + { + var newURI; + aURI = aURI || ''; + if (aURI && String(aURI).indexOf('file:') == 0) { + var fileHandler = Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler); + var tempLocalFile = fileHandler.getFileFromURLSpec(aURI); + newURI = Services.io.newFileURI(tempLocalFile); + } + else { + if (!/^\w+\:/.test(aURI)) + aURI = 'http://'+aURI; + newURI = Services.io.newURI(aURI, null, null); + } + return newURI; + }, + + getGroupTabURI : function TSTBase_getGroupTabURI(aOptions) + { + aOptions = aOptions || {}; + var parameters = []; + parameters.push('title=' + encodeURIComponent(aOptions.title || '')); + parameters.push('temporary=' + !!aOptions.temporary); + return 'about:treestyletab-group?' + parameters.join('&'); + }, + +// xpath + + NSResolver : { + lookupNamespaceURI : function(aPrefix) + { + switch (aPrefix) + { + case 'xul': + return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; + case 'html': + case 'xhtml': + return 'http://www.w3.org/1999/xhtml'; + case 'xlink': + return 'http://www.w3.org/1999/xlink'; + default: + return ''; + } + } + }, + + evaluateXPath : function TSTBase_evaluateXPath(aExpression, aContext, aType) + { + if (!aType) + aType = Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE; + try { + var XPathResult = (aContext.ownerDocument || aContext).evaluate( + aExpression, + (aContext || document), + this.NSResolver, + aType, + null + ); + } + catch(e) { + return { + singleNodeValue : null, + snapshotLength : 0, + snapshotItem : function() { + return null + } + }; + } + return XPathResult; + }, + + getArrayFromXPathResult : function TSTBase_getArrayFromXPathResult(aXPathResult) + { + var max = aXPathResult.snapshotLength; + var array = new Array(max); + if (!max) + return array; + + for (var i = 0; i < max; i++) + { + array[i] = aXPathResult.snapshotItem(i); + } + + return array; + }, + +/* Session Store API */ + + getTabValue : function TSTBase_getTabValue(aTab, aKey) + { + var value = ''; + try { + value = SessionStore.getTabValue(aTab, aKey); + } + catch(e) { + } + + if (this.useTMPSessionAPI) { + let TMPValue = aTab.getAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey); + if (TMPValue) + value = TMPValue; + } + + return value; + }, + + setTabValue : function TSTBase_setTabValue(aTab, aKey, aValue) + { + if (!aValue) + return this.deleteTabValue(aTab, aKey); + + aTab.setAttribute(aKey, aValue); + try { + this.checkCachedSessionDataExpiration(aTab); + SessionStore.setTabValue(aTab, aKey, String(aValue)); + } + catch(e) { + } + + if (this.useTMPSessionAPI) + aTab.setAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey, aValue); + + return aValue; + }, + + deleteTabValue : function TSTBase_deleteTabValue(aTab, aKey) + { + aTab.removeAttribute(aKey); + try { + this.checkCachedSessionDataExpiration(aTab); + SessionStore.setTabValue(aTab, aKey, ''); + SessionStore.deleteTabValue(aTab, aKey); + } + catch(e) { + } + + if (this.useTMPSessionAPI) + aTab.removeAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey); + }, + + // workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm + // This is obsolete for lately Firefox and no need to be updated. See: https://github.com/piroor/treestyletab/issues/508#issuecomment-17526429 + checkCachedSessionDataExpiration : function TSTBase_checkCachedSessionDataExpiration(aTab) + { + var data = aTab.linkedBrowser.__SS_data; + if (data && + data._tabStillLoading && + aTab.getAttribute('busy') != 'true' && + !utils.isTabNeedToBeRestored(aTab)) + data._tabStillLoading = false; + }, + + markAsClosedSet : function TSTBase_markAsClosedSet(aTabs) /* PUBLIC API */ + { + if (!aTabs || aTabs.length <= 1) + return; + var id = this.makeNewClosedSetId() + '::' + aTabs.length; + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + this.setTabValue(aTabs[i], this.kCLOSED_SET_ID, id); + } + }, + + unmarkAsClosedSet : function TSTBase_unmarkAsClosedSet(aTabs) /* PUBLIC API */ + { + if (!aTabs || !aTabs.length) + return; + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + this.deleteTabValue(aTabs[i], this.kCLOSED_SET_ID); + } + }, + + useTMPSessionAPI : false, + + kTMP_SESSION_DATA_PREFIX : 'tmp-session-data-', + +// tab + + getTabStrip : function TSTBase_getTabStrip(aTabBrowser) + { + if (!(aTabBrowser instanceof Ci.nsIDOMElement)) + return null; + + var strip = aTabBrowser.mStrip; + return (strip && strip instanceof Ci.nsIDOMElement) ? + strip : + this.evaluateXPath( + 'ancestor::xul:toolbar[1]', + aTabBrowser.tabContainer, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue || aTabBrowser.tabContainer.parentNode; + }, + get tabStrip() + { + return this.getTabStrip(this.browser); + }, + + getTabContainerBox : function TSTBase_getTabContainerBox(aTabBrowser) + { + if (!(aTabBrowser instanceof Ci.nsIDOMElement)) + return null; + + var strip = this.getTabStrip(aTabBrowser); + return strip.treeStyleTabToolbarInnerBox || aTabBrowser.tabContainer; + }, + get tabContainerBox() + { + return this.getTabContainerBox(this.browser); + }, + + setTabbrowserAttribute : function TSTBase_setTabbrowserAttribute(aName, aValue, aTabBrowser) + { + aTabBrowser = aTabBrowser || this.mTabBrowser || this.browser; + if (aValue) { + aTabBrowser.setAttribute(aName, aValue); + aTabBrowser.mTabContainer.setAttribute(aName, aValue); + aTabBrowser.treeStyleTab.setTabStripAttribute(aName, aValue); + } + else { + aTabBrowser.removeAttribute(aName); + aTabBrowser.mTabContainer.removeAttribute(aName); + aTabBrowser.treeStyleTab.removeTabStripAttribute(aName); + } + }, + + removeTabbrowserAttribute : function TSTBase_removeTabbrowserAttribute(aName, aTabBrowser) + { + this.setTabbrowserAttribute(aName, null, aTabBrowser); + }, + + setTabStripAttribute : function TSTBase_setTabStripAttribute(aAttr, aValue) + { + var strip = this.tabStrip; + if (!strip) + return; + var isFeatureAttribute = aAttr.indexOf('treestyletab-') == 0; + if (aValue) { + if (this._tabStripPlaceHolder) + this._tabStripPlaceHolder.setAttribute(aAttr, aValue); + if (!this._tabStripPlaceHolder || aAttr != 'ordinal') + strip.setAttribute(aAttr, aValue); + if (strip.treeStyleTabToolbarInnerBox) + strip.treeStyleTabToolbarInnerBox.setAttribute(aAttr, aValue); + if (isFeatureAttribute) { + // Only attributes for TST's feature are applied to the root element. + // (width, height, and other general attributes have to be ignored!) + strip.ownerDocument.defaultView.setTimeout(function(aSelf) { + strip.ownerDocument.documentElement.setAttribute(aAttr, aValue); + }, 10, this); + } + } + else { + if (this._tabStripPlaceHolder) + this._tabStripPlaceHolder.removeAttribute(aAttr); + if (!this._tabStripPlaceHolder || aAttr != 'ordinal') + strip.removeAttribute(aAttr); + if (strip.treeStyleTabToolbarInnerBox) + strip.treeStyleTabToolbarInnerBox.removeAttribute(aAttr); + if (isFeatureAttribute) { + strip.ownerDocument.defaultView.setTimeout(function(aSelf) { + strip.ownerDocument.documentElement.removeAttribute(aAttr); + }, 10, this); + } + } + }, + + removeTabStripAttribute : function TSTBase_removeTabStripAttribute(aAttr) + { + this.setTabStripAttribute(aAttr, null); + }, + + getTabFromChild : function TSTBase_getTabFromChild(aTab) + { + return this.evaluateXPath( + 'ancestor-or-self::xul:tab', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getTabFromEvent : function TSTBase_getTabFromEvent(aEvent) + { + return this.getTabFromChild(aEvent.originalTarget || aEvent.target); + }, + + getNewTabButtonFromEvent : function TSTBase_getNewTabButtonFromEvent(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::*[' + +'@id="new-tab-button" or ' + +'contains(concat(" ", normalize-space(@class), " "), " tabs-newtab-button ")' + +'][1]', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getSplitterFromEvent : function TSTBase_getSplitterFromEvent(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::xul:splitter[contains(concat(" ", normalize-space(@class), " "), " '+this.kSPLITTER+' ")]', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + isEventFiredOnGrippy : function TSTBase_isEventFiredOnGrippy(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::xul:grippy', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue; + }, + + getTabFromBrowser : function TSTBase_getTabFromBrowser(aBrowser, aTabBrowser) + { + var b = aTabBrowser || this.browser; + var tabs = this.getAllTabs(b); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + if (tab.linkedBrowser == aBrowser) + return tab; + } + return null; + }, + + getTabFromFrame : function TSTBase_getTabFromFrame(aFrame, aTabBrowser) + { + var b = aTabBrowser || this.browser; + var top = aFrame.top; + var tabs = this.getAllTabs(b); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + if (tab.linkedBrowser.contentWindow == top) + return tab; + } + return null; + }, + + getTabbarFromChild : function TSTBase_getTabbarFromChild(aNode) + { + return this.evaluateXPath( + 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' + + 'ancestor-or-self::xul:tabs[@tabbrowser] | ' + + 'ancestor-or-self::xul:toolbar/child::xul:tabs[@tabbrowser]', + aNode, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + getAncestorTabbarFromChild : function TSTBase_getAncestorTabbarFromChild(aNode) + { + return this.evaluateXPath( + 'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' + + 'ancestor-or-self::xul:tabs[@tabbrowser]', + aNode, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getTabbarFromEvent : function TSTBase_getTabbarFromEvent(aEvent) + { + return this.getTabbarFromChild(aEvent.originalTarget || aEvent.target); + }, + getAncestorTabbarFromEvent : function TSTBase_getAncestorTabbarFromEvent(aEvent) + { + return this.getAncestorTabbarFromChild(aEvent.originalTarget || aEvent.target); + }, + + cleanUpTabsArray : function TSTBase_cleanUpTabsArray(aTabs) + { + var newTabs = []; + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (!tab || !tab.parentNode) + continue; // ignore removed tabs + if (newTabs.indexOf(tab) < 0) + newTabs.push(tab); + } + newTabs.sort(this.sortTabsByOrder); + return newTabs; + }, + + sortTabsByOrder : function TSTBase_sortTabsByOrder(aA, aB) + { + return aA._tPos - aB._tPos; + }, + + gatherSubtreeMemberTabs : function TSTBase_gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren) + { + var tabs = aTabOrTabs; + if (!(tabs instanceof Array)) { + tabs = [aTabOrTabs]; + } + + var b = this.getTabBrowserFromChild(tabs[0]); + var descendant = []; + for (var i = 0, maxi = tabs.length; i < maxi; i++) + { + descendant = descendant.concat(b.treeStyleTab.getDescendantTabs(tabs[i])); + } + + return this.cleanUpTabsArray(aOnlyChildren ? descendant : tabs.concat(descendant)); + }, + + splitTabsToSubtrees : function TSTBase_splitTabsToSubtrees(aTabs) /* PUBLIC API */ + { + var groups = []; + + var group = []; + aTabs = this.cleanUpTabsArray(aTabs); + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + let parent = this.getParentTab(tab); + if (!parent || group.indexOf(parent) < 0) { + if (group.length) + groups.push(group); + group = [tab]; + } + else { + group.push(tab); + } + } + if (group.length) + groups.push(group); + return groups; + }, + +// tabbrowser + + getTabBrowserFromChild : function TSTBase_getTabBrowserFromChild(aTabBrowserChild) + { + if (!aTabBrowserChild) + return null; + + if (aTabBrowserChild.__treestyletab__linkedTabBrowser) // tab + return aTabBrowserChild.__treestyletab__linkedTabBrowser; + + if (aTabBrowserChild.localName == 'tabbrowser') // itself + return aTabBrowserChild; + + if (aTabBrowserChild.tabbrowser) // tabs + return aTabBrowserChild.tabbrowser; + + if (aTabBrowserChild.localName == 'toolbar') // tabs toolbar + return aTabBrowserChild.getElementsByTagName('tabs')[0].tabbrowser; + + // tab context menu + var popup = this.evaluateXPath( + 'ancestor-or-self::xul:menupopup[@id="tabContextMenu"]', + aTabBrowserChild, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + if (popup && 'TabContextMenu' in aTabBrowserChild.ownerDocument.defaultView) + return this.getTabBrowserFromChild(aTabBrowserChild.ownerDocument.defaultView.TabContextMenu.contextTab); + + var b = this.evaluateXPath( + 'ancestor::xul:tabbrowser | '+ + 'ancestor::xul:tabs[@tabbrowser] |'+ + 'ancestor::xul:toolbar/descendant::xul:tabs', + aTabBrowserChild, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + return (b && b.tabbrowser) || b; + }, + + getTabBrowserFromFrame : function TSTBase_getTabBrowserFromFrame(aFrame) + { + var w = this.browserWindow; + return !w ? null : + ('SplitBrowser' in w) ? this.getTabBrowserFromChild(w.SplitBrowser.getSubBrowserAndBrowserFromFrame(aFrame.top).browser) : + this.browser ; + }, + + getFrameFromTabBrowserElements : function TSTBase_getFrameFromTabBrowserElements(aFrameOrTabBrowser) + { + var frame = aFrameOrTabBrowser; + if (frame == '[object XULElement]') { + if (frame.localName == 'tab') { + frame = frame.linkedBrowser.contentWindow; + } + else if (frame.localName == 'browser') { + frame = frame.contentWindow; + } + else { + frame = this.getTabBrowserFromChild(frame); + if (!frame) + return null; + frame = frame.contentWindow; + } + } + if (!frame) + frame = this.browser.contentWindow; + + return frame; + }, + +/* get tab(s) */ + + getTabById : function TSTBase_getTabById(aId, aTabBrowserChildren) + { + if (!aId) + return null; + + if (aTabBrowserChildren && !(aTabBrowserChildren instanceof Ci.nsIDOMNode)) + aTabBrowserChildren = null; + + var b = this.getTabBrowserFromChild(aTabBrowserChildren) || this.browser; + + if (this.tabsHash) // XPath-less implementation + return this.tabsHash[aId] || null; + + return b.mTabContainer.querySelector('tab['+this.kID+'="'+aId+'"]'); + }, + + isTabDuplicated : function TSTBase_isTabDuplicated(aTab) + { + if (!aTab) + return false; + var id = this.getTabValue(aTab, this.kID); + var b = this.getTabBrowserFromChild(aTab) || this.browser; + var tabs = b.mTabContainer.querySelectorAll('tab['+this.kID+'="'+id+'"], tab['+this.kID_RESTORING+'="'+id+'"]'); + return tabs.length > 1; + }, + + /** + * Returns all tabs in the current group as an array. + * It includes tabs hidden by Tab Panorama. + */ + getAllTabs : function TSTBase_getTabs(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + this.assertBeforeDestruction(b && b.mTabContainer); + return Array.slice(b.mTabContainer.querySelectorAll('tab')); + }, + + /** + * Returns all tabs in the current group as an array. + * It excludes tabs hidden by Tab Panorama. + */ + getTabs : function TSTBase_getTabs(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + this.assertBeforeDestruction(b && b.mTabContainer); + return Array.slice(b.mTabContainer.querySelectorAll('tab:not([hidden="true"])')); + }, + + getAllTabsArray : function TSTBase_getAllTabsArray(aTabBrowserChild) /* for backward compatibility */ + { + return this.getAllTabs(aTabBrowserChild); + }, + + getTabsArray : function TSTBase_getTabsArray(aTabBrowserChild) /* for backward compatibility */ + { + return this.getTabs(aTabBrowserChild); + }, + + /** + * Returns the first tab in the current group. + */ + getFirstTab : function TSTBase_getFirstTab(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + this.assertBeforeDestruction(b && b.mTabContainer); + var tabs = b.visibleTabs; + return tabs ? tabs[0] : b.mTabContainer.firstChild; + }, + + /** + * Returns the first visible, not collapsed, and not pinned tab. + */ + getFirstNormalTab : function TSTBase_getFirstNormalTab(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + this.assertBeforeDestruction(b && b.mTabContainer); + return b.mTabContainer.querySelector('tab:not([pinned="true"]):not([hidden="true"])'); + }, + + /** + * Returns the last tab in the current group. + */ + getLastTab : function TSTBase_getLastTab(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + this.assertBeforeDestruction(b && b.mTabContainer); + var tabs = b.visibleTabs; + return tabs ? tabs[tabs.length-1] : b.mTabContainer.lastChild ; + }, + + /** + * Returns the next tab in the current group. + */ + getNextTab : function TSTBase_getNextTab(aTab) + { + if (!aTab) + return null; + var b = this.getTabBrowserFromChild(aTab); + this.assertBeforeDestruction(b && b.mTabContainer); + var tabs = b.visibleTabs; + if (tabs) { + let index = tabs.indexOf(aTab); + if (index > -1) + return tabs.length > index ? tabs[index+1] : null + } + var tab = aTab.nextSibling; + return (tab && tab.localName == 'tab') ? tab : null ; + }, + + /** + * Returns the previous tab in the current group. + */ + getPreviousTab : function TSTBase_getPreviousTab(aTab) + { + if (!aTab) + return null; + var b = this.getTabBrowserFromChild(aTab); + this.assertBeforeDestruction(b && b.mTabContainer); + var tabs = b.visibleTabs; + if (tabs) { + let index = tabs.indexOf(aTab); + if (index > -1) + return 0 < index ? tabs[index-1] : null + } + var tab = aTab.previousSibling; + return (tab && tab.localName == 'tab') ? tab : null ; + }, + + /** + * Returns the index of the specified tab, in the current group. + */ + getTabIndex : function TSTBase_getTabIndex(aTab) + { + if (!aTab) + return -1; + var b = this.getTabBrowserFromChild(aTab); + return this.getTabs(b).indexOf(aTab); + }, + + /** + * Returns the next not collapsed tab in the current group. + */ + getNextVisibleTab : function TSTBase_getNextVisibleTab(aTab) + { + if (!aTab) + return null; + + var b = this.getTabBrowserFromChild(aTab); + if (!this.canCollapseSubtree(b)) + return this.getNextTab(aTab); + + var tabs = this.getVisibleTabs(b); + if (tabs.indexOf(aTab) < 0) + tabs.push(aTab); + tabs.sort(this.sortTabsByOrder); + + var index = tabs.indexOf(aTab); + return (index < tabs.length-1) ? tabs[index+1] : null ; + }, + + /** + * Returns the previous not collapsed tab in the current group. + */ + getPreviousVisibleTab : function TSTBase_getPreviousVisibleTab(aTab) + { + if (!aTab) + return null; + + var b = this.getTabBrowserFromChild(aTab); + if (!this.canCollapseSubtree(b)) + return this.getPreviousTab(aTab); + + var tabs = this.getVisibleTabs(b); + if (tabs.indexOf(aTab) < 0) + tabs.push(aTab); + tabs.sort(this.sortTabsByOrder); + + var index = tabs.indexOf(aTab); + return (index > 0) ? tabs[index-1] : null ; + }, + + /** + * Returns the last not collapsed tab in the current group. + */ + getLastVisibleTab : function TSTBase_getLastVisibleTab(aTabBrowserChild) + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + if (!b) + return null; + var tabs = this.getVisibleTabs(b); + return tabs.length ? tabs[tabs.length-1] : null ; + }, + + /** + * Returns a XPathResult of not collapsed tabs in the current group. + */ + getVisibleTabs : function TSTBase_getVisibleTabs(aTabBrowserChild) /* OBSOLETE */ + { + var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser); + if (!this.canCollapseSubtree(b)) + return this.getTabs(b); + return Array.slice(b.mTabContainer.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"]):not([hidden="true"])')); + }, + + getVisibleTabsArray : function TSTBase_getVisibleTabsArray(aTabBrowserChild) /* for backward compatibility */ + { + return this.getVisibleTabs(aTabBrowserChild); + }, + + /** + * Returns the index of the specified tab, in the array of not collapsed + * tabs in the current group. + */ + getVisibleIndex : function TSTBase_getVisibleIndex(aTab) + { + if (!aTab) + return -1; + var b = this.getTabBrowserFromChild(aTab); + return this.getVisibleTabs(b).indexOf(aTab); + }, + + /** + * Returns tabs which are newly opened in the given operation. + */ + getNewTabsWithOperation : function TSTBase_getNewTabsWithOperation(aOperation, aTabBrowser) + { + var previousTabs = this.getTabsInfo(aTabBrowser); + aOperation.call(this); + return this.getNewTabsFromPreviousTabsInfo(aTabBrowser, previousTabs); + }, + + /** + * Returns tabs which are newly opened. This requires the "previous state". + */ + getNewTabsFromPreviousTabsInfo : function TSTBase_getNewTabsFromPreviousTabsInfo(aTabBrowser, aTabsInfo) + { + var tabs = this.getTabs(aTabBrowser); + var currentTabsInfo = this.getTabsInfo(aTabBrowser); + return tabs.filter(function(aTab, aIndex) { + return aTabsInfo.indexOf(currentTabsInfo[aIndex]) < 0; + }); + }, + getTabsInfo : function TSTBase_getTabsInfo(aTabBrowser) + { + var tabs = this.getTabs(aTabBrowser); + return tabs.map(function(aTab) { + return aTab.getAttribute(this.kID)+'\n'+ + aTab.getAttribute('busy')+'\n'+ + aTab.linkedBrowser.currentURI.spec; + }, this); + }, + +/* notify "ready to open child tab(s)" */ + + readyToOpenChildTab : function TSTBase_readyToOpenChildTab(aFrameOrTabBrowser, aMultiple, aInsertBefore) /* PUBLIC API */ + { + if (!utils.getTreePref('autoAttach')) + return false; + + var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); + if (!frame) + return false; + + var ownerBrowser = this.getTabBrowserFromFrame(frame); + + var parentTab = this.getTabFromFrame(frame, ownerBrowser); + if (!parentTab || parentTab.getAttribute('pinned') == 'true') + return false; + + ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab); + var parentId = parentTab.getAttribute(this.kID); + + var refId = null; + if (aInsertBefore) { + ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab); + refId = aInsertBefore.getAttribute(this.kID); + } + + dump('Tree Style Tab: new child tab is requested.\n'+new Error().stack+'\n'); + + ownerBrowser.treeStyleTab.readiedToAttachNewTab = true; + ownerBrowser.treeStyleTab.readiedToAttachMultiple = aMultiple || false ; + ownerBrowser.treeStyleTab.multipleCount = aMultiple ? 0 : -1 ; + ownerBrowser.treeStyleTab.parentTab = parentId; + ownerBrowser.treeStyleTab.insertBefore = refId; + + return true; + }, + /** + * Extended version. If you don't know whether a new tab will be actually + * opened or not (by the command called after TST's API), then use this. + * This version automatically cancels the "ready" state with delay. + */ + readyToOpenChildTabNow : function TSTBase_readyToOpenChildTabNow(...aArgs) /* PUBLIC API */ + { + if (this.readyToOpenChildTab.apply(this, aArgs)) { + let self = this; + this.Deferred.next(function() { + self.stopToOpenChildTab(aArgs[0]); + }).error(this.defaultDeferredErrorHandler); + return true; + } + return false; + }, + + readyToOpenNextSiblingTab : function TSTBase_readyToOpenNextSiblingTab(aFrameOrTabBrowser) /* PUBLIC API */ + { + var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); + if (!frame) + return false; + + var ownerBrowser = this.getTabBrowserFromFrame(frame); + + var tab = this.getTabFromFrame(frame, ownerBrowser); + if (!tab || tab.getAttribute('pinned') == 'true') + return false; + + var parentTab = this.getParentTab(tab); + var nextTab = this.getNextSiblingTab(tab); + if (parentTab) { + /** + * If the base tab has a parent, open the new tab as a child of + * the parent tab. + */ + return this.readyToOpenChildTab(parentTab, false, nextTab); + } + else { + /** + * Otherwise, open the tab as a new root tab. If there is no + * tab next to the base tab (in other words, if the tab is the + * last tab), then do nothing. + */ + if (!nextTab) + return; + ownerBrowser.treeStyleTab.readiedToAttachNewTab = true; + ownerBrowser.treeStyleTab.parentTab = null; + ownerBrowser.treeStyleTab.insertBefore = nextTab.getAttribute(this.kID); + return true; + } + }, + /** + * Extended version. If you don't know whether a new tab will be actually + * opened or not (by the command called after TST's API), then use this. + * This version automatically cancels the "ready" state with delay. + */ + readyToOpenNextSiblingTabNow : function TSTBase_readyToOpenNextSiblingTabNow(...aArgs) /* PUBLIC API */ + { + if (this.readyToOpenNextSiblingTab.apply(this, aArgs)) { + let self = this; + this.Deferred.next(function() { + self.stopToOpenChildTab(aArgs[0]); + }).error(this.defaultDeferredErrorHandler); + return true; + } + return false; + }, + + readyToOpenNewTabGroup : function TSTBase_readyToOpenNewTabGroup(aFrameOrTabBrowser, aTreeStructure, aExpandAllTree) /* PUBLIC API */ + { + if (!utils.getTreePref('autoAttach')) + return false; + + var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); + if (!frame) + return false; + + this.stopToOpenChildTab(frame); + + var ownerBrowser = this.getTabBrowserFromFrame(frame); + ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = true; + ownerBrowser.treeStyleTab.readiedToAttachMultiple = true; + ownerBrowser.treeStyleTab.multipleCount = 0; + ownerBrowser.treeStyleTab.treeStructure = aTreeStructure; + ownerBrowser.treeStyleTab.shouldExpandAllTree = !!aExpandAllTree; + + return true; + }, + /** + * Extended version. If you don't know whether new tabs will be actually + * opened or not (by the command called after TST's API), then use this. + * This version automatically cancels the "ready" state with delay. + */ + readyToOpenNewTabGroupNow : function TSTBase_readyToOpenNewTabGroupNow(...aArgs) /* PUBLIC API */ + { + + if (this.readyToOpenNewTabGroup.apply(this, aArgs)) { + let self = this; + this.Deferred.next(function() { + self.stopToOpenChildTab(aArgs[0]); + }).error(this.defaultDeferredErrorHandler); + return true; + } + return false; + }, + + stopToOpenChildTab : function TSTBase_stopToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */ + { + var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); + if (!frame) + return false; + + var ownerBrowser = this.getTabBrowserFromFrame(frame); + ownerBrowser.treeStyleTab.readiedToAttachNewTab = false; + ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = false; + ownerBrowser.treeStyleTab.readiedToAttachMultiple = false; + ownerBrowser.treeStyleTab.multipleCount = -1; + ownerBrowser.treeStyleTab.parentTab = null; + ownerBrowser.treeStyleTab.insertBefore = null; + ownerBrowser.treeStyleTab.treeStructure = null; + ownerBrowser.treeStyleTab.shouldExpandAllTree = false; + + return true; + }, + + checkToOpenChildTab : function TSTBase_checkToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */ + { + var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); + if (!frame) + return false; + + var ownerBrowser = this.getTabBrowserFromFrame(frame); + return !!(ownerBrowser.treeStyleTab.readiedToAttachNewTab || ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup); + }, + + kNEWTAB_DO_NOTHING : -1, + kNEWTAB_OPEN_AS_ORPHAN : 0, + kNEWTAB_OPEN_AS_CHILD : 1, + kNEWTAB_OPEN_AS_SIBLING : 2, + kNEWTAB_OPEN_AS_NEXT_SIBLING : 3, + readyToOpenRelatedTabAs : function TSTBase_readyToOpenRelatedTabAs(aBaseTab, aBehavior) + { + var frame = this.getFrameFromTabBrowserElements(aBaseTab); + if (!frame) + return; + + aBaseTab = this.getTabFromFrame(frame, this.getTabBrowserFromFrame(frame)); + + switch (aBehavior) + { + case this.kNEWTAB_OPEN_AS_ORPHAN: + case this.kNEWTAB_DO_NOTHING: + default: + break; + case this.kNEWTAB_OPEN_AS_CHILD: + this.readyToOpenChildTabNow(aBaseTab); + break; + case this.kNEWTAB_OPEN_AS_SIBLING: + let (parentTab = this.getParentTab(aBaseTab)) { + if (parentTab) + this.readyToOpenChildTabNow(parentTab); + } + break; + case this.kNEWTAB_OPEN_AS_NEXT_SIBLING: + this.readyToOpenNextSiblingTabNow(aBaseTab); + break; + } + }, + + handleNewTabFromCurrent : function TSTBase_handleNewTabFromCurrent(aBaseTab) + { + this.readyToOpenRelatedTabAs(aBaseTab, utils.getTreePref('autoAttach.fromCurrent')); + }, + +/* tree manipulations */ + + get treeViewEnabled() /* PUBLIC API */ + { + return this._treeViewEnabled; + }, + set treeViewEnabled(aValue) + { + this._treeViewEnabled = !!aValue; + Services.obs.notifyObservers( + window, + this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, + this._treeViewEnabled + ); + return aValue; + }, + _treeViewEnabled : true, + + get rootTabs() /* PUBLIC API */ + { + return Array.slice(this.browser.mTabContainer.querySelectorAll('tab:not(['+this.kNEST+']), tab['+this.kNEST+'=""], tab['+this.kNEST+'="0"]')); + }, + + get allRootTabs() /* PUBLIC API */ + { + return this.rootTabs; + }, + + get visibleRootTabs() /* PUBLIC API */ + { + return this.rootTabs.filter(function(aTab) { + return !aTab.hidden; + }); + }, + + canCollapseSubtree : function TSTBase_canCollapseSubtree(aTabOrTabBrowser) /* PUBLIC API */ + { + if (aTabOrTabBrowser && + aTabOrTabBrowser.localName == 'tab' && + aTabOrTabBrowser.getAttribute(this.kALLOW_COLLAPSE) != 'true') + return false; + + var b = this.getTabBrowserFromChild(aTabOrTabBrowser) || this.browser; + return b && b.getAttribute(this.kALLOW_COLLAPSE) == 'true'; + }, + + isCollapsed : function TSTBase_isCollapsed(aTab) /* PUBLIC API */ + { + if (!aTab || + !this.canCollapseSubtree(this.getRootTab(aTab))) + return false; + + return aTab.getAttribute(this.kCOLLAPSED) == 'true'; + }, + + isSubtreeCollapsed : function TSTBase_isSubtreeCollapsed(aTab) /* PUBLIC API */ + { + if (!aTab || !this.canCollapseSubtree(aTab) || !this.hasChildTabs(aTab)) + return false; + + return aTab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true'; + }, + + shouldCloseTabSubtreeOf : function TSTBase_shouldCloseTabSubtreeOf(aTab) + { + return ( + this.hasChildTabs(aTab) && + ( + utils.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN || + this.isSubtreeCollapsed(aTab) + ) + ); + }, + shouldCloseTabSubTreeOf : function TSTBase_shouldCloseTabSubTreeOf(...aArgs) { + return this.shouldCloseTabSubtreeOf.apply(this, aArgs); + }, // obsolete, for backward compatibility + + shouldCloseLastTabSubtreeOf : function TSTBase_shouldCloseLastTabSubtreeOf(aTab) + { + var b = this.getTabBrowserFromChild(aTab); + return ( + b && + this.shouldCloseTabSubtreeOf(aTab) && + this.getDescendantTabs(aTab).length + 1 == this.getAllTabs(b).length + ); + }, + shouldCloseLastTabSubTreeOf : function TSTBase_shouldCloseLastTabSubTreeOf(...aArgs) { + return this.shouldCloseLastTabSubtreeOf.apply(this, aArgs); + }, // obsolete, for backward compatibility + + getParentTab : function TSTBase_getParentTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + var parent; + var id = aTab.getAttribute(this.kPARENT); + if (this.tabsHash) { // XPath-less implementation + parent = this.getTabById(id); + if (parent && !parent.parentNode && this.tabsHash) { + delete this.tabsHash[id]; + parent = null; + } + } + else { + parent = this.evaluateXPath( + 'preceding-sibling::xul:tab[@'+this.kID+'="'+id+'"][1]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + } + return (parent && parent != aTab) ? parent : null ; + }, + + getAncestorTabs : function TSTBase_getAncestorTabs(aTab) /* PUBLIC API */ + { + var tabs = [aTab]; + var parentTab = aTab; + while (parentTab = this.getParentTab(parentTab)) + { + if (tabs.indexOf(parentTab) > -1) { + let message = 'recursive tree detected!\n'+ + tabs.concat([parentTab]) + .reverse().map(function(aTab) { + return ' '+aTab._tPos+' : '+ + aTab.label+'\n '+ + aTab.getAttribute(this.kID); + }, this).join('\n'); + dump(message+'\n'); + break; + } + + if (aTab._tPos < parentTab._tPos) { + let message = 'broken tree detected!\n'+ + tabs.concat([parentTab]) + .reverse().map(function(aTab) { + return ' '+aTab._tPos+' : '+ + aTab.label+'\n '+ + aTab.getAttribute(this.kID); + }, this).join('\n'); + dump(message+'\n'); + } + + tabs.push(parentTab); + aTab = parentTab; + } + return tabs.slice(1); + }, + + getRootTab : function TSTBase_getRootTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let ancestors = this.getAncestorTabs(aTab); + return ancestors.length ? ancestors[ancestors.length-1] : aTab ; + } + + return this.evaluateXPath( + '(self::*[not(@'+this.kPARENT+')] | preceding-sibling::xul:tab[not(@'+this.kPARENT+')])[last()]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getNextSiblingTab : function TSTBase_getNextSiblingTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let parentTab = this.getParentTab(aTab); + + if (!parentTab) { + let next = aTab; + do { + next = next.nextSibling; + } + while (next && + next.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && + this.getParentTab(next)); + return next; + } + + let children = parentTab.getAttribute(this.kCHILDREN); + if (children) { + let list = ('|'+children).split('|'+aTab.getAttribute(this.kID)); + list = list.length > 1 ? list[1].split('|') : [] ; + for (let i = 0, maxi = list.length; i < maxi; i++) + { + let firstChild = this.getTabById(list[i], aTab); + if (firstChild) + return firstChild; + } + } + return null; + } + + var parent = aTab.getAttribute(this.kPARENT); + return this.evaluateXPath( + 'following-sibling::xul:tab['+ + (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ + '][1]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getPreviousSiblingTab : function TSTBase_getPreviousSiblingTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let parentTab = this.getParentTab(aTab); + + if (!parentTab) { + let prev = aTab; + do { + prev = prev.previousSibling; + } + while (prev && + prev.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && + this.getParentTab(prev)); + return prev; + } + + let children = parentTab.getAttribute(this.kCHILDREN); + if (children) { + let list = ('|'+children).split('|'+aTab.getAttribute(this.kID))[0].split('|'); + for (let i = list.length-1; i > -1; i--) + { + let lastChild = this.getTabById(list[i], aTab); + if (lastChild) + return lastChild; + } + } + return null; + } + + var parent = aTab.getAttribute(this.kPARENT); + return this.evaluateXPath( + 'preceding-sibling::xul:tab['+ + (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ + '][1]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getSiblingTabs : function TSTBase_getSiblingTabs(aTab) /* PUBLIC API */ + { + var parent = this.getParentTab(aTab); + + var siblings = parent && parent.parentNode ? this.getChildTabs(parent) : this.visibleRootTabs ; + return siblings.filter(function(aSiblingTab) { + return aSiblingTab != aTab; + }); + }, + + getChildTabs : function TSTBase_getChildTabs(aTab, aAllTabsArray) /* PUBLIC API */ + { + var tabs = []; + if (!aTab) + return tabs; + + var children = aTab.getAttribute(this.kCHILDREN); + if (!children) + return tabs; + + if (aAllTabsArray) + tabs = aAllTabsArray; + + var list = children.split('|'); + for (let i = 0, maxi = list.length; i < maxi; i++) + { + let tab = this.getTabById(list[i], aTab); + if (!tab || tab == aTab) + continue; + if (tabs.indexOf(tab) > -1) { + let message = 'broken (possible recursive) tree detected!\n'+ + tabs.map(function(aTab) { + return ' '+aTab._tPos+' : '+ + aTab.label+'\n '+ + aTab.getAttribute(this.kID); + }, this).join('\n'); + dump(message+'\n'); + continue; + } + tabs.push(tab); + if (aAllTabsArray) + this.getChildTabs(tab, tabs); + } + + return tabs; + }, + + hasChildTabs : function TSTBase_hasChildTabs(aTab) /* PUBLIC API */ + { + if (!aTab) + return false; + return aTab.hasAttribute(this.kCHILDREN); + }, + + getDescendantTabs : function TSTBase_getDescendantTabs(aTab) /* PUBLIC API */ + { + var tabs = []; + this.getChildTabs(aTab, tabs); + return tabs; + }, + + getFirstChildTab : function TSTBase_getFirstChildTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let children = aTab.getAttribute(this.kCHILDREN); + let firstChild = null; + if (children) { + let list = children.split('|'); + for (let i = 0, maxi = list.length; i < maxi; i++) + { + firstChild = this.getTabById(list[i], aTab); + if (firstChild && firstChild != aTab) + break; + } + } + return firstChild; + } + + return this.evaluateXPath( + 'following-sibling::xul:tab[@'+this.kPARENT+'="'+aTab.getAttribute(this.kID)+'"][1]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getLastChildTab : function TSTBase_getLastChildTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let children = aTab.getAttribute(this.kCHILDREN); + let lastChild = null; + if (children) { + let list = children.split('|'); + for (let i = list.length-1; i > -1; i--) + { + lastChild = this.getTabById(list[i], aTab); + if (lastChild && lastChild != aTab) + break; + } + } + return lastChild; + } + + return this.evaluateXPath( + 'following-sibling::xul:tab[@'+this.kPARENT+'="'+aTab.getAttribute(this.kID)+'"][last()]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + getLastDescendantTab : function TSTBase_getLastDescendantTab(aTab) /* PUBLIC API */ + { + if (!aTab) + return null; + + if (this.tabsHash) { // XPath-less implementation + let tabs = this.getDescendantTabs(aTab); + return tabs.length ? tabs[tabs.length-1] : null ; + } + + var parent = aTab.getAttribute(this.kPARENT); + return this.evaluateXPath( + 'following-sibling::xul:tab['+ + (parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+ + '][1]/preceding-sibling::xul:tab[1][not(@'+this.kID+'="'+aTab.getAttribute(this.kID)+'")]', + aTab, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + collectRootTabs : function TSTBase_collectRootTabs(aTabs) /* PUBLIC API */ + { + aTabs = Array.slice(aTabs); + return aTabs.filter(function(aTab) { + var parent = this.getParentTab(aTab); + return !parent || aTabs.indexOf(parent) < 0; + }, this); + }, + + getChildIndex : function TSTBase_getChildIndex(aTab, aParent) /* PUBLIC API */ + { + var parent = this.getParentTab(aTab); + if (!aParent || !parent || aParent != parent) { + let tabs = [aTab].concat(this.getAncestorTabs(aTab)); + parent = aTab; + for (let i = 0, maxi = tabs.length; i < maxi && parent != aParent; i++) + { + aTab = parent; + parent = i < maxi ? tabs[i+1] : null ; + } + if (parent != aParent) + return -1; + aParent = parent; + } + + if (aParent) { + let children = aParent.getAttribute(this.kCHILDREN); + let list = children.split('|'); + let id = aTab.getAttribute(this.kID); + for (let i = 0, maxi = list.length; i < maxi; i++) + { + if (list[i] == id) + return i; + } + return -1; + } + else { + let tabs = this.rootTabs; + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + if (tabs[i] == aTab) + return i; + } + } + return -1; + }, + + getXOffsetOfTab : function TSTBase_getXOffsetOfTab(aTab) + { + var extraCondition = this.canCollapseSubtree(aTab) ? + '[not(@'+this.kCOLLAPSED+'="true")]' : + '' ; + + return this.evaluateXPath( + 'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+ + '/attribute::'+this.kX_OFFSET+')', + aTab, + Ci.nsIDOMXPathResult.NUMBER_TYPE + ).numberValue; + }, + getYOffsetOfTab : function TSTBase_getYOffsetOfTab(aTab) + { + var extraCondition = this.canCollapseSubtree(aTab) ? + '[not(@'+this.kCOLLAPSED+'="true")]' : + ''; + + return this.evaluateXPath( + 'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+ + '/attribute::'+this.kY_OFFSET+')', + aTab, + Ci.nsIDOMXPathResult.NUMBER_TYPE + ).numberValue; + }, + getFutureBoxObject : function TSTBase_getFutureBoxObject(aTab) + { + var tabBox = aTab.boxObject; + var xOffset = this.getXOffsetOfTab(aTab); + var yOffset = this.getYOffsetOfTab(aTab); + return { + width : tabBox.width, + height : tabBox.height, + x : tabBox.x + xOffset, + y : tabBox.y + yOffset, + screenX : tabBox.screenX + xOffset, + screenY : tabBox.screenY + yOffset + }; + }, + getTabActualScreenPosition : function TSTBase_getTabActualScreenPosition(aTab, aOrient) + { + aOrient = aOrient || aTab.parentNode.orient; + return aOrient == 'vertical' ? + this.getTabActualScreenY(aTab) : + this.getTabActualScreenX(aTab) ; + }, + MATRIX_PATTERN : /matrix\((-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\)/, + getTabActualScreenX : function TSTBase_getTabActualScreenX(aTab) + { + var x = aTab.boxObject.screenX; + + var w = aTab.ownerDocument.defaultView; + var transform = w.getComputedStyle(aTab, null).transform; + var offset = transform && transform.match(this.MATRIX_PATTERN); + offset = offset ? parseFloat(offset[5]) : 0 ; + + return x + offset; + }, + getTabActualScreenY : function TSTBase_getTabActualScreenY(aTab) + { + var y = aTab.boxObject.screenY; + + var w = aTab.ownerDocument.defaultView; + var transform = w.getComputedStyle(aTab, null).transform; + var offset = transform && transform.match(this.MATRIX_PATTERN); + offset = offset ? parseFloat(offset[6]) : 0 ; + + return y + offset; + }, + + isGroupTab : function TSTBase_isGroupTab(aTab, aLazyCheck) + { + return ( + (aLazyCheck || aTab.linkedBrowser.sessionHistory.count == 1) && + aTab.linkedBrowser.currentURI.spec.indexOf('about:treestyletab-group') == 0 + ); + }, + + isTemporaryGroupTab : function TSTBase_isTemporaryGroupTab(aTab) + { + return ( + this.isGroupTab(aTab, true) && + /.*[\?&;]temporary=(?:1|yes|true)/i.test(aTab.linkedBrowser.currentURI.spec) + ); + }, + + get pinnedTabsCount() + { + return this.browser.mTabContainer.querySelectorAll('tab[pinned="true"]').length; + }, + + forceExpandTabs : function TSTBase_forceExpandTabs(aTabs) + { + var collapsedStates = aTabs.map(function(aTab) { + return this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; + }, this); + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + this.collapseExpandSubtree(tab, false, true); + this.collapseExpandTab(tab, false, true); + } + return collapsedStates; + }, + + getTreeStructureFromTabs : function TSTBase_getTreeStructureFromTabs(aTabs) + { + /* this returns... + [A] => -1 (parent is not in this tree) + [B] => 0 (parent is 1st item in this tree) + [C] => 0 (parent is 1st item in this tree) + [D] => 2 (parent is 2nd in this tree) + [E] => -1 (parent is not in this tree, and this creates another tree) + [F] => 0 (parent is 1st item in this another tree) + */ + return this.cleanUpTreeStructureArray( + aTabs.map(function(aTab, aIndex) { + let tab = this.getParentTab(aTab); + let index = tab ? aTabs.indexOf(tab) : -1 ; + return index >= aIndex ? -1 : index ; + }, this), + -1 + ); + }, + cleanUpTreeStructureArray : function TSTBase_cleanUpTreeStructureArray(aTreeStructure, aDefaultParent) + { + var offset = 0; + aTreeStructure = aTreeStructure + .map(function(aPosition, aIndex) { + return (aPosition == aIndex) ? -1 : aPosition ; + }) + .map(function(aPosition, aIndex) { + if (aPosition == -1) { + offset = aIndex; + return aPosition; + } + return aPosition - offset; + }); + + /* The final step, this validates all of values. + Smaller than -1 is invalid, so it becomes to -1. */ + aTreeStructure = aTreeStructure.map(function(aIndex) { + return aIndex < -1 ? aDefaultParent : aIndex ; + }, this); + return aTreeStructure; + }, + + applyTreeStructureToTabs : function TSTBase_applyTreeStructureToTabs(aTabs, aTreeStructure, aExpandStates) + { + var b = this.getTabBrowserFromChild(aTabs[0]); + if (!b) + return; + var sv = b.treeStyleTab; + + aTabs = aTabs.slice(0, aTreeStructure.length); + aTreeStructure = aTreeStructure.slice(0, aTabs.length); + + aExpandStates = (aExpandStates && typeof aExpandStates == 'object') ? + aExpandStates : + aTabs.map(function(aTab) { + return !!aExpandStates; + }); + aExpandStates = aExpandStates.slice(0, aTabs.length); + while (aExpandStates.length < aTabs.length) aExpandStates.push(-1); + + var parentTab = null; + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (sv.isCollapsed(tab)) + sv.collapseExpandTab(tab, false, true); + sv.detachTab(tab); + + let parentIndexInTree = aTreeStructure[i]; + if (parentIndexInTree < 0) // there is no parent, so this is a new parent! + parentTab = tab.getAttribute(sv.kID); + + let parent = sv.getTabById(parentTab); + if (parent) { + let tabs = [parent].concat(sv.getDescendantTabs(parent)); + parent = parentIndexInTree < tabs.length ? tabs[parentIndexInTree] : parent ; + } + if (parent) { + sv.attachTabTo(tab, parent, { + forceExpand : true, + dontMove : true + }); + } + } + + for (let i = aTabs.length-1; i > -1; i--) + { + sv.collapseExpandSubtree(aTabs[i], !sv.hasChildTabs(aTabs[i]) || !aExpandStates[i], true); + } + }, + + getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser) + { + return this.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser)); + }, + + applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree) + { + var tabs = this.getAllTabs(aTabBrowser); + return this.applyTreeStructureToTabs(tabs, aTreeStructure, aExpandAllTree); + }, + +/* tabbar position */ + + get position() /* PUBLIC API */ + { + return utils.getTreePref('tabbar.position') || 'top'; + }, + set position(aValue) + { + var position = String(aValue).toLowerCase(); + if (!position || !/^(top|bottom|left|right)$/.test(position)) + position = 'top'; + + if (position != utils.getTreePref('tabbar.position')) + utils.setTreePref('tabbar.position', position); + + return aValue; + }, + get currentTabbarPosition() /* for backward compatibility */ + { + return this.position; + }, + set currentTabbarPosition(aValue) + { + return this.position = aValue; + }, + + getPositionFlag : function TSTBase_getPositionFlag(aPosition) + { + aPosition = String(aPosition).toLowerCase(); + return (aPosition == 'left') ? this.kTABBAR_LEFT : + (aPosition == 'right') ? this.kTABBAR_RIGHT : + (aPosition == 'bottom') ? this.kTABBAR_BOTTOM : + this.kTABBAR_TOP; + }, + +/* Pref Listener */ + + domains : [ + 'extensions.treestyletab.', + 'browser.tabs.animate', + 'browser.tabs.insertRelatedAfterCurrent', + 'extensions.stm.tabBarMultiRows' // Super Tab Mode + ], + + observe : function TSTBase_observe(aSubject, aTopic, aData) + { + switch (aTopic) + { + case 'nsPref:changed': + this.onPrefChange(aData); + return; + } + }, + + onPrefChange : function TSTBase_onPrefChange(aPrefName) + { + var value = prefs.getPref(aPrefName); + switch (aPrefName) + { + case 'extensions.treestyletab.indent.vertical': + this.baseIndentVertical = value; + Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value); + return; + case 'extensions.treestyletab.indent.horizontal': + this.baseIndentHorizontal = value; + Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value); + return; + + case 'extensions.treestyletab.tabbar.width': + case 'extensions.treestyletab.tabbar.shrunkenWidth': + return this.updateTabWidthPrefs(aPrefName); + + case 'browser.tabs.insertRelatedAfterCurrent': + case 'extensions.stm.tabBarMultiRows': // Super Tab Mode + if (this.prefOverriding) + return; + aPrefName += '.override'; + prefs.setPref(aPrefName, value); + case 'browser.tabs.insertRelatedAfterCurrent.override': + case 'extensions.stm.tabBarMultiRows.override': // Super Tab Mode + if (prefs.getPref(aPrefName+'.force')) { + let defaultValue = prefs.getDefaultPref(aPrefName); + if (value != defaultValue) { + prefs.setPref(aPrefName, defaultValue); + return; + } + } + this.prefOverriding = true; + let (target = aPrefName.replace('.override', '')) { + let originalValue = prefs.getPref(target); + if (originalValue !== null && originalValue != value) + prefs.setPref(target+'.backup', originalValue); + prefs.setPref(target, prefs.getPref(aPrefName)); + } + this.prefOverriding = false; + return; + + case 'extensions.treestyletab.clickOnIndentSpaces.enabled': + return this.shouldDetectClickOnIndentSpaces = prefs.getPref(aPrefName); + + case 'extensions.treestyletab.tabbar.scroll.smooth': + return this.smoothScrollEnabled = value; + case 'extensions.treestyletab.tabbar.scroll.duration': + return this.smoothScrollDuration = value; + + case 'extensions.treestyletab.tabbar.scrollToNewTab.mode': + return this.scrollToNewTabMode = value; + + case 'extensions.treestyletab.tabbar.narrowScrollbar.size': + return this.updateNarrowScrollbarStyle(); + + case 'browser.tabs.animate': + return this.animationEnabled = value; + case 'extensions.treestyletab.animation.indent.duration': + return this.indentDuration = value; + case 'extensions.treestyletab.animation.collapse.duration': + return this.collapseDuration = value; + + case 'extensions.treestyletab.twisty.expandSensitiveArea': + return this.shouldExpandTwistyArea = value; + + case 'extensions.treestyletab.counter.role.horizontal': + return this.counterRoleHorizontal = value; + + case 'extensions.treestyletab.counter.role.vertical': + return this.counterRoleVertical = value; + + default: + return; + } + }, + + updateTabWidthPrefs : function TSTBase_updateTabWidthPrefs(aPrefName) + { + var expanded = utils.getTreePref('tabbar.width'); + var shrunken = utils.getTreePref('tabbar.shrunkenWidth'); + var originalExpanded = expanded; + var originalShrunken = shrunken; + if (aPrefName == 'extensions.treestyletab.tabbar.shrunkenWidth') { + if (expanded <= shrunken) + expanded = parseInt(shrunken / this.DEFAULT_SHRUNKEN_WIDTH_RATIO) + let w = this.browserWindow; + if (w && expanded > w.gBrowser.boxObject.width) { + expanded = w.gBrowser.boxObject.width * this.MAX_TABBAR_SIZE_RATIO; + if (expanded <= shrunken) + shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO) + } + } + else { + if (expanded <= shrunken) + shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO); + } + if (expanded != originalExpanded || + shrunken != originalShrunken) { + utils.setTreePref('tabbar.width', Math.max(0, expanded)); + utils.setTreePref('tabbar.shrunkenWidth', Math.max(0, shrunken)); + } + }, + + get shouldApplyNewPref() + { + return ( + !this.applyOnlyForActiveWindow || + this.window == this.topBrowserWindow + ) && + !this.inWindowDestoructionProcess; + }, + + applyOnlyForActiveWindow : false, + setPrefForActiveWindow : function TSTBase_setPrefForActiveWindow(aTask) { + TreeStyleTabBase.applyOnlyForActiveWindow = true; + try { + aTask.call(this); + } + finally { + TreeStyleTabBase.applyOnlyForActiveWindow = false; + } + } + +}); + +TreeStyleTabBase.init(); + diff --git a/modules/browser.js b/modules/browser.js index fba999cd..9dc4604c 100644 --- a/modules/browser.js +++ b/modules/browser.js @@ -1,6812 +1,6812 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2011-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * wanabe - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TreeStyleTabBrowser']; - -const DEBUG = false; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm'); -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); -XPCOMUtils.defineLazyModuleGetter(this, 'FullTooltipManager', 'resource://treestyletab-modules/fullTooltip.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'TabbarDNDObserver', 'resource://treestyletab-modules/tabbarDNDObserver.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'TabpanelDNDObserver', 'resource://treestyletab-modules/tabpanelDNDObserver.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'AutoHideBrowser', 'resource://treestyletab-modules/autoHide.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUIShowHideObserver', 'resource://treestyletab-modules/browserUIShowHideObserver.js'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); - -Cu.import('resource://treestyletab-modules/window.js'); - -function TreeStyleTabBrowser(aWindowService, aTabBrowser) -{ - this.id = Date.now() + '-' + parseInt(Math.random() * 65000); - - this.windowService = aWindowService; - this.window = aWindowService.window; - this.document = aWindowService.document; - - this.mTabBrowser = aTabBrowser; - aTabBrowser.treeStyleTab = this; - - this.tabVisibilityChangedTabs = []; - this.updateTabsIndentWithDelayTabs = []; - this.deferredTasks = {}; - - this.tabVisibilityChangedTabs = []; - this._updateFloatingTabbarReason = 0; - this.internallyTabMovingCount = 0; - this.subTreeMovingCount = 0; - this.subTreeChildrenMovingCount = 0; - this._treeViewEnabled = true; -} - -TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, { - - kMENUITEM_RELOADSUBTREE : 'context-item-reloadTabSubtree', - kMENUITEM_RELOADCHILDREN : 'context-item-reloadDescendantTabs', - kMENUITEM_REMOVESUBTREE : 'context-item-removeTabSubtree', - kMENUITEM_REMOVECHILDREN : 'context-item-removeDescendantTabs', - kMENUITEM_REMOVEALLTABSBUT : 'context-item-removeAllTabsButThisTree', - kMENUITEM_COLLAPSEEXPAND_SEPARATOR : 'context-separator-collapseExpandAll', - kMENUITEM_COLLAPSE : 'context-item-collapseAllSubtree', - kMENUITEM_EXPAND : 'context-item-expandAllSubtree', - kMENUITEM_AUTOHIDE_SEPARATOR : 'context-separator-toggleAutoHide', - kMENUITEM_AUTOHIDE : 'context-item-toggleAutoHide', - kMENUITEM_FIXED : 'context-item-toggleFixed', - kMENUITEM_BOOKMARKSUBTREE : 'context-item-bookmarkTabSubtree', - - kMENUITEM_CLOSE_TABS_TO_END : 'context_closeTabsToTheEnd', - - mTabBrowser : null, - - indent : -1, - indentProp : 'margin', - indentTarget : 'left', - indentCSSProp : 'margin-left', - collapseTarget : 'top', - collapseCSSProp : 'margin-top', - screenPositionProp : 'screenY', - offsetProp : 'offsetY', - translateFunction : 'translateY', - sizeProp : 'height', - invertedScreenPositionProp : 'screenX', - invertedSizeProp : 'width', - startProp : 'top', - endProp : 'bottom', - - maxTreeLevelPhisical : false, - - needRestoreTree : false, - -/* elements */ - - get browser() - { - return this.mTabBrowser; - }, - - get container() - { - if (!this._container) { - this._container = this.document.getElementById('appcontent'); - } - return this._container; - }, - _container : null, - - get scrollBox() - { - return ( // Tab Mix Plus - utils.getTreePref('compatibility.TMP') && - this.document.getAnonymousElementByAttribute(this.mTabBrowser.mTabContainer, 'class', 'tabs-frame') - ) || - this.mTabBrowser.mTabContainer.mTabstrip; - }, - get scrollBoxObject() - { - var node = this.scrollBox; - if (node._scrollbox) - node = node._scrollbox; - return (node.scrollBoxObject || node.boxObject) - .QueryInterface(Ci.nsIScrollBoxObject); // for Tab Mix Plus (ensure scrollbox-ed) - }, - - get splitter() - { - var d = this.document; - return d.getAnonymousElementByAttribute(this.mTabBrowser, 'class', this.kSPLITTER) || - d.getAnonymousElementByAttribute(this.mTabBrowser, 'id', 'tabkit-splitter'); // Tab Kit - }, - - get tabStripPlaceHolder() - { - return this._tabStripPlaceHolder; - }, - set tabStripPlaceHolder(value) - { - return (this._tabStripPlaceHolder = value); - }, - -/* properties */ - - get maxTreeLevel() - { - return this._maxTreeLevel; - }, - set maxTreeLevel(aValue) - { - this._maxTreeLevel = aValue; - this.setTabbrowserAttribute(this.kMAX_LEVEL, this._maxTreeLevel || '0'); - this.enableSubtreeIndent = this._maxTreeLevel != 0; - return aValue; - }, - _maxTreeLevel : -1, - - get baseIndent() { - return this.isVertical ? this.baseIndentVertical : this.baseIndentHorizontal; - }, - - get enableSubtreeIndent() - { - return this._enableSubtreeIndent; - }, - set enableSubtreeIndent(aValue) - { - this._enableSubtreeIndent = aValue; - this.setTabbrowserAttribute(this.kINDENTED, this._enableSubtreeIndent ? 'true' : null); - return aValue; - }, - _enableSubtreeIndent : true, - - get allowSubtreeCollapseExpand() - { - return this._allowSubtreeCollapseExpand; - }, - set allowSubtreeCollapseExpand(aValue) - { - this._allowSubtreeCollapseExpand = aValue; - this.setTabbrowserAttribute(this.kALLOW_COLLAPSE, this._allowSubtreeCollapseExpand ? 'true' : null); - return aValue; - }, - _allowSubtreeCollapseExpand : true, - - get fixed() - { - var orient = this.isVertical ? 'vertical' : 'horizontal' ; - if (!this.windowService.preInitialized) - return utils.getTreePref('tabbar.fixed.'+orient); - - var b = this.mTabBrowser; - if (!b) - return false; - return b.getAttribute(this.kFIXED+'-'+orient) == 'true'; - }, - set fixed(aValue) - { - this.setTabbrowserAttribute(this.kFIXED, aValue || null, this.mTabBrowser); - return aValue; - }, - get isFixed() // for backward compatibility - { - return this.fixed; - }, - - get position() /* PUBLIC API */ - { - return ( - // Don't touch to the element before it is initialized by XBL constructor. - (this.windowService.preInitialized && this.browser.getAttribute(this.kTABBAR_POSITION)) || - this.base.position - ); - }, - set position(aValue) - { - var position = String(aValue).toLowerCase(); - if (!position || !/^(top|bottom|left|right)$/.test(position)) - position = 'top'; - - if (position == this.position) - return position; - - if ('UndoTabService' in this.window && this.window.UndoTabService.isUndoable()) { - var current = this.position; - var self = this; - this.window.UndoTabService.doOperation( - function() { - self._changeTabbarPosition(position); - }, - { - label : utils.treeBundle.getString('undo_changeTabbarPosition_label'), - name : 'treestyletab-changeTabbarPosition-private', - data : { - oldPosition : current, - newPosition : position, - target : self.mTabBrowser.id - } - } - ); - } - else { - this._changeTabbarPosition(position); - } - return position; - }, - _changeTabbarPosition : function TSTBrowser_changeTabbarPosition(aNewPosition) - { - if (this.deferredTasks['_changeTabbarPosition']) - this.deferredTasks['_changeTabbarPosition'].cancel(); - - var oldPosition = this.position; - this.fireTabbarPositionEvent(true, oldPosition, aNewPosition); - - this.initTabbar(aNewPosition, oldPosition); - this.reinitAllTabs(); - - var self = this; - (this.deferredTasks['_changeTabbarPosition'] = this.Deferred.next(function() { - self.checkTabsIndentOverflow(); - self.fireTabbarPositionEvent(false, oldPosition, aNewPosition); - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['_changeTabbarPosition']; - }); - }, - -/* status getters */ - - get isVertical() - { - if (!this.windowService.preInitialized) - return ['left', 'right'].indexOf(this.position) > -1; - - var b = this.mTabBrowser; - if (!b) - return false; - - if (b.hasAttribute(this.kMODE)) - return b.getAttribute(this.kMODE) == 'vertical'; - - var box = this.scrollBox || b.mTabContainer ; - return (box.getAttribute('orient') || this.window.getComputedStyle(box, '').getPropertyValue('-moz-box-orient')) == 'vertical'; - }, - - get isVisible() - { - var bar = this.ownerToolbar; - var style = this.window.getComputedStyle(bar, ''); - if (style.visibility != 'visible' || style.display == 'none') - return false; - - var box = bar.boxObject; - return !!(box.width || box.height); - }, - - isFloating : true, // for backward compatibility (but this should be removed) - - get ownerToolbar() - { - return this.evaluateXPath( - 'ancestor-or-self::xul:toolbar[1]', - this.mTabBrowser.tabContainer, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - get canStackTabs() - { - return ( - !this.isVertical && - this.canCollapseSubtree() && - utils.getTreePref('stackCollapsedTabs') - ); - }, - - get counterRole() - { - return this.isVertical ? this.counterRoleVertical : this.counterRoleHorizontal ; - }, - - get isDestroying() - { - return !this.mTabBrowser || !this.mTabBrowser.mTabContainer; - }, - -/* utils */ - -/* get tab contents */ - - getTabLabel : function TSTBrowser_getTabLabel(aTab) - { - var d = this.document; - var label = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-stack') || // Mac OS X - ( // Tab Mix Plus - utils.getTreePref('compatibility.TMP') && - d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-container') - ) || - d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text tab-label'); - return label; - }, - - getTabClosebox : function TSTBrowser_getTabClosebox(aTab) - { - var d = this.document; - var close = ( // Tab Mix Plus - utils.getTreePref('compatibility.TMP') && - d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button always-right') - ) || - d.getAnonymousElementByAttribute(aTab, 'anonid', 'close-button') || // with Australis - d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button'); - return close; - }, - - getTabTwisty : function TSTBrowser_getTabTwisty(aTab) - { - return this.document.getAnonymousElementByAttribute(aTab, 'class', this.kTWISTY); - }, - - getTabTwistyAnchorNode : function TSTBrowser_getTabTwistyAnchorNode(aTab) - { - return this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon') || // Tab Mix Plus - this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber'); - }, - - getTabFromTabbarEvent : function TSTBrowser_getTabFromTabbarEvent(aEvent) - { - if ( - !this.shouldDetectClickOnIndentSpaces || - !this.getAncestorTabbarFromEvent(aEvent) || - this.isEventFiredOnClickable(aEvent) || - this.getSplitterFromEvent(aEvent) - ) - return null; - return this.getTabFromCoordinates(aEvent); - }, - getTabFromCoordinates : function TSTBrowser_getTabFromCoordinates(aCoordinates, aTabs) - { - var tab = this.document.elementFromPoint(aCoordinates.clientX, aCoordinates.clientY); - if (tab && tab.localName == 'tab' && (!aTabs || aTabs.indexOf(tab) > -1)) - return tab; - - var positionCoordinate = aCoordinates[this.screenPositionProp]; - - var tabs = aTabs || this.getTabs(this.mTabBrowser); - if (!tabs.length || - this.getTabActualScreenPosition(tabs[0]) > positionCoordinate || - this.getTabActualScreenPosition(tabs[tabs.length-1]) < positionCoordinate) - return null; - - var low = 0; - var high = tabs.length - 1; - while (low <= high) { - let middle = Math.floor((low + high) / 2); - let position = this.getTabActualScreenPosition(tabs[middle]); - if (position > positionCoordinate) { - high = middle - 1; - } - else if (position + tabs[middle].boxObject[this.sizeProp] < positionCoordinate) { - low = middle + 1; - } - else { - return tabs[middle]; - } - } - return null; -/* - var tab = null; - this.getTabs(this.mTabBrowser).some(function(aTab) { - var box = aTab.boxObject; - if (box[this.screenPositionProp] > positionCoordinate || - box[this.screenPositionProp] + box[this.sizeProp] < positionCoordinate) { - return false; - } - tab = aTab; - return true; - }, this); - return tab; -*/ - }, - - getNextFocusedTab : function TSTBrowser_getNextFocusedTab(aTab) - { - return this.getNextSiblingTab(aTab) || - this.getPreviousVisibleTab(aTab); - }, - - isTabInViewport : function TSTBrowser_isTabInViewport(aTab) - { - if (!this.windowService.preInitialized || !aTab) - return false; - if (aTab.getAttribute('pinned') == 'true') - return true; - var tabBox = this.getFutureBoxObject(aTab); - var barBox = this.scrollBox.boxObject; - return ( - tabBox.screenX >= barBox.screenX && - tabBox.screenX + tabBox.width <= barBox.screenX + barBox.width && - tabBox.screenY >= barBox.screenY && - tabBox.screenY + tabBox.height <= barBox.screenY + barBox.height - ); - }, - - isMultiRow : function TSTBrowser_isMultiRow() - { - var w = this.window; - return ('tabberwocky' in w && utils.getTreePref('compatibility.Tabberwocky')) ? - (prefs.getPref('tabberwocky.multirow') && !this.isVertical) : - ('TabmixTabbar' in w && utils.getTreePref('compatibility.TMP')) ? - w.TabmixTabbar.isMultiRow : - false ; - }, - - positionPinnedTabs : function TSTBrowser_positionPinnedTabs(aWidth, aHeight, aJustNow) - { - var b = this.mTabBrowser; - var tabbar = b.tabContainer; - if ( - !tabbar || - !tabbar._positionPinnedTabs || - !tabbar.boxObject.width - ) - return; - - var count = this.pinnedTabsCount; - if (!this.isVertical || !count) { - this.resetPinnedTabs(); - b.mTabContainer._positionPinnedTabs(); - return; - } - - var tabbarPlaceHolderWidth = this._tabStripPlaceHolder.boxObject.width; - var tabbarWidth = this.tabStrip.boxObject.width; - - var maxWidth = tabbarPlaceHolderWidth || tabbarWidth; - - var faviconized = utils.getTreePref('pinnedTab.faviconized'); - var faviconizedSize = tabbar.childNodes[0].boxObject.height; - - var width = faviconized ? faviconizedSize : maxWidth ; - var height = faviconizedSize; - var maxCol = Math.max(1, Math.floor(maxWidth / width)); - var maxRow = Math.ceil(count / maxCol); - var col = 0; - var row = 0; - - var baseX = this.tabStrip.boxObject.screenX - this.document.documentElement.boxObject.screenX; - - var shrunkenOffset = (this.position == 'right' && tabbarPlaceHolderWidth) ? - tabbarWidth - tabbarPlaceHolderWidth : - 0 ; - - var removeFaviconizedClassPattern = new RegExp('\\s+'+this.kFAVICONIZED, 'g'); - - tabbar.style.MozMarginStart = ''; - tabbar.style.setProperty('margin-top', (height * maxRow)+'px', 'important'); - for (let i = 0; i < count; i++) - { - let item = tabbar.childNodes[i]; - - let style = item.style; - style.MozMarginStart = ''; - - let transitionStyleBackup = style.transition || style.MozTransition || ''; - if (aJustNow) - style.MozTransition = style.transition = 'none'; - - let className = item.className.replace(removeFaviconizedClassPattern, ''); - if (faviconized) - className += ' '+this.kFAVICONIZED; - if (className != item.className) - item.className = className; - - style.maxWidth = style.width = width+'px'; - style.setProperty('margin-left', ((width * col) + shrunkenOffset)+'px', 'important'); - style.left = baseX+'px'; - style.right = 'auto'; - style.marginRight = ''; - - style.setProperty('margin-top', (- height * (maxRow - row))+'px', 'important'); - style.top = style.bottom = ''; - - if (aJustNow) { - let key = 'positionPinnedTabs_tab_'+parseInt(Math.random() * 65000); - (this.deferredTasks[key] = this.Deferred.next(function() { // "transition" must be cleared after the reflow. - style.MozTransition = style.transition = transitionStyleBackup; - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - } - - col++; - if (col >= maxCol) { - col = 0; - row++; - } - } - }, - positionPinnedTabsWithDelay : function TSTBrowser_positionPinnedTabsWithDelay(...aArgs) - { - if (this.deferredTasks['positionPinnedTabsWithDelay']) - return; - - var lastArgs = this.deferredTasks['positionPinnedTabsWithDelay'] ? - this.deferredTasks['positionPinnedTabsWithDelay'].__treestyletab__args : - [null, null, false] ; - lastArgs[0] = lastArgs[0] || aArgs[0]; - lastArgs[1] = lastArgs[1] || aArgs[1]; - lastArgs[2] = lastArgs[2] || aArgs[2]; - - var self = this; - (this.deferredTasks['positionPinnedTabsWithDelay'] = this.Deferred.wait(0).next(function() { - return self.Deferred.next(function() { - // do with delay again, after Firefox's reposition was completely finished. - self.positionPinnedTabs.apply(self, lastArgs); - }); - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['positionPinnedTabsWithDelay']; - }); - this.deferredTasks['positionPinnedTabsWithDelay'].__treestyletab__args = lastArgs; - }, - - resetPinnedTabs : function TSTBrowser_resetPinnedTabs() - { - var b = this.mTabBrowser; - var tabbar = b.tabContainer; - tabbar.style.MozMarginStart = tabbar.style.marginTop = ''; - for (var i = 0, count = this.pinnedTabsCount; i < count; i++) - { - let style = tabbar.childNodes[i].style; - style.maxWidth = style.width = style.left = style.right = - style.MozMarginStart = style.marginLeft = style.marginRight = style.marginTop = ''; - } - }, - - updateTabsZIndex : function TSTBrowser_updateTabsZIndex(aStacked) - { - var tabs = this.getTabs(this.mTabBrowser); - var count = tabs.length; - for (let i = 0; i < count; i++) - { - let tab = tabs[i]; - if (aStacked) - tab.style.zIndex = count * 1000 - i; - else - tab.style.zIndex = ''; - } - }, - - fixTooNarrowTabbar : function TSTBrowser_fixTooNarrowTabbar() - { - /** - * The tab bar can become smaller than the actual size of the - * floating tab bar, and then, we cannot resize tab bar by - * dragging anymore. To avoid this problem, we have to enlarge - * the tab bar larger than the floating tab bar. - */ - if (this.isVertical) { - let key = this.autoHide.expanded ? - 'tabbar.width' : 'tabbar.shrunkenWidth' ; - let width = utils.getTreePref(key); - let minWidth = Math.max(this.MIN_TABBAR_WIDTH, this.scrollBox.boxObject.width); - if (minWidth > width) { - this.setPrefForActiveWindow(function() { - utils.setTreePref(key, minWidth); - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); - }); - } - } - else { - let height = utils.getTreePref('tabbar.height'); - let minHeight = Math.max(this.MIN_TABBAR_HEIGHT, this.scrollBox.boxObject.height); - if (minHeight > height) { - this.setPrefForActiveWindow(function() { - utils.setTreePref('tabbar.height', minHeight); - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); - }); - } - } - }, - -/* initialize */ - - init : function TSTBrowser_init() - { - this.stopRendering(); - - var w = this.window; - var d = this.document; - var b = this.mTabBrowser; - b.tabContainer.treeStyleTab = this; - - this.tabsHash = {}; - - if (b.tabContainer.parentNode.localName == 'toolbar') - b.tabContainer.parentNode.classList.add(this.kTABBAR_TOOLBAR); - - /** - * On secondary (and later) window, SSWindowStateBusy event can be fired - * before DOMContentLoad, on "domwindowopened". - */ - this.needRestoreTree = w.__treestyletab__WindowStateBusy || false; - delete w.__treestyletab__WindowStateBusy; - - this._initTabbrowserExtraContents(); - - var position = this.position; - this.fireTabbarPositionEvent(this.kEVENT_TYPE_TABBAR_POSITION_CHANGING, 'top', position); /* PUBLIC API */ - - this.setTabbrowserAttribute(this.kFIXED+'-horizontal', utils.getTreePref('tabbar.fixed.horizontal') ? 'true' : null, b); - this.setTabbrowserAttribute(this.kFIXED+'-vertical', utils.getTreePref('tabbar.fixed.vertical') ? 'true' : null, b); - this.setTabStripAttribute(this.kTAB_STRIP_ELEMENT, true); - - /** - * has its custom background color for itself, but it - * prevents to make transparent background of the vertical tab bar. - * So, I re-define the background color of content area for - * es via dynamically generated stylesheet. - * See: - * https://bugzilla.mozilla.org/show_bug.cgi?id=558585 - * http://hg.mozilla.org/mozilla-central/rev/e90bdd97d168 - */ - if (b.style.backgroundColor) { - let color = b.style.backgroundColor; - let pi = d.createProcessingInstruction( - 'xml-stylesheet', - 'type="text/css" href="data:text/css,'+encodeURIComponent( - ('.tabbrowser-tabbox > tabpanels > notificationbox {\n' + - ' background-color: %COLOR%;\n' + - '}').replace(/%COLOR%/, color) - )+'"' - ); - d.insertBefore(pi, d.documentElement); - b.style.backgroundColor = ''; - } - - this.initTabbar(null, this.kTABBAR_TOP); - - w.addEventListener('resize', this, true); - w.addEventListener('beforecustomization', this, true); - w.addEventListener('aftercustomization', this, false); - w.addEventListener('customizationchange', this, false); - w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); - w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); - w.addEventListener('tabviewframeinitialized', this, false); - w.addEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); - w.addEventListener('SSWindowStateBusy', this, false); - - b.addEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); - - w['piro.sakura.ne.jp'].tabsDragUtils.initTabBrowser(b); - - w.TreeStyleTabWindowHelper.initTabbrowserMethods(b); - this._initTabbrowserContextMenu(); - w.TreeStyleTabWindowHelper.updateTabDNDObserver(b); - - this.getAllTabs(b).forEach(this.initTab, this); - - this.onPrefChange('extensions.treestyletab.maxTreeLevel'); - this.onPrefChange('extensions.treestyletab.tabbar.style'); - this.onPrefChange('extensions.treestyletab.twisty.style'); - this.onPrefChange('extensions.treestyletab.showBorderForFirstTab'); - this.onPrefChange('extensions.treestyletab.tabbar.invertTabContents'); - this.onPrefChange('extensions.treestyletab.tabbar.invertClosebox'); - this.onPrefChange('extensions.treestyletab.tabbar.autoShow.mousemove'); - this.onPrefChange('extensions.treestyletab.tabbar.invertScrollbar'); - this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar'); - this.onPrefChange('browser.tabs.animate'); - - Services.obs.addObserver(this, this.kTOPIC_INDENT_MODIFIED, false); - Services.obs.addObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL, false); - Services.obs.addObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, false); - Services.obs.addObserver(this, 'lightweight-theme-styling-update', false); - prefs.addPrefListener(this); - - // Don't init these ovservers on this point to avoid needless initializations. - // this.tabbarDNDObserver; - // this.panelDNDObserver; - this._readyToInitDNDObservers(); - - // Init autohide service only if it have to be activated. - if (this.isAutoHide) - this.autoHide; - - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_INITIALIZE); - this.fixTooNarrowTabbar(); - - this.fireTabbarPositionEvent(false, 'top', position); /* PUBLIC API */ - - this.startRendering(); - - if (this.deferredTasks['init']) - this.deferredTasks['init'].cancel(); - - var self = this; - (this.deferredTasks['init'] = this.Deferred.next(function() { - // This command is always enabled and the TabsOnTop can be enabled - // by .updateVisibility(). - // So we have to reset TabsOnTop state on the startup. - var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop'); - var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ; - if (TabsOnTop && TabsOnTop.syncUI && toggleTabsOnTop && self.isVertical) { - toggleTabsOnTop.setAttribute('disabled', true); - if (TabsOnTop.enabled && TabsOnTop.toggle) - TabsOnTop.toggle(); - } - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['init']; - }); - }, - - _initTabbrowserExtraContents : function TSTBrowser_initTabbrowserExtraContents() - { - var d = this.document; - var b = this.mTabBrowser; - - var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); - if (!toggler) { - toggler = d.createElement('spacer'); - toggler.setAttribute(this.kTAB_STRIP_ELEMENT, true); - toggler.setAttribute('class', this.kTABBAR_TOGGLER); - toggler.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 - b.mTabBox.insertBefore(toggler, b.mTabBox.firstChild); - if (b.mTabDropIndicatorBar == toggler) - b.mTabDropIndicatorBar = d.getAnonymousElementByAttribute(b, 'class', 'tab-drop-indicator-bar'); - } - - var placeHolder = d.getAnonymousElementByAttribute(b, 'anonid', 'strip'); - if (!placeHolder) { - placeHolder = d.createElement('hbox'); - placeHolder.setAttribute(this.kTAB_STRIP_ELEMENT, true); - placeHolder.setAttribute('anonid', 'strip'); - placeHolder.setAttribute('class', 'tabbrowser-strip '+this.kTABBAR_PLACEHOLDER); - placeHolder.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 - b.mTabBox.insertBefore(placeHolder, toggler.nextSibling); - } - this.tabStripPlaceHolder = (placeHolder != this.tabStrip) ? placeHolder : null ; - - if (this.tabStripPlaceHolder) - this.tabStripPlaceHolderBoxObserver = new BrowserUIShowHideObserver(this, this.tabStripPlaceHolder.parentNode); - }, - - _initTabbrowserContextMenu : function TSTBrowser_initTabbrowserContextMenu() - { - var w = this.window; - var d = this.document; - var b = this.mTabBrowser; - - var tabContextMenu = b.tabContextMenu || - d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); - tabContextMenu.addEventListener('popupshowing', this, false); - if (!('MultipleTabService' in w)) { - w.setTimeout(function(aSelf, aTabBrowser, aPopup) { - let suffix = '-tabbrowser-'+(aTabBrowser.id || 'instance-'+parseInt(Math.random() * 65000)); - let ids = [ - aSelf.kMENUITEM_RELOADSUBTREE, - aSelf.kMENUITEM_RELOADCHILDREN, - aSelf.kMENUITEM_REMOVESUBTREE, - aSelf.kMENUITEM_REMOVECHILDREN, - aSelf.kMENUITEM_REMOVEALLTABSBUT, - aSelf.kMENUITEM_COLLAPSEEXPAND_SEPARATOR, - aSelf.kMENUITEM_COLLAPSE, - aSelf.kMENUITEM_EXPAND, - aSelf.kMENUITEM_AUTOHIDE_SEPARATOR, - aSelf.kMENUITEM_AUTOHIDE, - aSelf.kMENUITEM_FIXED, - aSelf.kMENUITEM_BOOKMARKSUBTREE - ]; - for (let i = 0, maxi = ids.length; i < maxi; i++) - { - let id = ids[i]; - let item = d.getElementById(id).cloneNode(true); - item.setAttribute('id', item.getAttribute('id')+suffix); - - let refNode = void(0); - let insertAfter = item.getAttribute('multipletab-insertafter'); - if (insertAfter) { - try { - eval('refNode = ('+insertAfter+').nextSibling'); - } - catch(e) { - } - } - let insertBefore = item.getAttribute('multipletab-insertbefore'); - if (refNode === void(0) && insertBefore) { - try { - eval('refNode = '+insertBefore); - } - catch(e) { - } - } - aPopup.insertBefore(item, refNode || null); - } - tabContextMenu = null; - }, 0, this, b, tabContextMenu); - } - - let closeTabsToEnd = d.getElementById(this.kMENUITEM_CLOSE_TABS_TO_END); - if (closeTabsToEnd) { - this._closeTabsToEnd_horizontalLabel = closeTabsToEnd.getAttribute('label'); - this._closeTabsToEnd_horizontalAccesskey = closeTabsToEnd.getAttribute('accesskey'); - } - - var removeTabItem = d.getAnonymousElementByAttribute(b, 'id', 'context_closeTab'); - if (removeTabItem) { - removeTabItem.setAttribute( - 'oncommand', - removeTabItem.getAttribute('oncommand').replace( - /(tabbrowser\.removeTab\(([^\)]+)\))/, - 'if (tabbrowser.treeStyleTab.warnAboutClosingTabSubtreeOf($2)) $1' - ) - ); - } - }, - - _initTooltipManager : function TSTBrowser_initTooltipManager() - { - if (this.tooltipManager) - return; - - this.tooltipManager = new FullTooltipManager(this); - }, - - _readyToInitDNDObservers : function TSTBrowser_readyToInitDNDObservers() - { - var w = this.window; - this._DNDObserversInitialized = false; - w.addEventListener('mouseover', this, true); - w.addEventListener('dragover', this, true); - }, - - _initDNDObservers : function TSTBrowser_initDNDObservers() - { - if (this._DNDObserversInitialized) - return; - - this.tabbarDNDObserver; - this.panelDNDObserver; - - var w = this.window; - w.removeEventListener('mouseover', this, true); - w.removeEventListener('dragover', this, true); - this._DNDObserversInitialized = true; - }, - - initTab : function TSTBrowser_initTab(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - if (!aTab.hasAttribute(this.kID)) { - let id = this.getTabValue(aTab, this.kID) || this.makeNewId(); - aTab.setAttribute(this.kID, id); - aTab.setAttribute(this.kID_NEW, id); - aTab.setAttribute(this.kSUBTREE_COLLAPSED, true); - aTab.setAttribute(this.kALLOW_COLLAPSE, true); - let self = this; - let key = 'initTab_'+id; - if (this.deferredTasks[key]) - this.deferredTasks[key].cancel(); - (this.deferredTasks[key] = this.Deferred.next(function() { - // changed by someone! - if (aTab.getAttribute(self.kID) != id) - return; - - aTab.removeAttribute(this.kID_NEW); - if (!self.getTabValue(aTab, self.kID)) { - self.setTabValue(aTab, self.kID, id); - if (!(id in self.tabsHash)) - self.tabsHash[id] = aTab; - } - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - if (!(id in this.tabsHash)) - this.tabsHash[id] = aTab; - } - else { - // if the tab is restored from session, it can be not-cached. - let id = aTab.getAttribute(this.kID); - if (!(id in this.tabsHash)) - this.tabsHash[id] = aTab; - } - - aTab.__treestyletab__linkedTabBrowser = this.mTabBrowser; - - if (!aTab.linkedBrowser.__treestyletab__toBeRestored) - aTab.linkedBrowser.__treestyletab__toBeRestored = utils.isTabNotRestoredYet(aTab); - - this.initTabAttributes(aTab); - this.initTabContents(aTab); - - if (!aTab.hasAttribute(this.kNEST)) - aTab.setAttribute(this.kNEST, 0); - }, - - isTabInitialized : function TSTBrowser_isTabInitialized(aTab) - { - return aTab.getAttribute(this.kID); - }, - - ensureTabInitialized : function TSTBrowser_ensureTabInitialized(aTab) - { - if (!aTab || this.isTabInitialized(aTab)) - return; - this.initTab(aTab); - }, - - initTabAttributes : function TSTBrowser_initTabAttributes(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var pos = this.position; - if (pos == 'left' || pos == 'right') { - aTab.setAttribute('align', 'stretch'); - aTab.removeAttribute('maxwidth'); - aTab.removeAttribute('minwidth'); - aTab.removeAttribute('width'); - aTab.removeAttribute('flex'); - aTab.maxWidth = 65000; - aTab.minWidth = 0; - if (utils.getTreePref('compatibility.TMP')) - aTab.setAttribute('dir', 'ltr'); // Tab Mix Plus - } - else { - aTab.removeAttribute('align'); - aTab.removeAttribute('maxwidth'); - aTab.removeAttribute('minwidth'); - if (utils.getTreePref('compatibility.TMP')) - aTab.removeAttribute('dir'); // Tab Mix Plus - } - }, - - initTabContents : function TSTBrowser_initTabContents(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var d = this.document; - - var twisty = this.getTabTwisty(aTab); - var anchor = this.getTabTwistyAnchorNode(aTab); - if (anchor && !twisty) { - twisty = d.createElement('image'); - twisty.setAttribute('class', this.kTWISTY); - anchor.parentNode.appendChild(twisty); - } - - var label = this.getTabLabel(aTab); - var counter = d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER); - if (label && !counter) { - counter = d.createElement('hbox'); - counter.setAttribute('class', this.kCOUNTER_CONTAINER); - - let startParen = counter.appendChild(d.createElement('label')); - startParen.setAttribute('class', this.kCOUNTER_PAREN); - startParen.setAttribute('value', '('); - - let counterLabel = counter.appendChild(d.createElement('label')); - counterLabel.setAttribute('class', this.kCOUNTER); - counterLabel.setAttribute('value', '0'); - - let endParen = counter.appendChild(d.createElement('label')); - endParen.setAttribute('class', this.kCOUNTER_PAREN); - endParen.setAttribute('value', ')'); - - /** XXX - * Insertion before an anonymous element breaks its "xbl:inherits". - * For example, "xbl:inherits" of the closebox in a tab (Tab Mix Plus - * defines it) doesn't work. So, I don't use insertBefore(). - * Instead, the counter will be rearranged by "ordinal" attribute - * given by initTabContentsOrder(). - */ - // label.parentNode.insertBefore(counter, label.nextSibling); - label.parentNode.appendChild(counter); - } - - this.initTabContentsOrder(aTab, true); - }, - - initTabContentsOrder : function TSTBrowser_initTabContentsOrder(aTab, aForce) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var d = this.document; - - var namedNodes = { - label : this.getTabLabel(aTab), - close : this.getTabClosebox(aTab), - twistyAnchor : this.getTabTwistyAnchorNode(aTab), - twisty : this.getTabTwisty(aTab), - counter : d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER) - }; - - namedNodes.closeAnchor = namedNodes.label; - if (namedNodes.closeAnchor.parentNode != namedNodes.close.parentNode) { - let containerFinder = d.createRange(); - containerFinder.selectNode(namedNodes.closeAnchor); - containerFinder.setEndAfter(namedNodes.close); - let container = containerFinder.commonAncestorContainer; - while (namedNodes.closeAnchor.parentNode != container) - { - namedNodes.closeAnchor = namedNodes.closeAnchor.parentNode; - } - while (namedNodes.close.parentNode != container) - { - namedNodes.close = namedNodes.close.parentNode; - } - } - - namedNodes.counterAnchor = namedNodes.label; - - var foundContainers = []; - var containers = [ - namedNodes.twistyAnchor.parentNode, - namedNodes.label.parentNode, - namedNodes.counter.parentNode, - namedNodes.closeAnchor.parentNode - ]; - for (let i = 0, maxi = containers.length; i < maxi; i++) - { - let container = containers[i]; - if (foundContainers.indexOf(container) > -1) - continue; - this.initTabContentsOrderInternal(container, namedNodes, aForce); - foundContainers.push(container); - } - }, - initTabContentsOrderInternal : function TSTBrowser_initTabContentsOrderInternal(aContainer, aNamedNodes, aForce) - { - if (this.window.getComputedStyle(aContainer, '').getPropertyValue('-moz-box-orient') == 'vertical') - return; - - var nodes = Array.slice(this.document.getAnonymousNodes(aContainer) || aContainer.childNodes); - - // reset order at first! - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - if (node.getAttribute('class') == 'informationaltab-thumbnail-container') - continue; - node.setAttribute('ordinal', i); - } - - // after that, rearrange contents - - var index = nodes.indexOf(aNamedNodes.close); - if (index > -1) { - nodes.splice(index, 1); - if (this.mTabBrowser.getAttribute(this.kCLOSEBOX_INVERTED) == 'true') - nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor), 0, aNamedNodes.close); - else - nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor)+1, 0, aNamedNodes.close); - } - - index = nodes.indexOf(aNamedNodes.twisty); - if (index > -1) { - nodes.splice(index, 1); - nodes.splice(nodes.indexOf(aNamedNodes.twistyAnchor), 0, aNamedNodes.twisty); - } - - if (this.mTabBrowser.getAttribute(this.kTAB_CONTENTS_INVERTED) == 'true') - nodes.reverse(); - - // counter must rightside of the label! - index = nodes.indexOf(aNamedNodes.counter); - if (index > -1) { - nodes.splice(index, 1); - nodes.splice(nodes.indexOf(aNamedNodes.counterAnchor)+1, 0, aNamedNodes.counter); - } - - var count = nodes.length; - nodes.reverse(); - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - if (node.getAttribute('class') == 'informationaltab-thumbnail-container') - continue; - node.setAttribute('ordinal', (count - i + 1) * 100); - } - - if (aForce) { - /** - * After the order of contents are changed dynamically, - * Gecko doesn't re-render them in the new order. - * Changing of "display" or "position" can fix this problem. - */ - let shouldHideTemporaryState = ( - 'TabmixTabbar' in this.window || // Tab Mix Plus - 'InformationalTabService' in this.window // Informational Tab - ); - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - if (shouldHideTemporaryState) - node.style.visibility = 'hidden'; - node.style.position = 'fixed'; - } - let key = 'initTabContentsOrderInternal_'+parseInt(Math.random() * 65000); - (this.deferredTasks[key] = this.Deferred.wait(0.1).next(function() { - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - node.style.position = ''; - if (shouldHideTemporaryState) - node.style.visibility = ''; - } - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - } - }, - - updateInvertedTabContentsOrder : function TSTBrowser_updateInvertedTabContentsOrder(aTarget) - { - var self = this; - let key = 'updateInvertedTabContentsOrder_'+parseInt(Math.random() * 65000); - (this.deferredTasks[key] = this.Deferred.next(function() { - var b = self.mTabBrowser; - var tabs = !aTarget ? - [b.selectedTab] : - (aTarget instanceof Ci.nsIDOMElement) ? - [aTarget] : - (typeof aTarget == 'object' && 'length' in aTarget) ? - Array.slice(aTarget) : - self.getAllTabs(b); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - self.initTabContentsOrder(tabs[i]); - } - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - }, - - initTabbar : function TSTBrowser_initTabbar(aNewPosition, aOldPosition) - { - var d = this.document; - var b = this.mTabBrowser; - - if (aNewPosition && typeof aNewPosition == 'string') - aNewPosition = this.getPositionFlag(aNewPosition); - if (aOldPosition && typeof aOldPosition == 'string') - aOldPosition = this.getPositionFlag(aOldPosition); - - this._startListenTabbarEvents(); - this.window.TreeStyleTabWindowHelper.initTabbarMethods(b); - - this.stopRendering(); - - var pos = aNewPosition || this.getPositionFlag(this.position); - if (b.getAttribute('id') != 'content' && - !utils.getTreePref('tabbar.position.subbrowser.enabled')) { - pos = this.kTABBAR_TOP; - } - - aOldPosition = aOldPosition || pos; - - // We have to use CSS property hack instead, because the stopRendering() - // doesn't effect on the first time of startup. - // * This hack works in a "stop"-"start" pair, so, people never see the side effect. - // * This hack works only when "ordinal" properties are modified. - // So, this is just for the case: "right" or "bottom" tab bar on the startup. - if ( - pos != aOldPosition && - ( - ((pos & this.kTABBAR_REGULAR) && (aOldPosition & this.kTABBAR_INVERTED)) || - ((pos & this.kTABBAR_INVERTED) && (aOldPosition & this.kTABBAR_REGULAR)) - ) - ) - b.style.visibility = 'hidden'; - - var strip = this.tabStrip; - var placeHolder = this.tabStripPlaceHolder || strip; - var splitter = this._ensureNewSplitter(); - var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); - - // Tab Mix Plus - var scrollFrame, newTabBox, tabBarMode; - if (utils.getTreePref('compatibility.TMP')) { - scrollFrame = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-frame') || - d.getAnonymousElementByAttribute(b.mTabContainer, 'anonid', 'scroll-tabs-frame'); - newTabBox = d.getAnonymousElementByAttribute(b.mTabContainer, 'id', 'tabs-newbutton-box'); - let newTabButton = d.getElementById('new-tab-button'); - if (newTabButton && newTabButton.parentNode == b.tabContainer._container) - newTabBox = newTabButton; - tabBarMode = prefs.getPref('extensions.tabmix.tabBarMode'); - } - - // All-in-One Sidebar - var toolboxContainer = d.getAnonymousElementByAttribute(strip, 'anonid', 'aiostbx-toolbox-tableft'); - if (toolboxContainer) - toolboxContainer = toolboxContainer.parentNode; - - var scrollInnerBox = b.mTabContainer.mTabstrip._scrollbox ? - d.getAnonymousNodes(b.mTabContainer.mTabstrip._scrollbox)[0] : - scrollFrame; // Tab Mix Plus - - this.removeTabbrowserAttribute(this.kRESIZING, b); - - this.removeTabStripAttribute('width'); - b.mPanelContainer.removeAttribute('width'); - - var delayedPostProcess; - - if (pos & this.kTABBAR_VERTICAL) { - this.collapseTarget = 'top'; - this.screenPositionProp = 'screenY'; - this.offsetProp = 'offsetY'; - this.translateFunction = 'translateY'; - this.sizeProp = 'height'; - this.invertedScreenPositionProp = 'screenX'; - this.invertedSizeProp = 'width'; - this.startProp = 'top'; - this.endProp = 'bottom'; - - b.mTabBox.orient = splitter.orient = 'horizontal'; - strip.orient = - placeHolder.orient = - toggler.orient = - b.mTabContainer.orient = - b.mTabContainer.mTabstrip.orient = - b.mTabContainer.mTabstrip.parentNode.orient = 'vertical'; - b.mTabContainer.setAttribute('align', 'stretch'); // for Mac OS X - if (scrollInnerBox) - scrollInnerBox.removeAttribute('flex'); - - if (utils.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus - d.getAnonymousNodes(scrollFrame)[0].removeAttribute('flex'); - scrollFrame.parentNode.orient = - scrollFrame.orient = 'vertical'; - if (newTabBox) - newTabBox.orient = 'horizontal'; - if (tabBarMode == 2) - prefs.setPref('extensions.tabmix.tabBarMode', 1); - } - - if (toolboxContainer) - toolboxContainer.orient = 'vertical'; - - this.setTabbrowserAttribute(this.kMODE, 'vertical'); - - let width = this.maxTabbarWidth(utils.getTreePref('tabbar.width'), b); - this.setTabStripAttribute('width', width); - this.removeTabStripAttribute('height'); - b.mPanelContainer.removeAttribute('height'); - - if (strip.localName == 'toolbar') { - let nodes = strip.childNodes; - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - if (node.localName == 'tabs') - continue; - if (node.hasAttribute('flex')) - node.setAttribute('treestyletab-backup-flex', node.getAttribute('flex')); - node.removeAttribute('flex'); - } - } - - if (pos == this.kTABBAR_RIGHT) { - this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'right'); - if (utils.getTreePref('tabbar.invertTab')) { - this.setTabbrowserAttribute(this.kTAB_INVERTED, 'true'); - this.indentTarget = 'right'; - } - else { - this.removeTabbrowserAttribute(this.kTAB_INVERTED); - this.indentTarget = 'left'; - } - delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { - /* in Firefox 3, the width of the rightside tab bar - unexpectedly becomes 0 on the startup. so, we have - to set the width again. */ - aSelf.setTabStripAttribute('width', width); - aSelf.setTabStripAttribute('ordinal', 30); - aSplitter.setAttribute('ordinal', 20); - aToggler.setAttribute('ordinal', 40); - aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); - aSplitter.setAttribute('collapse', 'after'); - }; - } - else { - this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'left'); - this.removeTabbrowserAttribute(this.kTAB_INVERTED); - this.indentTarget = 'left'; - delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { - aSelf.setTabStripAttribute('ordinal', 10); - aSplitter.setAttribute('ordinal', 20); - aToggler.setAttribute('ordinal', 5); - aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); - aSplitter.setAttribute('collapse', 'before'); - }; - } - } - else { - this.collapseTarget = 'left'; - this.screenPositionProp = 'screenX'; - this.offsetProp = 'offsetX'; - this.translateFunction = 'translateX'; - this.sizeProp = 'width'; - this.invertedScreenPositionProp = 'screenY'; - this.invertedSizeProp = 'height'; - this.startProp = 'left'; - this.endProp = 'right'; - - b.mTabBox.orient = splitter.orient = 'vertical'; - strip.orient = - placeHolder.orient = - toggler.orient = - b.mTabContainer.orient = - b.mTabContainer.mTabstrip.orient = - b.mTabContainer.mTabstrip.parentNode.orient = 'horizontal'; - b.mTabContainer.removeAttribute('align'); // for Mac OS X - if (scrollInnerBox) - scrollInnerBox.setAttribute('flex', 1); - - if (utils.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus - d.getAnonymousNodes(scrollFrame)[0].setAttribute('flex', 1); - scrollFrame.parentNode.orient = - scrollFrame.orient = 'horizontal'; - if (newTabBox) - newTabBox.orient = 'vertical'; - } - - if (toolboxContainer) - toolboxContainer.orient = 'horizontal'; - - this.setTabbrowserAttribute(this.kMODE, utils.getTreePref('tabbar.multirow') ? 'multirow' : 'horizontal'); - this.removeTabbrowserAttribute(this.kTAB_INVERTED); - - if (strip.localName == 'toolbar') { - let nodes = strip.childNodes; - for (let i = 0, maxi = nodes.length; i < maxi; i++) - { - let node = nodes[i]; - if (node.localName == 'tabs') - continue; - let flex = node.hasAttribute('treestyletab-backup-flex'); - if (!flex) - continue; - node.setAttribute('flex', flex); - node.removeAttribute('treestyletab-backup-flex'); - } - } - - if (pos == this.kTABBAR_BOTTOM) { - this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'bottom'); - this.indentTarget = 'bottom'; - delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { - aSelf.setTabStripAttribute('ordinal', 30); - aSplitter.setAttribute('ordinal', 20); - aToggler.setAttribute('ordinal', 40); - aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); - }; - } - else { - this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'top'); - this.indentTarget = 'top'; - delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { - aSelf.setTabStripAttribute('ordinal', 10); - aSplitter.setAttribute('ordinal', 20); - aToggler.setAttribute('ordinal', 5); - aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); - }; - } - } - - var tabs = this.getAllTabs(b); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - tab.style.removeProperty(this.indentCSSProp); - tab.style.removeProperty(this.collapseCSSProp); - } - - this.indentProp = utils.getTreePref('indent.property'); - this.indentCSSProp = this.indentProp+'-'+this.indentTarget; - this.collapseCSSProp = 'margin-'+this.collapseTarget; - - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - this.updateTabCollapsed(tab, tab.getAttribute(this.kCOLLAPSED) == 'true', true); - } - - // for updateTabbarOverflow(), we should reset the "overflow" now. - b.mTabContainer.removeAttribute('overflow'); - let (container = this.document.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container')) { - if (container) - container.removeAttribute('overflow'); - } - - this.updateTabbarState(false); - - if (this.deferredTasks['initTabbar']) - this.deferredTasks['initTabbar'].cancel(); - - var self = this; - (this.deferredTasks['initTabbar'] = this.Deferred.next(function() { - delayedPostProcess(self, b, splitter, toggler); - self.updateTabbarOverflow(); - self.updateAllTabsButton(b); - self.updateAllTabsCount(); - delayedPostProcess = null; - self.mTabBrowser.style.visibility = ''; - - var event = d.createEvent('Events'); - event.initEvent(self.kEVENT_TYPE_TABBAR_INITIALIZED, true, false); - self.mTabBrowser.dispatchEvent(event); - - self.startRendering(); - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['initTabbar']; - }); - - pos = null; - scrollFrame = null; - newTabBox = null - tabBarMode = null; - toolboxContainer = null; - scrollInnerBox = null; - scrollInnerBox = null; - }, - - _startListenTabbarEvents : function TSTBrowser_startListenTabbarEvents() - { - var b = this.mTabBrowser; - - var tabContainer = b.mTabContainer; - tabContainer.addEventListener('TabOpen', this, true); - tabContainer.addEventListener('TabClose', this, true); - tabContainer.addEventListener('TabMove', this, true); - tabContainer.addEventListener('TabShow', this, true); - tabContainer.addEventListener('TabHide', this, true); - tabContainer.addEventListener('SSTabRestoring', this, true); - tabContainer.addEventListener('SSTabRestored', this, true); - tabContainer.addEventListener('TabPinned', this, true); - tabContainer.addEventListener('TabUnpinned', this, true); - tabContainer.addEventListener('mouseover', this, true); - tabContainer.addEventListener('mouseout', this, true); - tabContainer.addEventListener('dblclick', this, true); - tabContainer.addEventListener('select', this, true); - tabContainer.addEventListener('scroll', this, true); - - var strip = this.tabStrip; - strip.addEventListener('MozMouseHittest', this, true); // to block default behaviors of the tab bar - strip.addEventListener('mousedown', this, true); - strip.addEventListener('click', this, true); - strip.addEventListener('DOMMouseScroll', this, true); - - this.scrollBox.addEventListener('overflow', this, true); - this.scrollBox.addEventListener('underflow', this, true); - }, - - _ensureNewSplitter : function TSTBrowser__ensureNewSplitter() - { - var d = this.document; - var splitter = this.splitter; - - // We always have to re-create splitter, because its "collapse" - // behavior becomes broken by repositioning of the tab bar. - if (splitter) { - try { - splitter.removeEventListener('mousedown', this.windowService, false); - splitter.removeEventListener('mouseup', this.windowService, false); - splitter.removeEventListener('dblclick', this.windowService, false); - } - catch(e) { - } - let oldSplitter = splitter; - splitter = oldSplitter.cloneNode(true); - oldSplitter.parentNode.removeChild(oldSplitter); - } - else { - splitter = d.createElement('splitter'); - splitter.setAttribute(this.kTAB_STRIP_ELEMENT, true); - splitter.setAttribute('state', 'open'); - splitter.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 - let grippy = d.createElement('grippy') - grippy.setAttribute(this.kTAB_STRIP_ELEMENT, true); - // Workaround for https://github.com/piroor/treestyletab/issues/593 - // When you click the grippy... - // 1. The grippy changes "state" of the splitter from "collapsed" - // to "open". - // 2. The splitter changes visibility of the place holder. - // 3. BrowserUIShowHideObserver detects the change of place - // holder's visibility and triggers updateFloatingTabbar(). - // 4. updateFloatingTabbar() copies the visibility of the - // actual tab bar to the place holder. However, the tab bar - // is still collapsed. - // 5. As the result, the place holder becomes collapsed and - // the splitter disappear. - // So, we have to turn the actual tab bar visible manually - // when the grippy is clicked. - let tabContainer = this.mTabBrowser.tabContainer; - grippy.addEventListener('click', function() { - tabContainer.ownerDocument.defaultView.setTimeout(function() { - var visible = grippy.getAttribute('state') != 'collapsed'; - if (visible != tabContainer.visible) - tabContainer.visible = visible; - }, 0); - }, false); - splitter.appendChild(grippy); - } - - var splitterClass = splitter.getAttribute('class') || ''; - if (splitterClass.indexOf(this.kSPLITTER) < 0) - splitterClass += (splitterClass ? ' ' : '' ) + this.kSPLITTER; - splitter.setAttribute('class', splitterClass); - - splitter.addEventListener('mousedown', this.windowService, false); - splitter.addEventListener('mouseup', this.windowService, false); - splitter.addEventListener('dblclick', this.windowService, false); - - var ref = this.mTabBrowser.mPanelContainer; - ref.parentNode.insertBefore(splitter, ref); - - return splitter; - }, - - fireTabbarPositionEvent : function TSTBrowser_fireTabbarPositionEvent(aChanging, aOldPosition, aNewPosition) - { - if (aOldPosition == aNewPosition) - return false; - - var type = aChanging ? this.kEVENT_TYPE_TABBAR_POSITION_CHANGING : this.kEVENT_TYPE_TABBAR_POSITION_CHANGED; - var data = { - oldPosition : aOldPosition, - newPosition : aNewPosition - }; - - /* PUBLIC API */ - this.fireCustomEvent(type, this.mTabBrowser, true, false, data); - // for backward compatibility - this.fireCustomEvent(type.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); - - return true; - }, - - updateTabbarState : function TSTBrowser_updateTabbarState(aCancelable) - { - if (!this._fireTabbarStateChangingEvent() && aCancelable) - return; - - this.stopRendering(); - - var self = this; - - var w = this.window; - var d = this.document; - var b = this.mTabBrowser; - var orient; - var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop'); - var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ; - if (this.isVertical) { - orient = 'vertical'; - this.fixed = this.fixed; // ensure set to the current orient - if (toggleTabsOnTop) - toggleTabsOnTop.setAttribute('disabled', true); - } - else { - orient = 'horizontal'; - if (this.fixed) { - this.fixed = true; // ensure set to the current orient - if (!this.isMultiRow()) { - this.removeTabStripAttribute('height'); - b.mPanelContainer.removeAttribute('height'); - } - // remove ordinal for "tabs on top" https://bugzilla.mozilla.org/show_bug.cgi?id=544815 - if (this.position == 'top') { - this.removeTabStripAttribute('ordinal'); - if (TabsOnTop && !this.windowService.isPopupWindow && - this.windowService.initialized) { - let currentState = TabsOnTop.enabled; - let originalState = utils.getTreePref('tabsOnTop.originalState'); - if (originalState !== null && - currentState != originalState && - this.windowService.tabsOnTopChangingByUI && - !this.windowService.changingTabsOnTop) - utils.setTreePref('tabsOnTop.originalState', currentState); - // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=555987 - // This should be done when the value of the "ordinal" attribute - // is modified dynamically. So, we don' have to do it before - // the browser window is completely initialized. - TabsOnTop.enabled = !currentState; - if (this.deferredTasks['updateTabbarState_TabsOnTop']) - this.deferredTasks['updateTabbarState_TabsOnTop'].cancel(); - (this.deferredTasks['updateTabbarState_TabsOnTop'] = this.Deferred.next(function() { - TabsOnTop.enabled = currentState; - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['updateTabbarState_TabsOnTop']; - }); - } - } - } - else { - this.fixed = false; // ensure set to the current orient - this.setTabStripAttribute('height', this.maxTabbarHeight(utils.getTreePref('tabbar.height'), b)); - } - if (toggleTabsOnTop) { - if (this.position == 'top') - toggleTabsOnTop.removeAttribute('disabled'); - else - toggleTabsOnTop.setAttribute('disabled', true); - } - } - - if (TabsOnTop && !this.windowService.isPopupWindow) { - let updateTabsOnTop = function() { - self.windowService.updateTabsOnTop(); - }; - // TabsOnTop.enabled is always "false" before the browser window is - // completely initialized. So, we have to check it with delay only - // on the Startup. - if (this.initialized) - updateTabsOnTop(); - else - this.Deferred.next(updateTabsOnTop); - } - - if (this.deferredTasks['updateTabbarState']) - this.deferredTasks['updateTabbarState'].cancel(); - (this.deferredTasks['updateTabbarState'] = this.Deferred.next(function() { - self.updateFloatingTabbar(self.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); - self._fireTabbarStateChangedEvent(); - self.startRendering(); - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['updateTabbarState']; - }); - - var allowToCollapse = utils.getTreePref('allowSubtreeCollapseExpand.'+orient); - if (this.allowSubtreeCollapseExpand != allowToCollapse) - this.collapseExpandAllSubtree(false, false); - this.allowSubtreeCollapseExpand = allowToCollapse; - - this.maxTreeLevel = utils.getTreePref('maxTreeLevel.'+orient); - - this.setTabbrowserAttribute(this.kALLOW_STACK, this.canStackTabs ? 'true' : null); - this.updateTabsZIndex(this.canStackTabs); - - if (this.maxTreeLevelPhisical) - this.promoteTooDeepLevelTabs(); - - this.updateAllTabsIndent(); - }, - - _fireTabbarStateChangingEvent : function TSTBrowser_fireTabbarStateChangingEvent() - { - var b = this.mTabBrowser; - var orient = this.isVertical ? 'vertical' : 'horizontal' ; - var oldState = { - fixed : this.fixed, - maxTreeLevel : this.maxTreeLevel, - indented : this.maxTreeLevel != 0, - canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' - }; - var newState = { - fixed : utils.getTreePref('tabbar.fixed.'+orient), - maxTreeLevel : utils.getTreePref('maxTreeLevel.'+orient), - indented : utils.getTreePref('maxTreeLevel.'+orient) != 0, - canCollapse : utils.getTreePref('allowSubtreeCollapseExpand.'+orient) - }; - - if (oldState.fixed == newState.fixed && - oldState.maxTreeLevel == newState.maxTreeLevel && - oldState.indented == newState.indented && - oldState.canCollapse == newState.canCollapse) - return false; - - var data = { - oldState : oldState, - newState : newState - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING, this.mTabBrowser, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); - - return true; - }, - - _fireTabbarStateChangedEvent : function TSTBrowser_fireTabbarStateChangedEvent() - { - var b = this.mTabBrowser; - var state = { - fixed : this.fixed, - maxTreeLevel : this.maxTreeLevel, - indented : this.maxTreeLevel != 0, - canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' - }; - - var data = { - state : state - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this.mTabBrowser, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); - - return true; - }, - - updateFloatingTabbar : function TSTBrowser_updateFloatingTabbar(aReason) - { - var w = this.window; - if (this._updateFloatingTabbarTimer) { - w.clearTimeout(this._updateFloatingTabbarTimer); - this._updateFloatingTabbarTimer = null; - } - - this._updateFloatingTabbarReason |= aReason; - - if (this._updateFloatingTabbarReason & this.kTABBAR_UPDATE_NOW) { - this._updateFloatingTabbarInternal(this._updateFloatingTabbarReason); - this._updateFloatingTabbarReason = 0; - } - else { - this._updateFloatingTabbarTimer = w.setTimeout(function(aSelf) { - aSelf._updateFloatingTabbarTimer = null; - aSelf._updateFloatingTabbarInternal(aSelf._updateFloatingTabbarReason) - aSelf._updateFloatingTabbarReason = 0; - }, 0, this); - } - }, - - _updateFloatingTabbarInternal : function TSTBrowser_updateFloatingTabbarInternal(aReason) - { - aReason = aReason || this.kTABBAR_UPDATE_BY_UNKNOWN_REASON; - - if (DEBUG) { - let humanReadableReason = - (aReason & this.kTABBAR_UPDATE_BY_RESET ? 'reset ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_PREF_CHANGE ? 'prefchange ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE ? 'appearance-change ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR ? 'showhide ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE ? 'tabbar-resize ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_WINDOW_RESIZE ? 'window-resize ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_FULLSCREEN ? 'fullscreen ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE ? 'autohide ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_INITIALIZE ? 'initialize ' : '' ) + - (aReason & this.kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR ? 'toggle-sidebar ' : '' ); - dump('TSTBrowser_updateFloatingTabbarInternal: ' + humanReadableReason + '\n'); - } - - var d = this.document; - - // When the tab bar is invisible even if the tab bar is resizing, then - // now I'm trying to expand the tab bar from collapsed state. - // Then the tab bar must be shown. - if (aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE && - !this.browser.tabContainer.visible) - this.browser.tabContainer.visible = true; - - var splitter = this.splitter; - if (splitter.collapsed || splitter.getAttribute('state') != 'collapsed') { - // Synchronize visibility of the tab bar to the placeholder, - // because the tab bar can be shown/hidden by someone - // (Tab Mix Plus, Pale Moon, or some addons). - this._tabStripPlaceHolder.collapsed = - splitter.collapsed = - !this.browser.tabContainer.visible; - } - - var strip = this.tabStrip; - var collapsed = splitter.collapsed ? - strip.collapsed : - splitter.getAttribute('state') == 'collapsed' ; - var stripStyle = strip.style; - var tabContainerBox = this.getTabContainerBox(this.mTabBrowser); - var statusPanel = d.getElementById('statusbar-display'); - var statusPanelStyle = statusPanel ? statusPanel.style : null ; - var pos = this.position; - if (pos != 'top' || - this.mTabBrowser.getAttribute(this.kFIXED) != 'true') { - strip.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 - - if ( - this.autoHide.enabled && - this.autoHide.expanded && - (aReason & this.kTABBAR_UPDATE_SYNC_TO_PLACEHOLDER) && - this.autoHide.mode == this.autoHide.kMODE_SHRINK - ) - this.autoHide.hide(this.autoHide.kSHOWN_BY_ANY_REASON); - - let box = this._tabStripPlaceHolder.boxObject; - let root = d.documentElement.boxObject; - let realSize = this.getTabbarPlaceholderSize(); - - let width = (this.autoHide.expanded && this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? - this.maxTabbarWidth(utils.getTreePref('tabbar.width')) : - 0 - ) || realSize.width; - let height = (this.autoHide.expanded && !this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? - this.maxTabbarHeight(utils.getTreePref('tabbar.height')) : - 0 - ) || realSize.height; - let yOffset = pos == 'bottom' ? height - realSize.height : 0 ; - - stripStyle.top = (box.screenY - root.screenY + root.y - yOffset)+'px'; - stripStyle.left = pos == 'right' ? '' : - (box.screenX - root.screenX + root.x)+'px'; - stripStyle.right = pos != 'right' ? '' : - ((root.screenX + root.width) - (box.screenX + box.width))+'px'; - - stripStyle.width = (strip.width = tabContainerBox.width = width)+'px'; - stripStyle.height = (strip.height = tabContainerBox.height = height)+'px'; - - this._updateFloatingTabbarResizer({ - width : width, - realWidth : realSize.width, - height : height, - realHeight : realSize.height - }); - - this._lastTabbarPlaceholderSize = realSize; - - strip.collapsed = tabContainerBox.collapsed = collapsed; - - if (statusPanel && utils.getTreePref('repositionStatusPanel')) { - let offsetParentBox = this.base.findOffsetParent(statusPanel).boxObject; - let contentBox = this.mTabBrowser.mPanelContainer.boxObject; - let chromeMargins = (d.documentElement.getAttribute('chromemargin') || '0,0,0,0').split(','); - chromeMargins = chromeMargins.map(function(aMargin) { return parseInt(aMargin); }); - statusPanelStyle.marginTop = (pos == 'bottom') ? - '-moz-calc(0px - ' + (offsetParentBox.height - contentBox.height + chromeMargins[2]) + 'px - 3em)' : - '' ; - statusPanelStyle.marginLeft = (contentBox.screenX - offsetParentBox.screenX + chromeMargins[3])+'px'; - statusPanelStyle.marginRight = ((offsetParentBox.screenX + offsetParentBox.width) - (contentBox.screenX + contentBox.width) + chromeMargins[1])+'px'; - statusPanelStyle.maxWidth = this.isVertical ? - (contentBox.width-5)+'px' : // emulate the margin defined on https://bugzilla.mozilla.org/show_bug.cgi?id=632634 - '' ; - statusPanel.__treestyletab__repositioned = true; - } - - this.mTabBrowser.tabContainer.setAttribute('context', this.mTabBrowser.tabContextMenu.id); - } - else { - strip.collapsed = tabContainerBox.collapsed = collapsed; - stripStyle.top = stripStyle.left = stripStyle.right = stripStyle.width = stripStyle.height = ''; - - if ( - statusPanel && - ( - utils.getTreePref('repositionStatusPanel') || - statusPanel.__treestyletab__repositioned - ) - ) { - statusPanelStyle.marginTop = statusPanelStyle.marginLeft = - statusPanelStyle.marginRight = statusPanelStyle.maxWidth = ''; - statusPanel.__treestyletab__repositioned = false; - } - - strip.removeAttribute('layer'); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 - - this.mTabBrowser.tabContainer.removeAttribute('context'); - } - - if (tabContainerBox.boxObject.width) - this.positionPinnedTabs(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); - else - this.positionPinnedTabsWithDelay(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); - - if (!collapsed && aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE) { - let self = this; - this.Deferred.next(function() { - self.scrollToTab(self.browser.selectedTab); - }); - } - }, - getTabbarPlaceholderSize: function TSTBrowser_getTabbarPlaceholderSize() - { - var box = this._tabStripPlaceHolder.boxObject; - return { - width: parseInt(this._tabStripPlaceHolder.getAttribute('width') || box.width), - height: parseInt(this._tabStripPlaceHolder.getAttribute('height') || box.height) - }; - }, - getExistingTabsCount : function TSTBrowser_getTabsCount() - { - return this.getAllTabs(this.mTabBrowser).length - this.mTabBrowser._removingTabs.length; - }, - - _updateFloatingTabbarResizer : function TSTBrowser_updateFloatingTabbarResizer(aSize) - { - var d = this.document; - - var width = aSize.width; - var realWidth = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realWidth ; - var height = aSize.height; - var realHeight = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realHeight ; - var pos = this.position; - var vertical = this.isVertical; - - var splitter = d.getElementById('treestyletab-tabbar-resizer-splitter'); - if (!splitter) { - let box = d.createElement('box'); - box.setAttribute('id', 'treestyletab-tabbar-resizer-box'); - box.setAttribute(this.kTAB_STRIP_ELEMENT, true); - splitter = d.createElement('splitter'); - splitter.setAttribute(this.kTAB_STRIP_ELEMENT, true); - splitter.setAttribute('id', 'treestyletab-tabbar-resizer-splitter'); - splitter.setAttribute('class', this.kSPLITTER); - splitter.setAttribute('onmousedown', 'TreeStyleTabService.handleEvent(event);'); - splitter.setAttribute('onmouseup', 'TreeStyleTabService.handleEvent(event);'); - splitter.setAttribute('ondblclick', 'TreeStyleTabService.handleEvent(event);'); - box.appendChild(splitter); - this.tabStrip.appendChild(box); - } - - var box = splitter.parentNode; - - box.orient = splitter.orient = vertical ? 'horizontal' : 'vertical' ; - box.width = (width - realWidth) || width; - box.height = (height - realHeight) || height; - - var boxStyle = box.style; - boxStyle.top = pos == 'top' ? realHeight+'px' : '' ; - boxStyle.right = pos == 'right' ? realWidth+'px' : '' ; - boxStyle.left = pos == 'left' ? realWidth+'px' : '' ; - boxStyle.bottom = pos == 'bottom' ? realHeight+'px' : '' ; - - if (vertical) { - splitter.removeAttribute('width'); - splitter.setAttribute('height', height); - } - else { - splitter.setAttribute('width', width); - splitter.removeAttribute('height'); - } - - var splitterWidth = splitter.boxObject.width; - var splitterHeight = splitter.boxObject.height; - var splitterStyle = splitter.style; - splitterStyle.marginTop = pos == 'bottom' ? (-splitterHeight)+'px' : - vertical ? '0' : - box.height+'px' ; - splitterStyle.marginRight = pos == 'left' ? (-splitterWidth)+'px' : - !vertical ? '0' : - box.width+'px' ; - splitterStyle.marginLeft = pos == 'right' ? (-splitterWidth)+'px' : - !vertical ? '0' : - box.width+'px' ; - splitterStyle.marginBottom = pos == 'top' ? (-splitterHeight)+'px' : - vertical ? '0' : - box.height+'px' ; - }, - - updateTabbarOverflow : function TSTBrowser_updateTabbarOverflow() - { - var d = this.document; - var b = this.mTabBrowser; - b.mTabContainer.removeAttribute('overflow'); - var container = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container') || b.mTabContainer; - - if (container != b.mTabContainer) - container.removeAttribute('overflow'); - - var scrollBox = this.scrollBox; - scrollBox = d.getAnonymousElementByAttribute(scrollBox, 'anonid', 'scrollbox'); - if (scrollBox) - scrollBox = d.getAnonymousNodes(scrollBox)[0]; - if ( - scrollBox && - ( - scrollBox.boxObject.width > container.boxObject.width || - scrollBox.boxObject.height > container.boxObject.height - ) - ) { - b.mTabContainer.setAttribute('overflow', true); - if (container != b.mTabContainer) - container.setAttribute('overflow', true); - } - else { - b.mTabContainer.removeAttribute('overflow'); - if (container != b.mTabContainer) - container.removeAttribute('overflow'); - } - }, - - reinitAllTabs : function TSTBrowser_reinitAllTabs(aSouldUpdateCount) - { - var tabs = this.getAllTabs(this.mTabBrowser); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - this.initTabAttributes(tab); - this.initTabContents(tab); - if (aSouldUpdateCount) - this.updateTabsCount(tab); - } - }, - - destroy : function TSTBrowser_destroy() - { - this.animationManager.removeTask(this.smoothScrollTask); - - Object.keys(this.deferredTasks).forEach(function(key) { - if (this.deferredTasks[key].cancel) { - this.deferredTasks[key].cancel(); - delete this.deferredTasks[key]; - } - }, this); - - this.autoHide.destroy(); - delete this._autoHide; - - this._initDNDObservers(); // ensure initialized - this.tabbarDNDObserver.destroy(); - delete this._tabbarDNDObserver; - this.panelDNDObserver.destroy(); - delete this._panelDNDObserver; - - if (this.tooltipManager) { - this.tooltipManager.destroy(); - delete this.tooltipManager; - } - - if (this.tabStripPlaceHolderBoxObserver) { - this.tabStripPlaceHolderBoxObserver.destroy(); - delete this.tabStripPlaceHolderBoxObserver; - } - - var w = this.window; - var d = this.document; - var b = this.mTabBrowser; - delete b.tabContainer.treeStyleTab; - - var tabs = this.getAllTabs(b); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - this.stopTabIndentAnimation(tab); - this.stopTabCollapseAnimation(tab); - this.destroyTab(tab); - } - - this._endListenTabbarEvents(); - - w.removeEventListener('resize', this, true); - w.removeEventListener('beforecustomization', this, true); - w.removeEventListener('aftercustomization', this, false); - w.removeEventListener('customizationchange', this, false); - w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); - w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); - w.removeEventListener('tabviewframeinitialized', this, false); - w.removeEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); - w.removeEventListener('SSWindowStateBusy', this, false); - - b.removeEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); - - w['piro.sakura.ne.jp'].tabsDragUtils.destroyTabBrowser(b); - - var tabContextMenu = b.tabContextMenu || - d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); - tabContextMenu.removeEventListener('popupshowing', this, false); - - if (this.tabbarCanvas) { - this.tabbarCanvas.parentNode.removeChild(this.tabbarCanvas); - this.tabbarCanvas = null; - } - - Services.obs.removeObserver(this, this.kTOPIC_INDENT_MODIFIED); - Services.obs.removeObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL); - Services.obs.removeObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY); - Services.obs.removeObserver(this, 'lightweight-theme-styling-update'); - prefs.removePrefListener(this); - - delete this.windowService; - delete this.window; - delete this.document; - delete this.mTabBrowser.treeStyleTab; - delete this.mTabBrowser; - }, - - destroyTab : function TSTBrowser_destroyTab(aTab) - { - var id = aTab.getAttribute(this.kID); - if (id in this.tabsHash && - aTab == this.tabsHash[id]) - delete this.tabsHash[id]; - - if (aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) { - this.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - this.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave; - } - - delete aTab.__treestyletab__linkedTabBrowser; - }, - - _endListenTabbarEvents : function TSTBrowser_endListenTabbarEvents() - { - var b = this.mTabBrowser; - - var tabContainer = b.mTabContainer; - tabContainer.removeEventListener('TabOpen', this, true); - tabContainer.removeEventListener('TabClose', this, true); - tabContainer.removeEventListener('TabMove', this, true); - tabContainer.removeEventListener('TabShow', this, true); - tabContainer.removeEventListener('TabHide', this, true); - tabContainer.removeEventListener('SSTabRestoring', this, true); - tabContainer.removeEventListener('SSTabRestored', this, true); - tabContainer.removeEventListener('TabPinned', this, true); - tabContainer.removeEventListener('TabUnpinned', this, true); - tabContainer.removeEventListener('mouseover', this, true); - tabContainer.removeEventListener('mouseout', this, true); - tabContainer.removeEventListener('dblclick', this, true); - tabContainer.removeEventListener('select', this, true); - tabContainer.removeEventListener('scroll', this, true); - - var strip = this.tabStrip; - strip.removeEventListener('MozMouseHittest', this, true); - strip.removeEventListener('mousedown', this, true); - strip.removeEventListener('click', this, true); - strip.removeEventListener('DOMMouseScroll', this, true); - - this.scrollBox.removeEventListener('overflow', this, true); - this.scrollBox.removeEventListener('underflow', this, true); - }, - - saveCurrentState : function TSTBrowser_saveCurrentState() - { - this.autoHide.saveCurrentState(); - - var b = this.mTabBrowser; - var floatingBox = this.getTabStrip(b).boxObject; - var fixedBox = (this.tabStripPlaceHolder || this.getTabStrip(b)).boxObject; - var prefs = { - 'tabbar.fixed.horizontal' : b.getAttribute(this.kFIXED+'-horizontal') == 'true', - 'tabbar.fixed.vertical' : b.getAttribute(this.kFIXED+'-vertical') == 'true', - 'tabbar.width' : this.isVertical && this.autoHide.expanded && floatingBox.width ? floatingBox.width : void(0), - 'tabbar.shrunkenWidth' : this.isVertical && !this.autoHide.expanded && fixedBox.width ? fixedBox.width : void(0), - 'tabbar.height' : !this.isVertical && this.autoHide.expanded && floatingBox.height ? floatingBox.height : void(0) - }; - for (var i in prefs) - { - if (prefs[i] !== void(0) && utils.getTreePref(i) != prefs[i]) - utils.setTreePref(i, prefs[i]); - } - this.position = this.position; - }, - -/* toolbar customization */ - - syncDestroyTabbar : function TSTBrowser_syncDestroyTabbar() - { - this.stopRendering(); - - this._lastTreeViewEnabledBeforeDestroyed = this.treeViewEnabled; - this.treeViewEnabled = false; - this.maxTreeLevel = 0; - - this._lastTabbarPositionBeforeDestroyed = this.position; - if (this.position != 'top') { - let self = this; - this.doAndWaitDOMEvent( - this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, - this.window, - 100, - function() { - self.position = 'top'; - } - ); - } - - this.fixed = true; - - var tabbar = this.mTabBrowser.tabContainer; - tabbar.removeAttribute('width'); - tabbar.removeAttribute('height'); - tabbar.removeAttribute('ordinal'); - - this.removeTabStripAttribute('width'); - this.removeTabStripAttribute('height'); - this.removeTabStripAttribute('ordinal'); - this.removeTabStripAttribute('orient'); - - var toolbar = this.ownerToolbar; - this.destroyTabStrip(toolbar); - toolbar.classList.add(this.kTABBAR_TOOLBAR_READY); - - this._endListenTabbarEvents(); - - this.tabbarDNDObserver.endListenEvents(); - - this.window.setTimeout(function(aSelf) { - aSelf.updateCustomizedTabsToolbar(); - }, 100, this); - - this.startRendering(); - }, - destroyTabStrip : function TSTBrowser_destroyTabStrip(aTabStrip) - { - aTabStrip.classList.remove(this.kTABBAR_TOOLBAR); - aTabStrip.style.top = aTabStrip.style.left = aTabStrip.style.width = aTabStrip.style.height = ''; - aTabStrip.removeAttribute('height'); - aTabStrip.removeAttribute('width'); - aTabStrip.removeAttribute('ordinal'); - aTabStrip.removeAttribute('orient'); - }, - - syncReinitTabbar : function TSTBrowser_syncReinitTabbar() - { - this.stopRendering(); - - this.ownerToolbar.classList.add(this.kTABBAR_TOOLBAR); - this.ownerToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); - Array.slice(this.document.querySelectorAll('.'+this.kTABBAR_TOOLBAR_READY_POPUP)) - .forEach(this.safeRemovePopup, this); - - var position = this._lastTabbarPositionBeforeDestroyed || this.position; - delete this._lastTabbarPositionBeforeDestroyed; - - var self = this; - this.doAndWaitDOMEvent( - this.kEVENT_TYPE_TABBAR_INITIALIZED, - this.window, - 100, - function() { - self.initTabbar(position, 'top'); - } - ); - this.reinitAllTabs(true); - - this.tabbarDNDObserver.startListenEvents(); - - this.treeViewEnabled = this._lastTreeViewEnabledBeforeDestroyed; - delete this._lastTreeViewEnabledBeforeDestroyed; - - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET); - - this.startRendering(); - }, - - updateCustomizedTabsToolbar : function TSTBrowser_updateCustomizedTabsToolbar() - { - var d = this.document; - - var newToolbar = this.ownerToolbar; - newToolbar.classList.add(this.kTABBAR_TOOLBAR_READY); - - var oldToolbar = d.querySelector('.'+this.kTABBAR_TOOLBAR_READY); - if (oldToolbar == newToolbar) - return; - - if (oldToolbar && oldToolbar != newToolbar) { - this.safeRemovePopup(d.getElementById(oldToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP)); - oldToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); - } - - var id = newToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP; - var panel = d.getElementById(id); - if (!panel) { - panel = d.createElement('panel'); - panel.setAttribute('id', id); - panel.setAttribute('class', this.kTABBAR_TOOLBAR_READY_POPUP); - panel.setAttribute('noautohide', true); - panel.setAttribute('onmouseover', 'this.hidePopup()'); - panel.setAttribute('ondragover', 'this.hidePopup()'); - panel.appendChild(d.createElement('label')); - let position = this._lastTabbarPositionBeforeDestroyed || this.position; - let label = utils.treeBundle.getString('toolbarCustomizing_tabbar_'+(position == 'left' || position == 'right' ? 'vertical' : 'horizontal' )); - panel.firstChild.appendChild(d.createTextNode(label)); - d.getElementById('mainPopupSet').appendChild(panel); - } - panel.openPopup(newToolbar, 'end_after', 0, 0, false, false); - }, - safeRemovePopup : function TSTBrowser_safeRemovePopup(aPopup) - { - if (!aPopup) - return; - if (aPopup.state == 'open') { - aPopup.addEventListener('popuphidden', function onPopuphidden(aEvent) { - aPopup.removeEventListener(aEvent.type, onPopuphidden, false); - aPopup.parentNode.removeChild(aPopup); - }, false); - aPopup.hidePopup(); - } - else { - aPopup.parentNode.removeChild(aPopup); - } - }, - -/* nsIObserver */ - - domains : [ - 'extensions.treestyletab.', - 'browser.tabs.closeButtons', - 'browser.tabs.closeWindowWithLastTab', - 'browser.tabs.autoHide', - 'browser.tabs.animate' - ], - - observe : function TSTBrowser_observe(aSubject, aTopic, aData) - { - switch (aTopic) - { - case this.kTOPIC_INDENT_MODIFIED: - if (this.indent > -1) - this.updateAllTabsIndent(); - return; - - case this.kTOPIC_COLLAPSE_EXPAND_ALL: - if (!aSubject || aSubject == this.window) { - aData = String(aData); - this.collapseExpandAllSubtree( - aData.indexOf('collapse') > -1, - aData.indexOf('now') > -1 - ); - } - return; - - case 'lightweight-theme-styling-update': - return this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); - - case this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY: - return this.treeViewEnabled = (aData != 'false'); - - case 'nsPref:changed': - return this.onPrefChange(aData); - - default: - return; - } - }, - - onPrefChange : function TSTBrowser_onPrefChange(aPrefName) - { - // ignore after destruction - if (!this.window || !this.window.TreeStyleTabService) - return; - - var b = this.mTabBrowser; - var value = prefs.getPref(aPrefName); - var tabContainer = b.mTabContainer; - var tabs = this.getAllTabs(b); - switch (aPrefName) - { - case 'extensions.treestyletab.tabbar.position': - if (this.shouldApplyNewPref) - this.position = value; - return; - - case 'extensions.treestyletab.tabbar.invertTab': - case 'extensions.treestyletab.tabbar.multirow': - this.initTabbar(); - this.updateAllTabsIndent(); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.initTabContents(tabs[i]); - } - return; - case 'extensions.treestyletab.tabbar.invertTabContents': - this.setTabbrowserAttribute(this.kTAB_CONTENTS_INVERTED, value); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.initTabContents(tabs[i]); - } - return; - - case 'extensions.treestyletab.tabbar.invertClosebox': - this.setTabbrowserAttribute(this.kCLOSEBOX_INVERTED, value); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.initTabContents(tabs[i]); - } - return; - - case 'extensions.treestyletab.tabbar.style': - case 'extensions.treestyletab.tabbar.style.aero': - this.setTabbarStyle(utils.getTreePref('tabbar.style')); - value = utils.getTreePref('twisty.style'); - if (value != 'auto') - return; - case 'extensions.treestyletab.twisty.style': - return this.setTwistyStyle(value); - - case 'extensions.treestyletab.showBorderForFirstTab': - return this.setTabbrowserAttribute(this.kFIRSTTAB_BORDER, value); - - case 'extensions.treestyletab.tabbar.fixed.horizontal': - if (!this.shouldApplyNewPref) - return; - this.setTabbrowserAttribute(this.kFIXED+'-horizontal', value ? 'true' : null, b); - case 'extensions.treestyletab.maxTreeLevel.horizontal': - case 'extensions.treestyletab.allowSubtreeCollapseExpand.horizontal': - if (!this.isVertical) - this.updateTabbarState(true); - return; - - case 'extensions.treestyletab.tabbar.fixed.vertical': - if (!this.shouldApplyNewPref) - return; - this.setTabbrowserAttribute(this.kFIXED+'-vertical', value ? 'true' : null, b); - case 'extensions.treestyletab.maxTreeLevel.vertical': - case 'extensions.treestyletab.allowSubtreeCollapseExpand.vertical': - if (this.isVertical) - this.updateTabbarState(true); - return; - - case 'extensions.treestyletab.tabbar.width': - case 'extensions.treestyletab.tabbar.shrunkenWidth': - if (!this.shouldApplyNewPref) - return; - if (!this.autoHide.isResizing && this.isVertical) { - this.removeTabStripAttribute('width'); - this.setTabStripAttribute('width', this.autoHide.placeHolderWidthFromMode); - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); - } - this.checkTabsIndentOverflow(); - return; - - case 'extensions.treestyletab.tabbar.height': - if (!this.shouldApplyNewPref) - return; - this._horizontalTabMaxIndentBase = 0; - this.checkTabsIndentOverflow(); - return; - - case 'extensions.treestyletab.tabbar.autoShow.mousemove': - let (toggler = this.document.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER)) { - if (toggler) { - if (value) - toggler.removeAttribute('hidden'); - else - toggler.setAttribute('hidden', true); - } - } - return; - - case 'extensions.treestyletab.tabbar.invertScrollbar': - this.setTabbrowserAttribute(this.kINVERT_SCROLLBAR, value); - this.positionPinnedTabs(); - return; - - case 'extensions.treestyletab.tabbar.narrowScrollbar': - return this.setTabbrowserAttribute(this.kNARROW_SCROLLBAR, value); - - case 'extensions.treestyletab.maxTreeLevel.phisical': - if (this.maxTreeLevelPhisical = value) - this.promoteTooDeepLevelTabs(); - return; - - case 'browser.tabs.animate': - this.setTabbrowserAttribute(this.kANIMATION_ENABLED, - prefs.getPref('browser.tabs.animate') !== false - ? 'true' : null - ); - return; - - case 'browser.tabs.closeButtons': - case 'browser.tabs.closeWindowWithLastTab': - return this.updateInvertedTabContentsOrder(true); - - case 'browser.tabs.autoHide': - if (this.getTabs(this.mTabBrowser).length == 1) - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); - return; - - case 'extensions.treestyletab.tabbar.autoHide.mode': - case 'extensions.treestyletab.tabbar.autoHide.mode.fullscreen': - return this.autoHide; // ensure initialized - - case 'extensions.treestyletab.pinnedTab.faviconized': - return this.positionPinnedTabsWithDelay(); - - case 'extensions.treestyletab.counter.role.horizontal': - if (!this.isVertical) { - let self = this; - if (this.deferredTasks[aPrefName]) - this.deferredTasks[aPrefName].cancel(); - (this.deferredTasks[aPrefName] = this.Deferred - .next(function() { self.updateAllTabsCount(); })) - .error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[aPrefName]; - }); - } - return; - case 'extensions.treestyletab.counter.role.vertical': - if (this.isVertical) { - let self = this; - if (this.deferredTasks[aPrefName]) - this.deferredTasks[aPrefName].cancel(); - (this.deferredTasks[aPrefName] = this.Deferred - .next(function() { self.updateAllTabsCount(); })) - .error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[aPrefName]; - }); - } - return; - - default: - return; - } - }, - setTabbarStyle : function TSTBrowser_setTabbarStyle(aStyle) - { - if (/^(default|plain|flat|mixed|vertigo|metal|sidebar)(-aero)?$/.test(aStyle)) - aStyle = aStyle.toLowerCase(); - - if (aStyle.indexOf('default') == 0) { // old name (for compatibility) - utils.setTreePref('tabbar.style', aStyle = aStyle.replace('default', 'plain')); - } - - if (aStyle) { - let additionalValues = []; - if (/^(plain|flat|mixed|vertigo)$/.test(aStyle)) - additionalValues.push('square'); - if (/^(plain|flat|mixed)$/.test(aStyle)) - additionalValues.push('border'); - if (/^(flat|mixed)$/.test(aStyle)) - additionalValues.push('color'); - if (/^(plain|mixed)$/.test(aStyle)) - additionalValues.push('shadow'); - if (utils.getTreePref('tabbar.style.aero')) - additionalValues.push('aero'); - if (additionalValues.length) - aStyle = additionalValues.join(' ')+' '+aStyle; - - this.setTabbrowserAttribute(this.kSTYLE, aStyle); - } - else { - this.removeTabbrowserAttribute(this.kSTYLE); - } - }, - setTwistyStyle : function TSTBrowser_setTwistyStyle(aStyle) - { - if (aStyle != 'auto') { - this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); - return; - } - - aStyle = 'modern-black'; - - if (utils.getTreePref('tabbar.style') == 'sidebar') { - aStyle = 'osx'; - } - else if ( - prefs.getPref('extensions.informationaltab.thumbnail.enabled') && - prefs.getPref('extensions.informationaltab.thumbnail.position') < 100 - ) { - let self = this; - this.extensions.isAvailable('informationaltab@piro.sakura.ne.jp', { - ok : function() { - aStyle = 'retro'; - self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); - }, - ng : function() { - self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); - } - }); - return; - } - - this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); - }, - -/* DOM Event Handling */ - - handleEvent : function TSTBrowser_handleEvent(aEvent) - { - switch (aEvent.type) - { - case 'TabOpen': - return this.onTabOpen(aEvent); - - case 'TabClose': - return this.onTabClose(aEvent); - - case 'TabMove': - return this.onTabMove(aEvent); - - case 'TabShow': - case 'TabHide': - return this.onTabVisibilityChanged(aEvent); - - case 'SSTabRestoring': - return this.onTabRestoring(aEvent); - - case 'SSTabRestored': - return this.onTabRestored(aEvent); - - case 'TabPinned': - return this.onTabPinned(aEvent.originalTarget); - - case 'TabUnpinned': - return this.onTabUnpinned(aEvent.originalTarget); - - case 'select': - return this.onTabSelect(aEvent); - - case 'click': - return this.onClick(aEvent); - - case 'dblclick': - return this.onDblClick(aEvent); - - case 'MozMouseHittest': // to block default behaviors of the tab bar - return this.onMozMouseHittest(aEvent); - - case 'mousedown': - return this.onMouseDown(aEvent); - - case 'DOMMouseScroll': - return this.onDOMMouseScroll(aEvent); - - case 'scroll': - return this.onScroll(aEvent); - - case 'popupshowing': - return this.onPopupShowing(aEvent); - - case 'popuphiding': - return this.onPopupHiding(aEvent); - - case 'mouseover': - if (!this.tooltipManager) - this._initTooltipManager(); - if (!this._DNDObserversInitialized) - this._initDNDObservers(); - let (tab = aEvent.target) { - if (tab.__treestyletab__twistyHoverTimer) - this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer); - if (this.isEventFiredOnTwisty(aEvent)) { - tab.setAttribute(this.kTWISTY_HOVER, true); - tab.__treestyletab__twistyHoverTimer = this.window.setTimeout(function(aSelf) { - tab.setAttribute(aSelf.kTWISTY_HOVER, true); - delete tab.__treestyletab__twistyHoverTimer; - }, 0, this); - } - } - return; - - case 'mouseout': - let (tab = aEvent.target) { - if (tab.__treestyletab__twistyHoverTimer) { - this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer); - delete tab.__treestyletab__twistyHoverTimer; - } - tab.removeAttribute(this.kTWISTY_HOVER); - } - return; - - case 'dragover': - if (!this.tooltipManager) - this._initTooltipManager(); - if (!this._DNDObserversInitialized) - this._initDNDObservers(); - return; - - case 'overflow': - case 'underflow': - return this.onTabbarOverflow(aEvent); - - case 'resize': - return this.onResize(aEvent); - - - // toolbar customizing - case 'beforecustomization': - this.toolbarCustomizing = true; - return this.syncDestroyTabbar(); - case 'aftercustomization': - // Ignore it, because 'aftercustomization' fired not - // following to 'beforecustomization' is invalid. - // Personal Titlebar addon (or others) fires a fake - // event on its startup process. - if (!this.toolbarCustomizing) - return; - this.toolbarCustomizing = false; - return this.syncReinitTabbar(); - case 'customizationchange': - return this.updateCustomizedTabsToolbar(); - - case 'tabviewframeinitialized': - return this.lastTabViewGroup = this.getTabViewGroupId(); - - - case this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED: - return this.onTreeStyleTabPrintPreviewEntered(aEvent); - case this.kEVENT_TYPE_PRINT_PREVIEW_EXITED: - return this.onTreeStyleTabPrintPreviewExited(aEvent); - - - case this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END: - return this.cancelDelayedExpandOnTabSelect(); - - - case 'SSWindowStateBusy': - return this.needRestoreTree = true; - - - case 'nsDOMMultipleTabHandlerTabsClosing': - if (!this.onTabsClosing(aEvent)) - aEvent.preventDefault(); - return; - } - }, - lastScrollX : -1, - lastScrollY : -1, - - restoreLastScrollPosition : function TSTBrowser_restoreLastScrollPosition() - { - if (this.lastScrollX < 0 || this.lastScrollY < 0 || !this.isVisible) - return; - var lastX = this.lastScrollX; - var lastY = this.lastScrollY; - this.clearLastScrollPosition(); - if (!this.smoothScrollTask && - !this.scrollBox._smoothScrollTimer) { // don't restore scroll position if another scroll is already running. - let x = {}, y = {}; - let scrollBoxObject = this.scrollBoxObject; - scrollBoxObject.getPosition(x, y); - if (x.value != lastX || y.value != lastY) - scrollBoxObject.scrollTo(lastX, lastY); - } - }, - - clearLastScrollPosition : function TSTBrowser_clearLastScrollPosition() - { - this.lastScrollX = this.lastScrollY = -1; - }, - - updateLastScrollPosition : function TSTBrowser_updateLastScrollPosition() - { - if (!this.isVertical || !this.isVisible) - return; - var x = {}, y = {}; - var scrollBoxObject = this.scrollBoxObject; - if (!scrollBoxObject) - return; - scrollBoxObject.getPosition(x, y); - this.lastScrollX = x.value; - this.lastScrollY = y.value; - }, - - cancelPerformingAutoScroll : function TSTBrowser_cancelPerformingAutoScroll(aOnlyCancel) - { - if (this.smoothScrollTask) { - this.animationManager.removeTask(this.smoothScrollTask); - this.smoothScrollTask = null; - } - this.clearLastScrollPosition(); - - if (this.deferredTasks['cancelPerformingAutoScroll']) { - this.deferredTasks['cancelPerformingAutoScroll'].cancel(); - delete this.deferredTasks['cancelPerformingAutoScroll']; - } - - if (aOnlyCancel) - return; - - var self = this; - (this.deferredTasks['cancelPerformingAutoScroll'] = this.Deferred.wait(0.3)) - .next(function() { - delete self.deferredTasks['cancelPerformingAutoScroll']; - }).error(this.defaultDeferredErrorHandler); - }, - - shouldCancelEnsureElementIsVisible : function TSTBRowser_shouldCancelEnsureElementIsVisible() - { - return ( - this.deferredTasks['cancelPerformingAutoScroll'] && - (new Error()).stack.indexOf('onxblDOMMouseScroll') < 0 - ); - }, - - onTabOpen : function TSTBrowser_onTabOpen(aEvent, aTab) - { - var tab = aTab || aEvent.originalTarget; - var b = this.mTabBrowser; - - if (this.isTabInitialized(tab)) - return false; - - this.initTab(tab); - - var hasStructure = this.treeStructure && this.treeStructure.length; - var pareintIndexInTree = hasStructure ? this.treeStructure.shift() : 0 ; - var lastRelatedTab = b._lastRelatedTab; - - if (this.readiedToAttachNewTab) { - if (pareintIndexInTree < 0) { // there is no parent, so this is a new parent! - this.parentTab = tab.getAttribute(this.kID); - } - - let parent = this.getTabById(this.parentTab); - if (parent) { - let tabs = [parent].concat(this.getDescendantTabs(parent)); - parent = pareintIndexInTree > -1 && pareintIndexInTree < tabs.length ? tabs[pareintIndexInTree] : parent ; - } - if (parent) { - this.attachTabTo(tab, parent, { - dontExpand : this.shouldExpandAllTree - }); - } - - let refTab; - let newIndex = -1; - if (hasStructure) { - } - else if (this.insertBefore && - (refTab = this.getTabById(this.insertBefore))) { - newIndex = refTab._tPos; - } - else if ( - parent && - utils.getTreePref('insertNewChildAt') == this.kINSERT_FISRT && - (this.multipleCount <= 0 || this._addedCountInThisLoop <= 0) - ) { - /* 複数の子タブを一気に開く場合、最初に開いたタブだけを - 子タブの最初の位置に挿入し、続くタブは「最初の開いたタブ」と - 「元々最初の子だったタブ」との間に挿入していく */ - newIndex = parent._tPos + 1; - if (refTab = this.getFirstChildTab(parent)) - this.insertBefore = refTab.getAttribute(this.kID); - } - - if (newIndex > -1) { - if (newIndex > tab._tPos) - newIndex--; - this.internallyTabMovingCount++; - b.moveTabTo(tab, newIndex); - this.internallyTabMovingCount--; - } - - if (this.shouldExpandAllTree) - this.collapseExpandSubtree(parent, false); - } - - this._addedCountInThisLoop++; - if (!this._addedCountClearTimer) { - this._addedCountClearTimer = this.window.setTimeout(function(aSelf) { - aSelf._addedCountInThisLoop = 0; - aSelf._addedCountClearTimer = null; - }, 0, this); - } - - if (!this.readiedToAttachMultiple) { - this.stopToOpenChildTab(b); - } - else { - this.multipleCount++; - } - - if (this.animationEnabled) { - this.updateTabCollapsed(tab, true, true); - let self = this; - this.updateTabCollapsed(tab, false, this.windowService.restoringTree, function() { - /** - * When the system is too slow, the animation can start after - * smooth scrolling is finished. The smooth scrolling should be - * started together with the start of the animation effect. - */ - self.scrollToNewTab(tab); - }); - } - else { - this.scrollToNewTab(tab); - } - - this.updateInsertionPositionInfo(tab); - - if (prefs.getPref('browser.tabs.autoHide')) - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); - - if (this.canStackTabs) - this.updateTabsZIndex(true); - - // if there is only one tab and new another tab is opened, - // closebox appearance is possibly changed. - var tabs = this.getTabs(b); - if (tabs.length == 2) - this.updateInvertedTabContentsOrder(tabs); - - /** - * gBrowser.addTab() resets gBrowser._lastRelatedTab.owner - * when a new background tab is opened from the current tab, - * but it will fail with TST because gBrowser.moveTab() (called - * by TST) clears gBrowser._lastRelatedTab. - * So, we have to restore gBrowser._lastRelatedTab manually. - */ - b._lastRelatedTab = lastRelatedTab; - - return true; - }, - _addedCountInThisLoop : 0, - _addedCountClearTimer : null, - _checkRestoringWindowTimerOnTabAdded : null, - - scrollToNewTab : function TSTBrowser_scrollToNewTab(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - if (this.scrollToNewTabMode > 0) - this.scrollToTab(aTab, this.scrollToNewTabMode < 2); - }, - - updateInsertionPositionInfo : function TSTBrowser_updateInsertionPositionInfo(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var prev = this.getPreviousSiblingTab(aTab); - if (prev) { - this.setTabValue(aTab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); - this.setTabValue(prev, this.kINSERT_BEFORE, aTab.getAttribute(this.kID)); - } - - var next = this.getNextSiblingTab(aTab); - if (next) { - this.setTabValue(aTab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); - this.setTabValue(next, this.kINSERT_AFTER, aTab.getAttribute(this.kID)); - } - }, - - onTabClose : function TSTBrowser_onTabClose(aEvent) - { - var tab = aEvent.originalTarget; - var d = this.document; - var b = this.mTabBrowser; - - tab.setAttribute(this.kREMOVED, true); - - this.stopTabIndentAnimation(tab); - this.stopTabCollapseAnimation(tab); - - var closeParentBehavior = this.getCloseParentBehaviorForTab(tab); - - var collapsed = this.isCollapsed(tab); - if (collapsed) - this.stopRendering(); - - var backupAttributes = this._collectBackupAttributes(tab); - if (DEBUG) - dump('onTabClose: backupAttributes = '+JSON.stringify(backupAttributes)+'\n'); - - if (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN || - this.isSubtreeCollapsed(tab)) - this._closeChildTabs(tab); - - this._saveAndUpdateReferenceTabsInfo(tab); - - var firstChild = this.getFirstChildTab(tab); - - this.detachAllChildren(tab, { - behavior : closeParentBehavior - }); - - var nextFocusedTab = null; - if (firstChild && - (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN || - closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD)) - nextFocusedTab = firstChild; - - var toBeClosedTabs = this._collectNeedlessGroupTabs(tab); - - var parentTab = this.getParentTab(tab); - if (parentTab) { - if (!nextFocusedTab && tab == this.getLastChildTab(parentTab)) { - if (tab == this.getFirstChildTab(parentTab)) // this is the really last child - nextFocusedTab = parentTab; - else - nextFocusedTab = this.getPreviousSiblingTab(tab); - } - - if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1) - nextFocusedTab = this.getNextFocusedTab(parentTab); - } - else if (!nextFocusedTab) { - nextFocusedTab = this.getNextFocusedTab(tab); - } - - if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1) - nextFocusedTab = this.getNextFocusedTab(nextFocusedTab); - - if (nextFocusedTab && nextFocusedTab.hasAttribute(this.kREMOVED)) - nextFocusedTab = null; - - this._reserveCloseRelatedTabs(toBeClosedTabs); - - this.detachTab(tab, { dontUpdateIndent : true }); - - this._restoreTabAttributes(tab, backupAttributes); - - if (b.selectedTab == tab) - this._tryMoveFocusFromClosingCurrentTab(nextFocusedTab); - - this.updateLastScrollPosition(); - - this.destroyTab(tab); - - if (tab.getAttribute('pinned') == 'true') - this.positionPinnedTabsWithDelay(); - - if (prefs.getPref('browser.tabs.autoHide')) - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); - - if (this.canStackTabs) - this.updateTabsZIndex(true); - - if (collapsed) - this.startRendering(); - }, - - _collectBackupAttributes : function TSTBrowser_collectBackupAttributes(aTab) - { - var attributes = {}; - - if (this.hasChildTabs(aTab)) { - attributes[this.kCHILDREN] = this.getTabValue(aTab, this.kCHILDREN); - attributes[this.kSUBTREE_COLLAPSED] = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED); - } - - var ancestors = this.getAncestorTabs(aTab); - if (ancestors.length) { - let next = this.getNextSiblingTab(aTab); - ancestors = ancestors.map(function(aAncestor) { - if (!next && (next = this.getNextSiblingTab(aAncestor))) - attributes[this.kINSERT_BEFORE] = next.getAttribute(this.kID); - return aAncestor.getAttribute(this.kID); - }, this); - attributes[this.kANCESTOR] = ancestors.join('|'); - } - - return attributes; - }, - - _closeChildTabs : function TSTBrowser_closeChildTabs(aTab) - { - var tabs = this.getDescendantTabs(aTab); - if (!this.fireTabSubtreeClosingEvent(aTab, tabs)) - return; - - var subtreeCollapsed = this.isSubtreeCollapsed(aTab); - if (subtreeCollapsed) - this.stopRendering(); - - this.markAsClosedSet([aTab].concat(tabs)); - - tabs.reverse(); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.mTabBrowser.removeTab(tabs[i], { animate : true }); - } - - this.fireTabSubtreeClosedEvent(this.mTabBrowser, aTab, tabs); - - if (subtreeCollapsed) - this.startRendering(); - }, - - _collectNeedlessGroupTabs : function TSTBrowser_collectNeedlessGroupTabs(aTab) - { - var tabs = []; - if (!aTab || !aTab.parentNode) - return tabs; - - var parent = this.getParentTab(aTab); - var siblings = this.getSiblingTabs(aTab); - var groupTabs = siblings.filter(function(aTab) { - return this.isTemporaryGroupTab(aTab); - }, this); - var groupTab = ( - groupTabs.length == 1 && - siblings.length == 1 && - this.hasChildTabs(groupTabs[0]) - ) ? groupTabs[0] : null ; - if (groupTab) - tabs.push(groupTab); - - var shouldCloseParentTab = ( - parent && - this.isTemporaryGroupTab(parent) && - this.getDescendantTabs(parent).length == 1 - ); - if (shouldCloseParentTab) - tabs.push(parent); - - return tabs; - }, - - _reserveCloseRelatedTabs : function TSTBrowser_reserveCloseRelatedTabs(aTabs) - { - if (!aTabs.length) - return; - - var key = 'onTabClose_'+parseInt(Math.random() * 65000); - var self = this; - (this.deferredTasks[key] = this.Deferred.next(function() { - aTabs.forEach(function(aTab) { - if (aTab.parentNode) - self.mTabBrowser.removeTab(aTab, { animate : true }); - }); - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }).next(function() { - aTabs = null; - self = null; - key = null; - }); - }, - - _saveAndUpdateReferenceTabsInfo : function TSTBrowser_saveAndUpdateReferenceTabsInfo(aTab) - { - var prev = this.getPreviousSiblingTab(aTab); - var next = this.getNextSiblingTab(aTab); - if (prev) { - this.setTabValue(aTab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); - - if (next) - this.setTabValue(prev, this.kINSERT_BEFORE, next.getAttribute(this.kID)); - else - this.deleteTabValue(prev, this.kINSERT_BEFORE); - } - if (next) { - this.setTabValue(aTab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); - - if (prev) - this.setTabValue(next, this.kINSERT_AFTER, prev.getAttribute(this.kID)); - else - this.deleteTabValue(next, this.kINSERT_AFTER); - } - }, - - _restoreTabAttributes : function TSTBrowser_restoreTabAttributes(aTab, aAttributes) - { - for (var i in aAttributes) - { - this.setTabValue(aTab, i, aAttributes[i]); - } - }, - - _tryMoveFocusFromClosingCurrentTab : function TSTBrowser_tryMoveFocusFromClosingCurrentTab(aNextFocusedTab) - { - if (!aNextFocusedTab || aNextFocusedTab.hidden) - return; - - var currentTab = this.mTabBrowser.selectedTab; - var d = this.document; - - var event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB, true, true); - var canFocus = currentTab.dispatchEvent(event); - - // for backward compatibility - event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB.replace(/^nsDOM/, ''), true, true); - canFocus = canFocus && currentTab.dispatchEvent(event); - - if (canFocus) { - this._focusChangedByCurrentTabRemove = true; - this.mTabBrowser.selectedTab = aNextFocusedTab; - } - }, - - onTabsClosing : function TSTBrowser_onTabsClosing(aEvent) - { - var tabs = aEvent.detail && aEvent.detail.tabs || - aEvent.getData('tabs') // for backward compatibility; - var b = this.getTabBrowserFromChild(tabs[0]); - - var trees = this.splitTabsToSubtrees(tabs); - if (trees.some(function(aTabs) { - return aTabs.length > 1 && - !this.fireTabSubtreeClosingEvent(aTabs[0], aTabs); - }, this)) - return false; - - trees.forEach(this.markAsClosedSet, this); - - var self = this; - let key = 'onTabClosing_'+parseInt(Math.random() * 65000); - (this.deferredTasks[key] = this.Deferred.next(function() { - for (let i = 0, maxi = trees.length; i < maxi; i++) - { - let tabs = trees[i]; - self.fireTabSubtreeClosedEvent(b, tabs[0], tabs); - } - })).error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - - return true; - }, - - onTabMove : function TSTBrowser_onTabMove(aEvent) - { - var tab = aEvent.originalTarget; - var b = this.mTabBrowser; - tab.__treestyletab__previousPosition = aEvent.detail; - - // When the tab was moved before TabOpen event is fired, we have to update manually. - var newlyOpened = !this.isTabInitialized(tab) && this.onTabOpen(null, tab); - - // twisty vanished after the tab is moved!! - this.initTabContents(tab); - - // On Firefox 29, 30 and laters, reopened (restored) tab can be - // placed in wrong place, because "TabMove" event fires before - // "SSTabRestoring" event and "kINSERT_BEFORE" information is - // unexpectedly cleared. So now I simulate the "SSTabRestoring" - // event here. - // See: https://github.com/piroor/treestyletab/issues/676#issuecomment-47700158 - if (tab.__SS_extdata) { - let storedId = tab.__SS_extdata[this.kID]; // getTabValue() doesn't get the value! - if (storedId && tab.getAttribute(this.kID) != storedId) - this.onTabRestoring(aEvent); - } - - if (this.hasChildTabs(tab) && !this.subTreeMovingCount) { - this.moveTabSubtreeTo(tab, tab._tPos); - } - - var parentTab = this.getParentTab(tab); - if (parentTab && !this.subTreeChildrenMovingCount) { - this.updateChildrenArray(parentTab); - } - - this.updateTabsCount(tab, true); - - var prev = this.getPreviousSiblingTab(tab); - var next = this.getNextSiblingTab(tab); - - if (prev) { - this.setTabValue(prev, this.kINSERT_BEFORE, tab.getAttribute(this.kID)); - this.setTabValue(tab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); - } - else - this.deleteTabValue(tab, this.kINSERT_AFTER); - - if (next) { - this.setTabValue(next, this.kINSERT_AFTER, tab.getAttribute(this.kID)); - this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); - } - else - this.deleteTabValue(tab, this.kINSERT_BEFORE); - - var old = aEvent.detail; - if (old > tab._tPos) - old--; - var tabs = this.getAllTabs(b); - old = tabs[old]; - - prev = this.getPreviousSiblingTab(old); - next = this.getNextSiblingTab(old); - - if (prev) { - this.setTabValue(prev, this.kINSERT_BEFORE, old.getAttribute(this.kID)); - this.setTabValue(old, this.kINSERT_AFTER, prev.getAttribute(this.kID)); - } - else - this.deleteTabValue(old, this.kINSERT_AFTER); - - if (next) { - this.setTabValue(next, this.kINSERT_AFTER, old.getAttribute(this.kID)); - this.setTabValue(old, this.kINSERT_BEFORE, next.getAttribute(this.kID)); - } - else - this.deleteTabValue(old, this.kINSERT_BEFORE); - - this.positionPinnedTabsWithDelay(); - - if (this.canStackTabs) - this.updateTabsZIndex(true); - - if ( - this.subTreeMovingCount || - this.internallyTabMovingCount || - // We don't have to fixup tree structure for a NEW TAB - // which has already been structured. - (newlyOpened && this.getParentTab(tab)) - ) - return; - - this.attachTabFromPosition(tab, aEvent.detail); - this.rearrangeTabViewItems(tab); - }, - - attachTabFromPosition : function TSTBrowser_attachTabFromPosition(aTab, aOldPosition) - { - var parent = this.getParentTab(aTab); - - if (aOldPosition === void(0)) - aOldPosition = aTab._tPos; - - var pos = this.getChildIndex(aTab, parent); - var oldPos = this.getChildIndex(this.getAllTabs(this.mTabBrowser)[aOldPosition], parent); - var delta; - if (pos == oldPos) { // no move? - return; - } - else if (pos < 0 || oldPos < 0) { - delta = 2; - } - else { - delta = Math.abs(pos - oldPos); - } - - var prevTab = this.getPreviousTab(aTab); - var nextTab = this.getNextTab(aTab); - - var tabs = this.getDescendantTabs(aTab); - if (tabs.length) { - nextTab = this.getNextTab(tabs[tabs.length-1]); - } - - var prevParent = this.getParentTab(prevTab); - var nextParent = this.getParentTab(nextTab); - - var prevLevel = prevTab ? Number(prevTab.getAttribute(this.kNEST)) : -1 ; - var nextLevel = nextTab ? Number(nextTab.getAttribute(this.kNEST)) : -1 ; - - var newParent; - - if (!prevTab) { // moved to topmost position - newParent = null; - } - else if (!nextTab) { // moved to last position - newParent = (delta > 1) ? prevParent : parent ; - } - else if (prevParent == nextParent) { // moved into existing tree - newParent = prevParent; - } - else if (prevLevel > nextLevel) { // moved to end of existing tree - if (this.mTabBrowser.selectedTab != aTab) { // maybe newly opened tab - newParent = prevParent; - } - else { // maybe drag and drop - var realDelta = Math.abs(aTab._tPos - aOldPosition); - newParent = realDelta < 2 ? prevParent : (parent || nextParent) ; - } - } - else if (prevLevel < nextLevel) { // moved to first child position of existing tree - newParent = parent || nextParent; - } - - if (newParent != parent) { - if (newParent) { - if (newParent.hidden == aTab.hidden) - this.attachTabTo(aTab, newParent, { insertBefore : nextTab }); - } - else { - this.detachTab(aTab); - } - } - }, - - updateChildrenArray : function TSTBrowser_updateChildrenArray(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var children = this.getChildTabs(aTab); - children.sort(this.sortTabsByOrder); - this.setTabValue( - aTab, - this.kCHILDREN, - children - .map(function(aItem) { - return aItem.getAttribute(this.kID); - }, this) - .join('|') - ); - }, - - // for TabView (Panorama aka Tab Candy) - rearrangeTabViewItems : function TSTBrowser_rearrangeTabViewItems(aTab) - { - if ( - !aTab.parentNode || // do nothing for closed tab! - !aTab.tabItem || - !aTab.tabItem.parent || - !aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder - ) - return; - - aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder(); - }, - - // for TabView (Panorama aka Tab Candy) - onTabVisibilityChanged : function TSTBrowser_onTabVisibilityChanged(aEvent) - { - /** - * Note: On this timing, we cannot know that which is the reason of this - * event, by exitting from Panorama or the "Move to Group" command in the - * context menu on tabs. So, we have to do operations with a delay to compare - * last and current group which is updated in the next event loop. - */ - - var tab = aEvent.originalTarget; - this.updateInvertedTabContentsOrder(tab); - this.tabVisibilityChangedTabs.push({ - tab : tab, - type : aEvent.type - }); - - if (this.tabVisibilityChangedTimer) { - this.window.clearTimeout(this.tabVisibilityChangedTimer); - this.tabVisibilityChangedTimer = null; - } - - this.tabVisibilityChangedTimer = this.window.setTimeout(function(aSelf) { - aSelf.tabVisibilityChangedTimer = null; - - var tabs = aSelf.tabVisibilityChangedTabs; - if (!tabs.length) - return; - - // restore tree from bottom safely - var restoreTabs = tabs.filter(function(aChanged) { - return aChanged.type == 'TabShow' && - aChanged.tab.__treestyletab__restoreState == aSelf.RESTORE_STATE_READY_TO_RESTORE; - }) - .map(function(aChanged) { - return aChanged.tab; - }) - .sort(function(aA, aB) { - return aB._tPos - aA._tPos; - }) - .filter(aSelf.restoreOneTab, aSelf); - for (let i = 0, maxi = restoreTabs.length; i < maxi; i++) - { - let tab = restoreTabs[i]; - aSelf.updateInsertionPositionInfo(tab); - delete tab.__treestyletab__restoreState; - } - - var currentGroupId = aSelf.getTabViewGroupId(); - if (aSelf.lastTabViewGroup && currentGroupId != aSelf.lastTabViewGroup) { - // We should clear it first, because updateTreeByTabVisibility() never change visibility of tabs. - aSelf.tabVisibilityChangedTabs = []; - aSelf.updateTreeByTabVisibility(tabs.map(function(aChanged) { return aChanged.tab; })); - } - else { - // For tabs moved by "Move to Group" command in the context menu on tabs - var processedTabs = {}; - /** - * subtreeFollowParentAcrossTabGroups() can change visibility of child tabs, so, - * we must not clear tabVisibilityChangedTabs here, and we have to use - * simple "for" loop instead of Array.prototype.forEach. - */ - for (let i = 0; i < aSelf.tabVisibilityChangedTabs.length; i++) - { - let changed = aSelf.tabVisibilityChangedTabs[i]; - let tab = changed.tab; - if (aSelf.getAncestorTabs(tab).some(function(aTab) { - return processedTabs[aTab.getAttribute(aSelf.kID)]; - })) - continue; - aSelf.subtreeFollowParentAcrossTabGroups(tab); - processedTabs[tab.getAttribute(aSelf.kID)] = true; - } - // now we can clear it! - aSelf.tabVisibilityChangedTabs = []; - } - aSelf.lastTabViewGroup = currentGroupId; - aSelf.checkTabsIndentOverflow(); - }, 0, this); - }, - tabVisibilityChangedTimer : null, - lastTabViewGroup : null, - - updateTreeByTabVisibility : function TSTBrowser_updateTreeByTabVisibility(aChangedTabs) - { - this.internallyTabMovingCount++; - - var allTabs = this.getAllTabs(this.mTabBrowser); - var normalTabs = allTabs.filter(function(aTab) { - return !aTab.hasAttribute('pinned'); - }); - aChangedTabs = aChangedTabs || normalTabs; - - var shownTabs = aChangedTabs.filter(function(aTab) { - return !aTab.hidden; - }); - - var movingTabToAnotherGroup = !shownTabs.length; - var switchingGroup = !movingTabToAnotherGroup; - - var lastIndex = allTabs.length - 1; - var lastMovedTab; - normalTabs = normalTabs.slice(0).reverse(); - for (let i = 0, maxi = normalTabs.length; i < maxi; i++) - { - let tab = normalTabs[i]; - let parent = this.getParentTab(tab); - let attached = false; - if (parent && (tab.hidden != parent.hidden)) { - let lastNextTab = null; - this.getAncestorTabs(tab).some(function(aAncestor) { - if (aAncestor.hidden == tab.hidden) { - this.attachTabTo(tab, aAncestor, { - dontMove : true, - insertBefore : lastNextTab - }); - attached = true; - return true; - } - lastNextTab = this.getNextSiblingTab(aAncestor); - return false; - }, this); - if (!attached) { - this.collapseExpandTab(tab, false, true); - this.detachTab(tab); - } - } - - if (aChangedTabs.indexOf(tab) < 0) - continue; - - if ( - switchingGroup && - !tab.hidden && - !attached && - !parent - ) { - let prev = this.getPreviousTab(tab); - let next = this.getNextTab(tab); - if ( - (prev && aChangedTabs.indexOf(prev) < 0 && !prev.hidden) || - (next && aChangedTabs.indexOf(next) < 0 && !next.hidden) - ) - this.attachTabFromPosition(tab, lastIndex); - } - - if (movingTabToAnotherGroup && tab.hidden) { - let index = lastMovedTab ? lastMovedTab._tPos - 1 : lastIndex ; - this.mTabBrowser.moveTabTo(tab, index); - lastMovedTab = tab; - } - } - this.internallyTabMovingCount--; - }, - - subtreeFollowParentAcrossTabGroups : function TSTBrowser_subtreeFollowParentAcrossTabGroups(aParent) - { - if (this.tabViewTreeIsMoving) - return; - - var id = this.getTabViewGroupId(aParent); - if (!id) - return; - - this.tabViewTreeIsMoving = true; - this.internallyTabMovingCount++; - var w = this.window; - var b = this.mTabBrowser; - var lastCount = this.getAllTabs(b).length - 1; - - this.detachTab(aParent); - b.moveTabTo(aParent, lastCount); - var descendantTabs = this.getDescendantTabs(aParent); - for (let i = 0, maxi = descendantTabs.length; i < maxi; i++) - { - let tab = descendantTabs[i]; - w.TabView.moveTabTo(tab, id); - b.moveTabTo(tab, lastCount); - } - this.internallyTabMovingCount--; - this.tabViewTreeIsMoving = false; - }, - tabViewTreeIsMoving : false, - - getTabViewGroupId : function TSTBrowser_getTabViewGroupId(aTab) - { - var tab = aTab || this.mTabBrowser.selectedTab; - var item = tab._tabViewTabItem; - if (!item) - return null; - - var group = item.parent; - if (!group) - return null; - - return group.id; - }, - - onRestoreTabContentStarted : function TSTBrowser_onRestoreTabContentStarted(aTab) - { - aTab.linkedBrowser.__treestyletab__toBeRestored = true; - }, - - onTabRestoring : function TSTBrowser_onTabRestoring(aEvent) - { - this.restoreTree(); - - var tab = aEvent.originalTarget; - - tab.linkedBrowser.__treestyletab__toBeRestored = false; - this.handleRestoredTab(tab); - - /** - * Updating of the counter which is used to know how many tabs were - * restored in a time. - */ - this.windowService.restoringCount++; - /** - * By nsSessionStore.js, the next "SSTabRestoring" event will be fined - * with "window.setTimeout()" following this "SSTabRestoring" event. - * So, we have to do "setTimeout()" twice, instead of "Deferred.next()". - */ - var self = this; - this.window.setTimeout(function() { - /** - * On this timing, the next "SSTabRestoring" is not fired yet. - * We only register the countdown task for the next event loop. - */ - let key = 'onTabRestoring_'+parseInt(Math.random() * 65000); - (self.deferredTasks[key] = self.windowService.Deferred.next(function() { - /** - * On this timing, the next "SSTabRestoring" was fired. - * Now we can decrement the counter. - */ - self.windowService.restoringCount--; - })).error(self.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - }, 0); - - if (!tab.selected && - this.mTabBrowser.currentURI.spec == 'about:sessionrestore') { - let frame = this.mTabBrowser.contentWindow; - frame = frame.wrappedJSObject || frame; - let tree = frame.document.getElementById('tabList'); - let data = frame.gTreeData; - if (tree && data) { - let item = data[tree.currentIndex]; - this.window.setTimeout(function(aSelf, aTab, aTitle, aParent) { - if (aTab.label== aTitle) - aSelf.attachTabTo(aTab, aParent); - }, 0, this, tab, item.label, this.mTabBrowser.selectedTab); - } - } - }, - - RESTORED_TREE_COLLAPSED_STATE_LAST_STATE : -1, - RESTORED_TREE_COLLAPSED_STATE_COLLAPSED : 0, - RESTORED_TREE_COLLAPSED_STATE_EXPANDED : 1, - RESTORE_STATE_INITIAL : 0, - RESTORE_STATE_READY_TO_RESTORE : 1, - RESTORE_STATE_STRUCTURE_RESTORED : 2, - handleRestoredTab : function TSTBrowser_handleRestoredTab(aTab) - { - if (aTab.__treestyletab__restoreState == this.RESTORE_STATE_READY_TO_RESTORE) { - // this is a hidden tab in the background group, and - // have to be restored by restoreOneTab() on "TabShown" event. - this.deleteTabValue(aTab, this.kCLOSED_SET_ID); - return; - } - - var [id, mayBeDuplicated] = this._restoreTabId(aTab); - var structureRestored = aTab.__treestyletab__restoreState == this.RESTORE_STATE_STRUCTURE_RESTORED; - var children = this.getTabValue(aTab, this.kCHILDREN); - if ( - !structureRestored && - ( - !mayBeDuplicated || - aTab.getAttribute(this.kCHILDREN) != children - ) - ) { - // failsafe - this.detachAllChildren(aTab, { - dontUpdateIndent : true, - dontAnimate : this.windowService.restoringTree - }); - } - - var closeSetId = !structureRestored && this._getCloseSetId(aTab, mayBeDuplicated); - - // remove temporary cache - var currentId = aTab.getAttribute(this.kID); - if (id != currentId && - currentId && - currentId in this.tabsHash && - this.tabsHash[currentId] == aTab) - delete this.tabsHash[currentId]; - - this.setTabValue(aTab, this.kID, id); - this.tabsHash[id] = aTab; - - if (structureRestored) { - this._fixMissingAttributesFromSessionData(aTab); - } - else { - let isSubtreeCollapsed = this._restoreSubtreeCollapsedState(aTab); - - let restoringMultipleTabs = this.windowService.restoringTree; - let options = { - dontExpand : restoringMultipleTabs, - dontUpdateIndent : true, - dontAnimate : restoringMultipleTabs - }; - let childTabs = this._restoreChildTabsRelation(aTab, children, mayBeDuplicated, options); - - this._restoreTabPositionAndIndent(aTab, childTabs, mayBeDuplicated); - - if (closeSetId) - this.restoreClosedSet(closeSetId, aTab); - - if (isSubtreeCollapsed) - this.collapseExpandSubtree(aTab, isSubtreeCollapsed); - } - - if (mayBeDuplicated) { - this.clearRedirectionTableWithDelay(); - this.clearRedirectbTabRelationsWithDelay(aTab); - } - - delete aTab.__treestyletab__restoreState; - }, - - _restoreTabId : function TSTBrowser_restoreTabId(aTab) - { - // kID can be overridden by nsSessionStore. kID_NEW is for failsafe. - var currentId = aTab.getAttribute(this.kID_NEW) || aTab.getAttribute(this.kID); - aTab.removeAttribute(this.kID_NEW); - var restoredId = this.getTabValue(aTab, this.kID); - var mayBeDuplicated = false; - - aTab.setAttribute(this.kID_RESTORING, restoredId); - if (this.isTabDuplicated(aTab)) { - mayBeDuplicated = true; - /** - * If the tab has its ID as the attribute, then we should use it - * instead of redirected ID, because the tab has been possibly - * attached to another tab. - */ - restoredId = currentId || this.redirectId(restoredId); - } - aTab.removeAttribute(this.kID_RESTORING); - - return [restoredId || currentId, mayBeDuplicated]; - }, - - _getCloseSetId : function TSTBrowser_getCloseSetId(aTab, aMayBeDuplicated) - { - var closeSetId = null; - if (!aMayBeDuplicated) { - closeSetId = this.getTabValue(aTab, this.kCLOSED_SET_ID); - /** - * If the tab is not a duplicated but it has a parent, then, - * it is wrongly attacched by tab moving on restoring. - * Restoring the old ID (the next statement) breaks the children - * list of the temporary parent and causes many problems. - * So, to prevent these problems, I detach the tab from the temporary - * parent manually. - * If the ID stored in the session equals to the value of the - * attribute stored in the element itself, then don't reset the - * tab, because the restoring session is got from the tab itself. - * ( like SS.setTabState(tab, SS.getTabState(tab)) ) - */ - if (this.getTabValue(aTab, this.kID) != aTab.getAttribute(this.kID)) - this.resetTab(aTab, false); - } - this.deleteTabValue(aTab, this.kCLOSED_SET_ID); - return closeSetId; - }, - - _fixMissingAttributesFromSessionData : function TSTBrowser_fixMissingAttributesFromSessionData(aTab) - { - /** - * By some reasons (ex. persistTabAttribute()), actual state of - * the tab (attributes) can be lost on SSTabRestoring. - * For failsafe, we must override actual attributes by stored - * values. - */ - var keys = [ - this.kINSERT_BEFORE, - this.kINSERT_AFTER - ]; - for (let i = 0, maxi = keys.length; i < maxi; i++) - { - let key = keys[i]; - let tab = this.getTabValue(aTab, key); - if (this.getTabById(tab)) - this.setTabValue(aTab, key, tab); - } - - let parentId = this.getTabValue(aTab, this.kPARENT); - let parentTab = this.getTabById(parentId); - if (parentTab && parentTab._tPos < aTab._tPos) - this.setTabValue(aTab, this.kPARENT, parentId); - else - this.deleteTabValue(aTab, this.kPARENT); - - let ancestors = [aTab].concat(this.getAncestorTabs(aTab)); - let children = this.getTabValue(aTab, this.kCHILDREN); - children = children.split('|').filter(function(aChild) { - let tab = this.getTabById(aChild); - return tab && ancestors.indexOf(tab) < 0; - }, this); - this.setTabValue(aTab, this.kCHILDREN, children.join('|')); - - let subtreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED); - if (subtreeCollapsed != aTab.getAttribute(this.kSUBTREE_COLLAPSED)) - this.collapseExpandSubtree(aTab, subtreeCollapsed == 'true', true); - }, - - _restoreSubtreeCollapsedState : function TSTBrowser_restoreSubtreeCollapsedState(aTab, aCollapsed) - { - var shouldCollapse = utils.getTreePref('collapseExpandSubtree.sessionRestore'); - - if (aCollapsed === void(0)) - aCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; - - var isSubtreeCollapsed = ( - this.windowService.restoringTree && - ( - shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_LAST_STATE ? - aCollapsed : - shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_COLLAPSED - ) - ); - this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, isSubtreeCollapsed); - return isSubtreeCollapsed; - }, - - _restoreChildTabsRelation : function TSTBrowser_restoreChildTabsRelation(aTab, aChildrenList, aMayBeDuplicated, aOptions) - { - var childTabs = []; - if (!aChildrenList) - return childTabs; - - aTab.removeAttribute(this.kCHILDREN); - - aChildrenList = aChildrenList.split('|'); - if (aMayBeDuplicated) - aChildrenList = aChildrenList.map(function(aChild) { - return this.redirectId(aChild); - }, this); - - for (let i = 0, maxi = aChildrenList.length; i < maxi; i++) - { - let childTab = aChildrenList[i]; - if (childTab && (childTab = this.getTabById(childTab))) { - let options = aOptions; - if (options && typeof options == 'function') - options = options(childTab); - this.attachTabTo(childTab, aTab, options); - childTabs.push(childTab); - } - } - aChildrenList = aChildrenList.join('|'); - if (aTab.getAttribute(this.kCHILDREN) == aChildrenList) - aTab.removeAttribute(this.kCHILDREN_RESTORING); - else - aTab.setAttribute(this.kCHILDREN_RESTORING, aChildrenList); - - return childTabs; - }, - - _restoreTabPositionAndIndent : function TSTBrowser_restoreTabPositionAndIndent(aTab, aChildTabs, aMayBeDuplicated) - { - var restoringMultipleTabs = this.windowService.restoringTree; - var position = this._prepareInsertionPosition(aTab, aMayBeDuplicated); - var parent = position.parent; - if (DEBUG) - dump('handleRestoredTab: found parent = ' + parent+'\n'); - if (parent) { - aTab.removeAttribute(this.kPARENT); - parent = this.getTabById(parent); - if (parent) { - this.attachTabTo(aTab, parent, { - dontExpand : restoringMultipleTabs, - insertBefore : position.next, - dontUpdateIndent : true, - dontAnimate : restoringMultipleTabs - }); - this.updateTabsIndent([aTab], undefined, restoringMultipleTabs); - this.checkTabsIndentOverflow(); - - if (parent.getAttribute(this.kCHILDREN_RESTORING)) - this.correctChildTabsOrderWithDelay(parent); - } - else { - this.deleteTabValue(aTab, this.kPARENT); - } - } - else { - if (aChildTabs.length) { - this.updateTabsIndent(aChildTabs, undefined, restoringMultipleTabs); - this.checkTabsIndentOverflow(); - } - this._restoreTabPosition(aTab, position.next); - } - }, - - _prepareInsertionPosition : function TSTBrowser_prepareInsertionPosition(aTab, aMayBeDuplicated) - { - var next = this.getTabValue(aTab, this.kINSERT_BEFORE); - if (next && aMayBeDuplicated) - next = this.redirectId(next); - next = this.getTabById(next); - - if (!next) { - let prev = this.getTabValue(aTab, this.kINSERT_AFTER); - if (prev && aMayBeDuplicated) - prev = this.redirectId(prev); - prev = this.getTabById(prev); - next = this.getNextSiblingTab(prev); - } - - var ancestors = (this.getTabValue(aTab, this.kANCESTOR) || this.getTabValue(aTab, this.kPARENT)).split('|'); - if (DEBUG) - dump('handleRestoredTab: ancestors = ' + ancestors+'\n'); - var parent = null; - for (let i in ancestors) - { - if (aMayBeDuplicated) - ancestors[i] = this.redirectId(ancestors[i]); - parent = this.getTabById(ancestors[i]); - if (parent) { - parent = ancestors[i]; - break; - } - } - this.deleteTabValue(aTab, this.kANCESTOR); - - /** - * If the tab is a duplicated and the tab has already been - * attached, then reuse current status based on attributes. - * (Note, if the tab is not a duplicated tab, all attributes - * have been cleared.) - */ - if (!parent) { - parent = aTab.getAttribute(this.kPARENT); - if (DEBUG) - dump('handleRestoredTab: parent = ' + parent+'\n'); - if (parent && !next) - next = this.getNextSiblingTab(aTab); - } - - return { - parent : parent, - next : next - }; - }, - - _restoreTabPosition : function TSTBrowser_restoreTabPosition(aTab, aNextTab) - { - if (!aNextTab) - aNextTab = this.getNextTab(aTab); - var parentOfNext = this.getParentTab(aNextTab); - var newPos = -1; - if (parentOfNext) { - let descendants = this.getDescendantTabs(parentOfNext); - if (descendants.length) - newPos = descendants[descendants.length-1]._tPos; - } - else if (aNextTab) { - newPos = aNextTab._tPos; - if (newPos > aTab._tPos) - newPos--; - } - if (newPos > -1) - this.mTabBrowser.moveTabTo(aTab, newPos); - }, - - correctChildTabsOrderWithDelay : function TSTBrowser_correctChildTabsOrderWithDelay(aTab) - { - if (aTab.correctChildTabsOrderWithDelayTimer) - this.window.clearTimeout(aTab.correctChildTabsOrderWithDelayTimer); - - aTab.correctChildTabsOrderWithDelayTimer = this.window.setTimeout(function(aSelf) { - aSelf.correctChildTabsOrder(aTab); - }, 10, this); - }, - - correctChildTabsOrder : function TSTBrowser_correctChildTabsOrder(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING); - if (!restoringChildren) return; - - var children = aTab.getAttribute(this.kCHILDREN); - if (restoringChildren != children) { - var restoringChildrenIDs = restoringChildren.split('|').reverse(); - for (let i = 0, maxi = restoringChildrenIDs.length; i < maxi; i++) - { - let child = this.getTabById(restoringChildrenIDs[i]); - if (!child) - continue; - - let nextTab = i > 0 ? - this.getTabById(restoringChildrenIDs[i-1]) : - this.getNextSiblingTab(aTab) ; - if (nextTab == this.getNextSiblingTab(child)) - continue; - - let newPos = -1; - if (nextTab) { - newPos = nextTab._tPos; - if (newPos > child._tPos) - newPos--; - } - if (newPos > -1) - this.moveTabSubtreeTo(child, newPos); - } - children = aTab.getAttribute(this.kCHILDREN); - } - - if (restoringChildren == children) - aTab.removeAttribute(this.kCHILDREN_RESTORING); - - aTab.correctChildTabsOrderWithDelayTimer = null; - }, - - redirectId : function TSTBrowser_redirectId(aId) - { - if (!(aId in this._redirectionTable)) - this._redirectionTable[aId] = this.makeNewId(); - return this._redirectionTable[aId]; - }, - _redirectionTable : {}, - - clearRedirectionTableWithDelay : function TSTBrowser_clearRedirectionTableWithDelay() - { - if (this._clearRedirectionTableTimer) { - this.window.clearTimeout(this._clearRedirectionTableTimer); - this._clearRedirectionTableTimer = null; - } - this._clearRedirectionTableTimer = this.window.setTimeout(function(aSelf) { - aSelf._redirectionTable = {}; - }, 1000, this); - }, - _clearRedirectionTableTimer : null, - - clearRedirectbTabRelationsWithDelay : function TSTBrowser_clearRedirectbTabRelationsWithDelay(aTab) - { - if (aTab._clearRedirectbTabRelationsTimer) { - this.window.clearTimeout(aTab._clearRedirectbTabRelationsTimer); - aTab._clearRedirectbTabRelationsTimer = null; - } - aTab._clearRedirectbTabRelationsTimer = this.window.setTimeout(function(aSelf) { - aSelf.clearRedirectbTabRelations(aTab); - delete aTab._clearRedirectbTabRelationsTimer; - }, 1500, this); - }, - clearRedirectbTabRelations : function TSTBrowser_clearRedirectbTabRelations(aTab) - { - if (!aTab || !aTab.parentNode) - return; - - var redirectingIds = Object.keys(this._redirectionTable).map(function(aId) { - return this._redirectionTable[aId]; - }, this); - var existingIds = this.getAllTabs(this.mTabBrowser).map(function(aTab) { - return this.getTabValue(aTab, this.kID); - }, this); - var validIds = redirectingIds.concat(existingIds); - validIds = validIds.filter(function(aId) { - return !!aId; - }); - - var ancestors = this.getTabValue(aTab, this.kANCESTORS); - if (ancestors) { - ancestors = ancestors.split('|'); - let actualAncestors = this.getAncestorTabs(aTab).map(function(aTab) { - return aTab.getAttribute(this.kID); - }, this); - ancestors = ancestors.filter(function(aAncestor) { - if (actualAncestors.indexOf(aAncestor) < 0) - return false; - else - return validIds.indexOf(aAncestor) > -1; - }, this); - if (ancestors.length) - this.setTabValue(aTab, this.kANCESTORS, ancestors.join('|')); - else - this.deleteTabValue(aTab, this.kANCESTORS); - } - - var children = this.getTabValue(aTab, this.kCHILDREN); - if (children) { - children = children.split('|'); - children = children.filter(function(aChild) { - if (this.getParentTab(this.getTabById(aChild)) != aTab) - return false; - else - return validIds.indexOf(aChild) > -1; - }, this); - if (children.length) - this.setTabValue(aTab, this.kCHILDREN, children.join('|')); - else - this.deleteTabValue(aTab, this.kCHILDREN); - } - - var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING); - if (restoringChildren) { - restoringChildren = restoringChildren.split('|'); - restoringChildren = restoringChildren.filter(function(aChild) { - return validIds.indexOf(aChild) > -1; - }, this); - if (restoringChildren.length) - aTab.setAttribute(this.kCHILDREN_RESTORING, restoringChildren.join('|')); - else - aTab.removeAttribute(this.kCHILDREN_RESTORING); - } - }, - - restoreClosedSet : function TSTBrowser_restoreClosedSet(aId, aRestoredTab) - { - var behavior = this.undoCloseTabSetBehavior; - if ( - aRestoredTab.__treestyletab__restoredByUndoCloseTab || - !this.browser.__treestyletab__readyToUndoCloseTab || - this.useTMPSessionAPI || - this._restoringClosedSet || - !(behavior & this.kUNDO_CLOSE_SET || behavior & this.kUNDO_ASK) - ) - return; - - var indexes = []; - var items = utils.evalInSandbox('('+this.SessionStore.getClosedTabData(this.window)+')'); - for (let i = 0, maxi = items.length; i < maxi; i++) - { - let item = items[i]; - if (item.state.extData && - item.state.extData[this.kCLOSED_SET_ID] && - item.state.extData[this.kCLOSED_SET_ID] == aId) - indexes.push(i); - } - - var count = parseInt(aId.split('::')[1]); - - if ( - !indexes.length || - ( - indexes.length+1 < count && - behavior & this.kUNDO_CLOSE_FULL_SET - ) - ) - return; - - if (behavior & this.kUNDO_ASK) { - let self = this; - aRestoredTab.addEventListener('SSTabRestoring', function onSSTabRestoring(aEvent) { - aRestoredTab.removeEventListener(aEvent.type, onSSTabRestoring, false); - self.askUndoCloseTabSetBehavior(aRestoredTab, indexes.length) - .next(function(aBehavior) { - if (aBehavior & self.kUNDO_CLOSE_SET) - self.doRestoreClosedSet(aRestoredTab, indexes); - }); - }, false); - } - else if (behavior & this.kUNDO_CLOSE_SET) { - this.doRestoreClosedSet(aRestoredTab, indexes); - } - }, - - doRestoreClosedSet : function TSTBrowser_doRestoreClosedSet(aRestoredTab, aIndexes) - { - if (!this.window.PlacesUIUtils._confirmOpenInTabs(aIndexes.length)) - return; - - this._restoringClosedSet = true; - this.stopRendering(); - - this.windowService.restoringTree = true; - - var offset = 0; - for (let i = 0, maxi = aIndexes.length; i < maxi; i++) - { - this.window.undoCloseTab(aIndexes[i] - (offset++)); - } - - this.window.setTimeout(function(aSelf, aNextFocused) { - aSelf.windowService.restoringTree = false; - aSelf.mTabBrowser.selectedTab = aNextFocused; - }, 0, this, aRestoredTab || aSelf.mTabBrowser.selectedTab); - - this.startRendering(); - this._restoringClosedSet = false; - }, - _restoringClosedSet : false, - - onTabRestored : function TSTBrowser_onTabRestored(aEvent) - { - delete aEvent.originalTarget.__treestyletab__restoredByUndoCloseTab; - }, - - onTabPinned : function TSTBrowser_onTabPinned(aTab) - { - var parentTab = this.getParentTab(aTab); - - this.collapseExpandSubtree(aTab, false); - - /** - * Children of the newly pinned tab are possibly - * moved to the top of the tab bar, by TabMove event - * from the newly pinned tab. So, we have to - * reposition unexpectedly moved children. - */ - if (!parentTab) { - /** - * Universal but dangerous logic. "__treestyletab__previousPosition" - * can be broken by multiple movings. - */ - let b = this.browser; - this.internallyTabMovingCount++; - let children = this.getDescendantTabs(aTab).reverse(); - for (let i = 0, maxi = children.length; i < maxi; i++) - { - let childTab = children[i]; - if (childTab.__treestyletab__previousPosition > childTab._tPos) - b.moveTabTo(childTab, childTab.__treestyletab__previousPosition); - } - this.internallyTabMovingCount--; - } - else { - /** - * Safer logic. This cannot be available for "root" tabs because - * their children (already moved) have no way to know the anchor - * position (the next sibling of the pinned tab itself). - */ - let b = this.browser; - this.internallyTabMovingCount++; - let children = this.getChildTabs(aTab).reverse(); - for (let i = 0, maxi = children.length; i < maxi; i++) - { - let childTab = children[i]; - if (childTab._tPos < parentTab._tPos) - b.moveTabTo(childTab, parentTab._tPos); - } - this.internallyTabMovingCount--; - } - - this.detachAllChildren(aTab, { - behavior : this.getCloseParentBehaviorForTab( - aTab, - this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD - ) - }); - this.detachTab(aTab); - - this.collapseExpandTab(aTab, false); - if (this.isVertical) - this.positionPinnedTabsWithDelay(); - }, - - onTabUnpinned : function TSTBrowser_onTabUnpinned(aTab) - { - var style = aTab.style; - style.marginLeft = style.marginRight = style.marginTop = ''; - - this.updateInvertedTabContentsOrder(aTab); - if (this.isVertical) - this.positionPinnedTabsWithDelay(); - }, - - onTabSelect : function TSTBrowser_onTabSelect(aEvent) - { - var b = this.mTabBrowser; - var tab = b.selectedTab - - this.cancelDelayedExpandOnTabSelect(); - - if ( - /** - * .previewTab() focuses to the tab internally, - * so we should ignore this event if it is fired from previewTab(). - */ - b._previewMode || - /** - * Ignore selected tabs which is being closed. For example, - * when a collapsed tree is closed, Firefox unexpectedly gives - * focus to a collapsed child in the tree. - */ - (b._removingTabs && b._removingTabs.indexOf(tab) > -1) - ) - return; - - var shouldCollapseExpandNow = utils.getTreePref('autoCollapseExpandSubtreeOnSelect'); - var newActiveTabOptions = { - canCollapseTree : shouldCollapseExpandNow, - canExpandTree : shouldCollapseExpandNow - }; - if (this.isCollapsed(tab)) { - if (utils.getTreePref('autoExpandSubtreeOnCollapsedChildFocused')) { - this.getAncestorTabs(tab).forEach(function(aAncestor) { - this.collapseExpandSubtree(aAncestor, false); - }, this); - this.handleNewActiveTab(tab, newActiveTabOptions); - } - else { - b.selectedTab = this.getRootTab(tab); - } - } - else if ( - ( - /** - * Focus movings by arrow keys should not be handled on TabSelect, - * because they are already handled by handleAdvanceSelectedTab(). - */ - this.windowService.arrowKeyEventOnTab && - this.windowService.arrowKeyEventOnTab.advanceFocus - ) || - ( - /** - * Focus movings by closing of the old current tab should be handled - * only when it is activated by user preference expressly. - */ - this._focusChangedByCurrentTabRemove && - !utils.getTreePref('autoCollapseExpandSubtreeOnSelect.onCurrentTabRemove') - ) - ) { - // do nothing! - } - else if (this.hasChildTabs(tab) && this.isSubtreeCollapsed(tab)) { - if ( - this._focusChangedByShortcut && - this.windowService.accelKeyPressed - ) { - if (utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut')) { - newActiveTabOptions.canExpandTree = true; - newActiveTabOptions.canCollapseTree = ( - newActiveTabOptions.canCollapseTree && - utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.collapseOthers') - ); - let delay = utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay'); - if (delay > 0) { - this._autoExpandOnTabSelectTimer = this.window.setTimeout(function(aSelf) { - if (tab && tab.parentNode) - aSelf.handleNewActiveTab(tab, newActiveTabOptions); - }, delay, this); - } - else { - this.handleNewActiveTab(tab, newActiveTabOptions); - } - } - else if (newActiveTabOptions.canExpandTree) { - this.windowService.expandTreeAfterKeyReleased(tab); - } - } - else { - this.handleNewActiveTab(tab, newActiveTabOptions); - } - } - - this._focusChangedByCurrentTabRemove = false; - this._focusChangedByShortcut = false; - - this.updateInvertedTabContentsOrder(); - - if (!this.isTabInViewport(tab)) { - this.scrollToTab(tab); - aEvent.stopPropagation(); - } - }, - cancelDelayedExpandOnTabSelect : function TSTBrowser_cancelDelayedExpandOnTabSelect() { - if (this._autoExpandOnTabSelectTimer) { - this.window.clearTimeout(this._autoExpandOnTabSelectTimer); - this._autoExpandOnTabSelectTimer = null; - } - }, - handleNewActiveTab : function TSTBrowser_handleNewActiveTab(aTab, aOptions) - { - if (this.doingCollapseExpand || !aTab || !aTab.parentNode) - return; - - aOptions = aOptions || {}; - - if (this._handleNewActiveTabTimer) - this.window.clearTimeout(this._handleNewActiveTabTimer); - - /** - * First, we wait until all event listeners for the TabSelect - * event were processed. - */ - this._handleNewActiveTabTimer = this.window.setTimeout(function(aSelf) { - aSelf.window.clearTimeout(aSelf._handleNewActiveTabTimer); - aSelf._handleNewActiveTabTimer = null; - - if (aOptions.canExpandTree) { - if (aOptions.canCollapseTree && - utils.getTreePref('autoExpand.intelligently')) - aSelf.collapseExpandTreesIntelligentlyFor(aTab); - else - aSelf.collapseExpandSubtree(aTab, false); - } - }, 0, this); - }, - _handleNewActiveTabTimer : null, - - handleAdvanceSelectedTab : function TSTBrowser_handleAdvanceSelectedTab(aDir, aWrap) - { - this._focusChangedByShortcut = this.windowService.accelKeyPressed; - - if (!this.canCollapseSubtree(this.mTabBrowser.selectedTab) || - utils.getTreePref('focusMode') != this.kFOCUS_VISIBLE) - return false; - - if (this.processArrowKeyOnFocusAdvanced()) - return true; - - return this.advanceSelectedTab(aDir, aWrap); - }, - - processArrowKeyOnFocusAdvanced : function TSTBrowser_processArrowKeyOnFocusAdvanced() - { - var event = this.windowService.arrowKeyEventOnTab; - if (!event) - return false; - - if ( - event.altKey || - event.ctrlKey || - event.metaKey || - event.shiftKey || - (this.isVertical ? (event.up || event.down) : (event.left || event.right)) - ) { - event.advanceFocus = true; - return false; - } - - var collapse, expand; - switch (this.position) - { - case 'top': - collapse = event.up; - expand = event.down; - break; - - case 'bottom': - collapse = event.down; - expand = event.up; - break; - - case 'left': - collapse = event.left; - expand = event.right; - break; - - case 'right': - if (utils.getTreePref('tabbar.invertTab')) { - collapse = event.right; - expand = event.left; - } - else { - collapse = event.left; - expand = event.right; - } - break; - } - - var tab = this.mTabBrowser.selectedTab; - - var collapsed = this.isSubtreeCollapsed(tab); - if (this.hasChildTabs(tab) && (collapsed ? expand : collapse )) { - event.collapse = collapse; - event.expand = expand; - this.collapseExpandSubtree(tab, !collapsed); - return true; - } - - var nextSelected; - if (expand) - nextSelected = this.getFirstChildTab(tab); - else if (collapse) - nextSelected = this.getParentTab(tab); - - if (nextSelected) { - event.advanceFocus = true; - this.mTabBrowser.selectedTab = nextSelected; - return true; - } - - return true; - }, - - advanceSelectedTab : function TSTBrowser_advanceSelectedTab(aDir, aWrap) - { - var tab = this.mTabBrowser.selectedTab; - var tabbar = this.mTabBrowser.mTabContainer; - - var nextTab = (aDir < 0) ? this.getPreviousVisibleTab(tab) : this.getNextVisibleTab(tab) ; - if (!nextTab && aWrap) { - let tabs = tabbar.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"])'); - nextTab = tabs[aDir < 0 ? tabs.length-1 : 0 ]; - } - if (nextTab && nextTab != tab) - tabbar._selectNewTab(nextTab, aDir, aWrap); - - return true; - }, - - onTabClick : function TSTBrowser_onTabClick(aEvent, aTab) - { - aTab = aTab || this.getTabFromEvent(aEvent); - - if (aEvent.button == 1) { - if (!this.warnAboutClosingTabSubtreeOf(aTab)) { - aEvent.preventDefault(); - aEvent.stopPropagation(); - } - return; - } - - if (aEvent.button != 0) - return; - - if (this.isEventFiredOnTwisty(aEvent)) { - if (this.hasChildTabs(aTab) && this.canCollapseSubtree(aTab)) { - this.manualCollapseExpandSubtree(aTab, aTab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); - aEvent.preventDefault(); - aEvent.stopPropagation(); - } - return; - } - - if (this.isEventFiredOnClosebox(aEvent)) { - if (!this.warnAboutClosingTabSubtreeOf(aTab)) { - aEvent.preventDefault(); - aEvent.stopPropagation(); - } - return; - } - }, - - onClick : function TSTBrowser_onClick(aEvent) - { - if ( - aEvent.target.ownerDocument != this.document || - aEvent.button != 0 || - this.isAccelKeyPressed(aEvent) - ) - return; - - var tab = this.getTabFromEvent(aEvent); - if (tab) { - this.onTabClick(aEvent, tab); - } - else { - // click on indented space on the tab bar - tab = this.getTabFromTabbarEvent(aEvent); - if (tab) - this.mTabBrowser.selectedTab = tab; - } - }, - - onDblClick : function TSTBrowser_onDblClick(aEvent) - { - let tab = this.getTabFromEvent(aEvent); - if (tab && - this.hasChildTabs(tab) && - utils.getTreePref('collapseExpandSubtree.dblclick')) { - this.manualCollapseExpandSubtree(tab, tab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); - aEvent.preventDefault(); - aEvent.stopPropagation(); - } - }, - - onMozMouseHittest : function TSTBrowser_onMozMouseHittest(aEvent) - { - // block default behaviors of the tab bar (dragging => window move, etc.) - if ( - !this.getTabFromEvent(aEvent) && - !this.isEventFiredOnClickable(aEvent) && - ( - this.position != 'top' || - aEvent.shiftKey || - this.tabbarDNDObserver.canDragTabbar(aEvent) - ) - ) - aEvent.stopPropagation(); - }, - - onMouseDown : function TSTBrowser_onMouseDown(aEvent) - { - if (this.isEventFiredOnScrollbar(aEvent)) - this.cancelPerformingAutoScroll(); - - if ( - aEvent.button == 0 && - this.isEventFiredOnTwisty(aEvent) - ) { - // prevent to select the tab for clicking on twisty - aEvent.stopPropagation(); - // prevent to focus to the tab element itself - aEvent.preventDefault(); - } - else { - this.onMozMouseHittest(aEvent); - } - }, - - onDOMMouseScroll : function TSTBrowser_onDOMMouseScroll(aEvent) - { - this.cancelPerformingAutoScroll(); - }, - - onScroll : function TSTBrowser_onScroll(aEvent) - { - // restore scroll position when a tab is closed. - this.restoreLastScrollPosition(); - }, - - onTabbarOverflow : function TSTBrowser_onTabbarOverflow(aEvent) - { - var tabs = this.mTabBrowser.mTabContainer; - var horizontal = tabs.orient == 'horizontal'; - if (horizontal) - return; - aEvent.stopPropagation(); - this.positionPinnedTabsWithDelay(); - if (aEvent.detail == 1) { - /** - * By horizontal overflow/underflow, Firefox can wrongly - * removes "overflow" attribute for vertical tab bar. - * We have to override the result. - */ - this.updateTabbarOverflow(); - } - else { - if (aEvent.type == 'overflow') { - tabs.setAttribute('overflow', 'true'); - this.scrollBoxObject.ensureElementIsVisible(tabs.selectedItem); - } - else { - tabs.removeAttribute('overflow'); - } - } - }, - - onResize : function TSTBrowser_onResize(aEvent) - { - if ( - !aEvent.originalTarget || - !(aEvent.originalTarget instanceof Ci.nsIDOMWindow) - ) - return; - - var resizedTopFrame = aEvent.originalTarget.top; - var isContentResize = resizedTopFrame == this.mTabBrowser.contentWindow; - var isChromeResize = resizedTopFrame == this.window; - - if (isChromeResize && aEvent.originalTarget != resizedTopFrame) { - // ignore resizing of sub frames in "position:fixed" box - let target = aEvent.target; - try { - let node = target.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell) - .chromeEventHandler; - let root = node.ownerDocument.documentElement; - while (node && node != root) { - if (node.boxObject && !node.boxObject.parentBox) { - isChromeResize = false; - break; - } - node = node.parentNode; - } - } - catch(e) { - } - } - - // Ignore events when a background tab raises to the foreground. - if (isContentResize && this._lastTabbarPlaceholderSize) { - let newSize = this.getTabbarPlaceholderSize(); - isContentResize = - newSize.width != this._lastTabbarPlaceholderSize.width || - newSize.height != this._lastTabbarPlaceholderSize.height; - } - - if (isContentResize || isChromeResize) { - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_WINDOW_RESIZE); - this.updateInvertedTabContentsOrder(true); - this.mTabBrowser.mTabContainer.adjustTabstrip(); - } - }, - - onPopupShowing : function TSTBrowser_onPopupShowing(aEvent) - { - if (aEvent.target == aEvent.currentTarget) - this.initTabContextMenu(aEvent); - }, - - initTabContextMenu : function TSTBrowser_initTabContextMenu(aEvent) - { - var b = this.mTabBrowser; - var sep, items = {}; - - var ids = [ - this.kMENUITEM_RELOADSUBTREE, - this.kMENUITEM_RELOADCHILDREN, - this.kMENUITEM_REMOVESUBTREE, - this.kMENUITEM_REMOVECHILDREN, - this.kMENUITEM_REMOVEALLTABSBUT, - this.kMENUITEM_COLLAPSE, - this.kMENUITEM_EXPAND, - this.kMENUITEM_AUTOHIDE, - this.kMENUITEM_FIXED, - this.kMENUITEM_BOOKMARKSUBTREE - ]; - for (let i = 0, maxi = ids.length; i < maxi; i++) - { - let id = ids[i]; - let item = aEvent.currentTarget.querySelector('*[id^="'+id+'"]'); - if (!item) - continue; - items[id] = item; - if (utils.getTreePref('show.'+id)) - item.removeAttribute('hidden'); - else - item.setAttribute('hidden', true); - switch (id) - { - case this.kMENUITEM_RELOADSUBTREE: - case this.kMENUITEM_RELOADCHILDREN: - case this.kMENUITEM_REMOVESUBTREE: - case this.kMENUITEM_REMOVECHILDREN: - case this.kMENUITEM_REMOVEALLTABSBUT: - case this.kMENUITEM_COLLAPSE: - case this.kMENUITEM_EXPAND: - case this.kMENUITEM_BOOKMARKSUBTREE: - this.showHideSubtreeMenuItem(item, [b.mContextTab]); - continue; - default: - continue; - } - } - - // collapse/expand all - sep = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_COLLAPSEEXPAND_SEPARATOR+'"]'); - let collapseItem = items[this.kMENUITEM_COLLAPSE]; - let expandItem = items[this.kMENUITEM_EXPAND]; - if (this.canCollapseSubtree(b) && - b.mTabContainer.querySelector('tab['+this.kCHILDREN+']')) { - if (collapseItem) { - if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']:not(['+this.kSUBTREE_COLLAPSED+'="true"])')) - collapseItem.removeAttribute('disabled'); - else - collapseItem.setAttribute('disabled', true); - } - - if (expandItem) { - if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']['+this.kSUBTREE_COLLAPSED+'="true"]')) - expandItem.removeAttribute('disabled'); - else - expandItem.setAttribute('disabled', true); - } - } - else { - if (collapseItem) - collapseItem.setAttribute('hidden', true); - if (expandItem) - expandItem.setAttribute('hidden', true); - } - if (sep) { - if ( - (!collapseItem || collapseItem.getAttribute('hidden') == 'true') && - (!expandItem || expandItem.getAttribute('hidden') == 'true') - ) { - sep.setAttribute('hidden', true); - } - else { - sep.removeAttribute('hidden'); - } - } - - // close all tabs but this tree - let removeAllTabsBut = items[this.kMENUITEM_REMOVEALLTABSBUT]; - if (removeAllTabsBut) { - let rootTabs = this.visibleRootTabs; - if (rootTabs.length == 1 && rootTabs[0] == b.mContextTab) - removeAllTabsBut.setAttribute('disabled', true); - else - removeAllTabsBut.removeAttribute('disabled'); - } - - // auto hide - let autohide = items[this.kMENUITEM_AUTOHIDE]; - if (autohide) - this.autoHide.updateMenuItem(autohide); - - // fix - let fixedPref; - let fixedLabel; - if (this.isVertical) { - fixedPref = b.getAttribute(this.kFIXED+'-vertical') == 'true'; - fixedLabel = 'label-vertical'; - } - else { - fixedPref = b.getAttribute(this.kFIXED+'-horizontal') == 'true'; - fixedLabel = 'label-horizontal'; - } - let fixed = items[this.kMENUITEM_FIXED]; - if (fixed) { - fixed.setAttribute('label', fixed.getAttribute(fixedLabel)); - if (fixedPref) - fixed.setAttribute('checked', true); - else - fixed.removeAttribute('checked'); - } - - sep = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_AUTOHIDE_SEPARATOR+'"]'); - if (sep) { - if ( - (autohide && autohide.getAttribute('hidden') != 'true') || - (fixed && fixed.getAttribute('hidden') != 'true') - ) { - sep.removeAttribute('hidden'); - } - else { - sep.setAttribute('hidden', true); - } - } - - let closeTabsToEnd = aEvent.currentTarget.querySelector('*[id^="'+this.kMENUITEM_CLOSE_TABS_TO_END+'"]'); - if (closeTabsToEnd) { // https://bugzilla.mozilla.org/show_bug.cgi?id=866880 - let label, accesskey; - if (this.isVertical) { - label = utils.treeBundle.getString('closeTabsToTheEnd_vertical_label'); - accesskey = utils.treeBundle.getString('closeTabsToTheEnd_vertical_accesskey'); - } - else { - label = this._closeTabsToEnd_horizontalLabel; - accesskey = this._closeTabsToEnd_horizontalAccesskey; - } - closeTabsToEnd.setAttribute('label', label); - closeTabsToEnd.setAttribute('accesskey', accesskey); - } - }, - - onTabsOnTopSyncCommand : function TSTBrowser_onTabsOnTopSyncCommand(aEnabled) - { - if ( - this.windowService.tabsOnTopChangingByUI || - !aEnabled || - this.position != 'top' || - this.fixed || - this.windowService.isPopupWindow - ) - return; - this.windowService.tabsOnTopChangingByUI = true; - var self = this; - if (this.deferredTasks['onTabsOnTopSyncCommand']) - this.deferredTasks['onTabsOnTopSyncCommand'].cancel(); - (this.deferredTasks['onTabsOnTopSyncCommand'] = this.Deferred - .next(function() { - self.windowService.toggleFixed(self.mTabBrowser); - })) - .next(function() { - if (self.window.TabsOnTop.enabled != aEnabled) - self.window.TabsOnTop.enabled = aEnabled; - }) - .error(this.defaultDeferredErrorHandler) - .next(function() { - self.windowService.tabsOnTopChangingByUI = false; - delete self.deferredTasks['onTabsOnTopSyncCommand']; - }); - }, - - onBeforeFullScreenToggle : function TSTBrowser_onBeforeFullScreenToggle() - { - if (this.position != 'top') { - var isEnteringFullScreenMode = !this.window.fullScreen; - // entering to the DOM-fullscreen (ex. YouTube Player) - if (this.document.mozFullScreen && isEnteringFullScreenMode) { - this.setTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED, true); - } - else { - if (this.document.documentElement.getAttribute(this.kDOM_FULLSCREEN_ACTIVATED) != 'true') { - if (isEnteringFullScreenMode) - this.autoHide.startForFullScreen(); - else - this.autoHide.endForFullScreen(); - } - this.removeTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED); - } - } - }, - - onTreeStyleTabPrintPreviewEntered : function TSTBrowser_onTreeStyleTabPrintPreviewEntered(aEvent) - { - this.setTabbrowserAttribute(this.kPRINT_PREVIEW, true); - }, - - onTreeStyleTabPrintPreviewExited : function TSTBrowser_onTreeStyleTabPrintPreviewExited(aEvent) - { - this.removeTabbrowserAttribute(this.kPRINT_PREVIEW); - }, - -/* commands */ - -/* reset */ - - resetTab : function TSTBrowser_resetTab(aTab, aDetachAllChildren) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - if (aDetachAllChildren) - this.detachAllChildren(aTab, { - dontUpdateIndent : true, - dontAnimate : true - }); - - this.detachTab(aTab, { - dontUpdateIndent : true, - dontAnimate : true - }); - - this.resetTabState(aTab); - this.updateTabsIndent([aTab], undefined, true); - }, - - resetTabState : function TSTBrowser_resetTabState(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - aTab.removeAttribute(this.kID); - aTab.removeAttribute(this.kID_RESTORING); - aTab.removeAttribute(this.kPARENT); - aTab.removeAttribute(this.kCHILDREN); - aTab.removeAttribute(this.kCHILDREN_RESTORING); - aTab.removeAttribute(this.kSUBTREE_COLLAPSED); - aTab.removeAttribute(this.kSUBTREE_EXPANDED_MANUALLY); - aTab.removeAttribute(this.kCOLLAPSED); - aTab.removeAttribute(this.kNEST); - this.updateTabCollapsed(aTab, false, true); - }, - - resetAllTabs : function TSTBrowser_resetAllTabs(aDetachAllChildren) - { - var tabs = this.getAllTabs(this.mTabBrowser); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.resetTab(tabs[i], aDetachAllChildren); - } - }, - - resetTabbarSize : function TSTBrowser_resetTabbarSize() - { - if (this.isVertical) { - utils.setTreePref('tabbar.shrunkenWidth', utils.getTreePref('tabbar.shrunkenWidth.default')); - utils.setTreePref('tabbar.width', utils.getTreePref('tabbar.width.default')); - } - else { - utils.setTreePref('tabbar.height', utils.getTreePref('tabbar.height.default')); - let tabContainerBox = this.getTabContainerBox(this.mTabBrowser); - tabContainerBox.removeAttribute('height'); - this._tabStripPlaceHolder.height = tabContainerBox.boxObject.height; - } - this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET); - }, - - get treeViewEnabled() /* PUBLIC API */ - { - return this._treeViewEnabled; - }, - set treeViewEnabled(aValue) - { - var newValue = !!aValue; - if (newValue == this._treeViewEnabled) - return aValue; - - this._treeViewEnabled = newValue; - if (this._treeViewEnabled) { - if (this._lastAllowSubtreeCollapseExpand) - this.allowSubtreeCollapseExpand = true; - delete this._lastAllowSubtreeCollapseExpand; - - let tabs = this.getAllTabs(this.browser); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - if (tab._TSTLastSubtreeCollapsed) - this.collapseExpandSubtree(tab, true, true); - if (tab._TSTLastSubtreeExpandedManually) - this.setTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY, true); - delete tab._TSTLastSubtreeCollapsed; - delete tab._TSTLastSubtreeExpandedManually; - this.updateTabIndent(tab, 0, true); - } - this.updateTabsIndent(this.rootTabs, undefined, true); - } - else { - let tabs = this.getAllTabs(this.browser); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - this.updateTabIndent(tab, 0, true); - tab._TSTLastSubtreeCollapsed = this.isSubtreeCollapsed(tab); - tab._TSTLastSubtreeExpandedManually = this.getTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; - this.collapseExpandSubtree(tab, false, true); - } - - this._lastAllowSubtreeCollapseExpand = this.allowSubtreeCollapseExpand; - this.allowSubtreeCollapseExpand = false; - } - return aValue; - }, -// _treeViewEnabled : true, - -/* attach/detach */ - - attachTabTo : function TSTBrowser_attachTabTo(aChild, aParent, aInfo) /* PUBLIC API */ - { - if (!aChild.parentNode || (aParent && !aParent.parentNode)) // do nothing for closed tab! - return; - - aInfo = aInfo || {}; - var newAncestors = []; - - if (aParent) { - newAncestors = [aParent].concat(this.getAncestorTabs(aParent)); - if (this.maxTreeLevelPhisical && this.maxTreeLevel > -1) { - let level = parseInt(aParent.getAttribute(this.kNEST) || 0) + 1; - newAncestors.some(function(aAncestor) { - if (level <= this.maxTreeLevel) - return true; - level--; - return false; - }, this); - } - } - - var currentParent; - if ( - !aChild || - !aParent || - aChild == aParent || - (currentParent = this.getParentTab(aChild)) == aParent || - aChild.getAttribute('pinned') == 'true' || - aParent.getAttribute('pinned') == 'true' - ) { - this.fireAttachedEvent(aChild, aParent); - return; - } - - // avoid recursive tree - var ancestors = [aParent].concat(this.getAncestorTabs(aChild)); - if (ancestors.indexOf(aChild) > -1) - return; - - currentParent = ancestors[ancestors.length-1]; - var shouldInheritIndent = ( - !currentParent || - (currentParent.getAttribute(this.kNEST) == aParent.getAttribute(this.kNEST)) - ); - - this.ensureTabInitialized(aChild); - this.ensureTabInitialized(aParent); - - if (!aInfo) - aInfo = {}; - - var id = aChild.getAttribute(this.kID); - - this.detachTab(aChild, { - dontUpdateIndent : true - }); - - var children = aParent.getAttribute(this.kCHILDREN) - .split('|').filter(function(aId) { - return this.getTabById(aId); - }, this); - - var newIndex; - - var oldIndex = children.indexOf(id); - if (oldIndex > -1) - children.splice(oldIndex, 1); - - var insertBefore = aInfo.insertBefore || - (aInfo.dontMove ? this.getNextTab(aChild) : null ); - var beforeTab = insertBefore ? insertBefore.getAttribute(this.kID) : null ; - var beforeIndex; - if (beforeTab && (beforeIndex = children.indexOf(beforeTab)) > -1) { - children.splice(beforeIndex, 0, id); - newIndex = insertBefore._tPos; - } - else { - children.push(id); - if (aInfo.dontMove && children.length > 1) { - children = children - .map(this.getTabById, this) - .sort(this.sortTabsByOrder) - .map(function(aTab) { - return aTab.getAttribute(this.kID); - }, this); - } - let refTab = aParent; - let descendant = this.getDescendantTabs(aParent); - if (descendant.length) { - let lastDescendant = descendant[descendant.length-1]; - /** - * The last descendant tab can be temporarilly moved - * upper than the root parent tab, in some cases. - * (the parent tab is pinned, etc.) - */ - if (!refTab || lastDescendant._tPos > refTab._tPos) - refTab = lastDescendant; - } - newIndex = refTab._tPos+1; - } - - this.setTabValue(aParent, this.kCHILDREN, children.join('|')); - this.setTabValue(aChild, this.kPARENT, aParent.getAttribute(this.kID)); - - this.updateTabsCount(aParent); - if (shouldInheritIndent && !aInfo.dontUpdateIndent) - this.inheritTabIndent(aChild, aParent); - - if (!aInfo.dontMove) { - if (newIndex > aChild._tPos) - newIndex--; - this.moveTabSubtreeTo(aChild, newIndex); - } - - if (aInfo.forceExpand) { - this.collapseExpandSubtree(aParent, false, aInfo.dontAnimate); - } - else if (!aInfo.dontExpand) { - if (utils.getTreePref('autoCollapseExpandSubtreeOnAttach') && - this.shouldTabAutoExpanded(aParent)) - this.collapseExpandTreesIntelligentlyFor(aParent); - - if (utils.getTreePref('autoCollapseExpandSubtreeOnSelect')) { - newAncestors.forEach(function(aAncestor) { - if (this.shouldTabAutoExpanded(aAncestor)) - this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate); - }, this); - } - else if (this.shouldTabAutoExpanded(aParent)) { - if (utils.getTreePref('autoExpandSubtreeOnAppendChild')) { - newAncestors.forEach(function(aAncestor) { - if (this.shouldTabAutoExpanded(aAncestor)) - this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate); - }, this); - } - else - this.collapseExpandTab(aChild, true, aInfo.dontAnimate); - } - - if (this.isCollapsed(aParent)) - this.collapseExpandTab(aChild, true, aInfo.dontAnimate); - } - else if (this.shouldTabAutoExpanded(aParent) || - this.isCollapsed(aParent)) { - this.collapseExpandTab(aChild, true, aInfo.dontAnimate); - } - - if (!aInfo.dontUpdateIndent) { - this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); - this.checkTabsIndentOverflow(); - } - - this.promoteTooDeepLevelTabs(aChild); - - this.fireAttachedEvent(aChild, aParent); - }, - fireAttachedEvent : function TSTBrowser_fireAttachedEvent(aChild, aParent) - { - var data = { - parentTab : aParent - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED, aChild, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED.replace(/^nsDOM/, ''), aChild, true, false, data); - }, - - shouldTabAutoExpanded : function TSTBrowser_shouldTabAutoExpanded(aTab) - { - return this.hasChildTabs(aTab) && - this.isSubtreeCollapsed(aTab); - }, - - detachTab : function TSTBrowser_detachTab(aChild, aInfo) /* PUBLIC API */ - { - if (!aChild || !aChild.parentNode) - return; - if (!aInfo) - aInfo = {}; - - var parentTab = this.getParentTab(aChild); - if (!parentTab) - return; - - var id = aChild.getAttribute(this.kID); - - this.setTabValue( - parentTab, - this.kCHILDREN, - parentTab.getAttribute(this.kCHILDREN) - .split('|') - .filter(function(aId) { - return this.getTabById(aId) && aId != id; - }, this).join('|') - ); - this.deleteTabValue(aChild, this.kPARENT); - - if (!this.hasChildTabs(parentTab)) - this.setTabValue(parentTab, this.kSUBTREE_COLLAPSED, true); - - this.updateTabsCount(parentTab); - - if (!aInfo.dontUpdateIndent) { - this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); - this.checkTabsIndentOverflow(); - } - - var data = { - parentTab : parentTab - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_DETACHED, aChild, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_DETACHED.replace(/^nsDOM/, ''), aChild, true, false, data); - - if (this.isTemporaryGroupTab(parentTab) && !this.hasChildTabs(parentTab)) { - this.window.setTimeout(function(aTabBrowser) { - if (parentTab.parentNode) - aTabBrowser.removeTab(parentTab, { animate : true }); - parentTab = null; - }, 0, this.getTabBrowserFromChild(parentTab)); - } - }, - partTab : function TSTBrowser_partTab(aChild, aInfo) /* PUBLIC API, for backward compatibility */ - { - return this.detachTab(aChild, aInfo); - }, - - detachAllChildren : function TSTBrowser_detachAllChildren(aTab, aInfo) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var children = this.getChildTabs(aTab); - if (!children.length) - return; - - aInfo = aInfo || {}; - if (!('behavior' in aInfo)) - aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN; - if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN) - aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD; - - var b = this.mTabBrowser; - var parentTab = this.getParentTab(aTab); - - if ( - this.isGroupTab(aTab) && - this.getTabs(b).filter(function(aTab) { - return !b._removingTabs || b._removingTabs.indexOf(aTab) < 0; - }).length == children.length - ) { - aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; - aInfo.dontUpdateIndent = false; - } - - var insertBefore = null; - if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN && - !utils.getTreePref('closeParentBehavior.moveDetachedTabsToBottom')) { - insertBefore = this.getNextSiblingTab(this.getRootTab(aTab)); - } - for (let i = 0, maxi = children.length; i < maxi; i++) - { - let tab = children[i]; - if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN) { - this.detachTab(tab, aInfo); - this.moveTabSubtreeTo(tab, insertBefore ? insertBefore._tPos - 1 : this.getLastTab(b)._tPos ); - } - else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD) { - this.detachTab(tab, aInfo); - if (i == 0) { - if (parentTab) { - this.attachTabTo(tab, parentTab, inherit(aInfo, { - dontExpand : true, - dontMove : true - })); - } - this.collapseExpandSubtree(tab, false); - this.deleteTabValue(tab, this.kSUBTREE_COLLAPSED); - } - else { - this.attachTabTo(tab, children[0], inherit(aInfo, { - dontExpand : true, - dontMove : true - })); - } - } - else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN && parentTab) { - this.attachTabTo(tab, parentTab, inherit(aInfo, { - dontExpand : true, - dontMove : true - })); - } - else { // aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN - this.detachTab(tab, aInfo); - } - } - }, - partAllChildren : function TSTBrowser_partAllChildren(aTab, aInfo) /* for backward compatibility */ - { - return this.detachAllChildren(aTab, aInfo); - }, - - detachTabs : function TSTBrowser_detachTabs(aTabs) - { - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (aTabs.indexOf(this.getParentTab(tab)) > -1) - continue; - this.detachAllChildren(tab, { - behavior : this.getCloseParentBehaviorForTab( - tab, - this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD - ) - }); - } - }, - partTabs : function TSTBrowser_partTabs(aTabs) /* for backward compatibility */ - { - return this.detachTabs(aTabs); - }, - - getCloseParentBehaviorForTab : function TSTBrowser_getCloseParentBehaviorForTab(aTab, aDefaultBehavior) - { - var closeParentBehavior = utils.getTreePref('closeParentBehavior'); - var closeRootBehavior = utils.getTreePref('closeRootBehavior'); - - var parentTab = this.getParentTab(aTab); - var behavior = aDefaultBehavior ? - aDefaultBehavior : - (!parentTab && closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN) ? - closeRootBehavior : - closeParentBehavior ; - if (behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD && - parentTab && - this.getChildTabs(parentTab).length == 1) - behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; - - return behavior; - }, - - updateTabsIndent : function TSTBrowser_updateTabsIndent(aTabs, aLevel, aJustNow) - { - if (!aTabs || !aTabs.length || !this._treeViewEnabled) - return; - - if (aLevel === void(0)) - aLevel = this.getAncestorTabs(aTabs[0]).length; - - var b = this.mTabBrowser; - var margin = this.indent < 0 ? this.baseIndent : this.indent ; - var indent = (this.maxTreeLevel < 0 ? aLevel : Math.min(aLevel, this.maxTreeLevel) ) * margin; - - var multirow = this.isMultiRow(); - if (multirow) { - let maxIndent = parseInt(aTabs[0].boxObject.height / 2); - indent = Math.min(aLevel * 3, maxIndent); - } - - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (!tab.parentNode) - continue; // ignore removed tabs - this.updateTabIndent(tab, indent, aJustNow); - tab.setAttribute(this.kNEST, aLevel); - this.updateCanCollapseSubtree(tab, aLevel); - this.updateTabsIndent(this.getChildTabs(tab), aLevel+1, aJustNow); - } - }, - updateTabsIndentWithDelay : function TSTBrowser_updateTabsIndentWithDelay(aTabs) - { - if (this.updateTabsIndentWithDelayTimer) - this.window.clearTimeout(this.updateTabsIndentWithDelayTimer); - - this.updateTabsIndentWithDelayTabs = this.updateTabsIndentWithDelayTabs.concat(aTabs); - this.updateTabsIndentWithDelayTimer = this.window.setTimeout(function(aSelf) { - var tabs = []; - for (let i = 0, maxi = aSelf.updateTabsIndentWithDelayTabs.length; i < maxi; i++) - { - let tab = aSelf.updateTabsIndentWithDelayTabs[i]; - if (tabs.indexOf(tab) < 0 && tab.parentNode) - tabs.push(tab); - } - aSelf.updateTabsIndentWithDelayTabs = []; - aSelf.updateTabsIndent(tabs); - aSelf.window.clearTimeout(aSelf.updateTabsIndentWithDelayTimer); - aSelf.updateTabsIndentWithDelayTimer = null; - tabs = null; - }, 0, this); - }, - updateTabsIndentWithDelayTimer : null, - - updateTabIndent : function TSTBrowser_updateTabIndent(aTab, aIndent, aJustNow) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - this.stopTabIndentAnimation(aTab); - - if (aTab.hasAttribute('pinned')) - return; - - if (!this.enableSubtreeIndent) - aIndent = 0; - - if (this.isMultiRow()) { - let colors = '-moz-border-'+this.indentTarget+'-colors:'+(function() { - var retVal = []; - for (var i = 1; i < aIndent; i++) - { - retVal.push('transparent'); - } - retVal.push('ThreeDShadow'); - return retVal.length == 1 ? 'none' : retVal.join(' ') ; - })()+' !important;'; - let boxes = this.document.getAnonymousNodes(aTab); - for (let i = 0, box = boxes.length; i < maxi; i++) - { - let box = boxes[i]; - if (box.nodeType != Node.ELEMENT_NODE) - continue; - box.setAttribute( - 'style', - box.getAttribute('style') - .replace(/(-moz-)?border-(top|bottom)(-[^:]*)?.*:[^;]+;?/g, '') + - '; border-'+this.indentTarget+': solid transparent '+aIndent+'px !important;'+colors - ); - } - return; - } - - if ( - !this.animationEnabled || - aJustNow || - this.indentDuration < 1 || - this.isCollapsed(aTab) - ) { - aTab.style.setProperty(this.indentCSSProp, aIndent+'px', 'important'); - return; - } - - var self = this; - var CSSTransitionEnabled = ('transition' in aTab.style || 'MozTransition' in aTab.style); - if (CSSTransitionEnabled) { - aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { - delete aTab.__treestyletab__updateTabIndentTask; - if (!self.isDestroying) - aTab.style.setProperty(self.indentCSSProp, aIndent+'px', 'important'); - return true; - }; - this.animationManager.addTask( - aTab.__treestyletab__updateTabIndentTask, - 0, 0, 1, this.window - ); - return; - } - - var startIndent = this.getPropertyPixelValue(aTab, this.indentCSSProp); - var delta = aIndent - startIndent; - var radian = 90 * Math.PI / 180; - aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { - if (self.isDestroying) - return true; - var indent, finished; - if (aTime >= aDuration) { - delete aTab.__treestyletab__updateTabIndentTask; - indent = aIndent; - finished = true; - } - else { - indent = startIndent + (delta * Math.sin(aTime / aDuration * radian)); - finished = false; - } - aTab.style.setProperty(self.indentCSSProp, indent+'px', 'important'); - if (finished) { - startIndent = null; - delta = null; - radian = null; - self = null; - aTab = null; - } - return finished; - }; - this.animationManager.addTask( - aTab.__treestyletab__updateTabIndentTask, - 0, 0, this.indentDuration, this.window - ); - }, - stopTabIndentAnimation : function TSTBrowser_stopTabIndentAnimation(aTab) - { - if (!aTab.parentNode) - return; // do nothing for closed tab! - this.animationManager.removeTask( - aTab.__treestyletab__updateTabIndentTask - ); - delete aTab.__treestyletab__updateTabIndentTask; - }, - - inheritTabIndent : function TSTBrowser_inheritTabIndent(aNewTab, aExistingTab) - { - var indent = this.getPropertyPixelValue(aExistingTab, this.indentCSSProp); - if (indent) - aNewTab.style.setProperty(this.indentCSSProp, indent+'px', 'important'); - else - aNewTab.style.removeProperty(this.indentCSSProp); - }, - - updateAllTabsIndent : function TSTBrowser_updateAllTabsIndent(aJustNow) - { - this.updateTabsIndent(this.rootTabs, 0, aJustNow); -// this.checkTabsIndentOverflow(); - }, - - checkTabsIndentOverflow : function TSTBrowser_checkTabsIndentOverflow(aDelay) - { - this.cancelCheckTabsIndentOverflow(); - this.checkTabsIndentOverflowTimer = this.window.setTimeout(function(aSelf) { - aSelf.checkTabsIndentOverflowTimer = null; - aSelf.checkTabsIndentOverflowCallback(); - }, aDelay || 100, this); - }, - cancelCheckTabsIndentOverflow : function TSTBrowser_cancelCheckTabsIndentOverflow() - { - if (this.checkTabsIndentOverflowTimer) { - this.window.clearTimeout(this.checkTabsIndentOverflowTimer); - this.checkTabsIndentOverflowTimer = null; - } - }, - checkTabsIndentOverflowTimer : null, - checkTabsIndentOverflowCallback : function TSTBrowser_checkTabsIndentOverflowCallback() - { - if (!utils.getTreePref('indent.autoShrink')) { - this.indent = -1; - return; - } - - var b = this.mTabBrowser; - var tabbarSize = b.mTabContainer.boxObject[this.invertedSizeProp]; - if (!tabbarSize) // don't update indent for collapsed tab bar - return; - - var tabs = Array.slice(b.mTabContainer.querySelectorAll( - 'tab['+this.kNEST+']:not(['+this.kNEST+'="0"]):not(['+this.kNEST+'=""])'+ - ':not(['+this.kCOLLAPSED+'="true"])'+ - ':not([hidden="true"])'+ - ':not([collapsed="true"])' - )); - if (!tabs.length) - return; - - var self = this; - tabs.sort(function(aA, aB) { return Number(aA.getAttribute(self.kNEST)) - Number(aB.getAttribute(self.kNEST)); }); - var nest = tabs[tabs.length-1].getAttribute(this.kNEST); - if (this.maxTreeLevel > -1) - nest = Math.min(nest, this.maxTreeLevel); - if (!nest) - return; - - var oldIndent = this.indent; - var indent = (oldIndent < 0 ? this.baseIndent : oldIndent ) * nest; - var maxIndentBase = Math.min( - this.getFirstNormalTab(b).boxObject[this.invertedSizeProp], - tabbarSize - ); - var isVertical = this.isVertical; - if (!isVertical) { - if (this._horizontalTabMaxIndentBase) - maxIndentBase = this._horizontalTabMaxIndentBase; - else - this._horizontalTabMaxIndentBase = maxIndentBase; - } - var maxIndent = maxIndentBase * (isVertical ? 0.33 : 0.5 ); - - var indentMin = utils.getTreePref(isVertical ? 'indent.min.vertical' : 'indent.min.horizontal'); - var indentUnit = Math.max(Math.floor(maxIndent / nest), indentMin); - if (indent > maxIndent) { - this.indent = indentUnit; - } - else { - this.indent = -1; - if ((this.baseIndent * nest) > maxIndent) - this.indent = indentUnit; - } - - if (oldIndent != this.indent) { - this.updateAllTabsIndent(); - } - }, - _horizontalTabMaxIndentBase : 0, - - updateCanCollapseSubtree : function TSTBrowser_updateCanCollapseSubtree(aTab, aLevel) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - if ( - !aLevel || - this.maxTreeLevel < 0 || - this.maxTreeLevel > aLevel - ) { - aTab.setAttribute(this.kALLOW_COLLAPSE, true); - this.collapseExpandSubtree(aTab, this.isSubtreeCollapsed(aTab)); - } - else { - this.collapseExpandSubtree(aTab, false); - aTab.removeAttribute(this.kALLOW_COLLAPSE); - } - }, - - updateTabsCount : function TSTBrowser_updateTabsCount(aTab, aDontUpdateAncestor) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - var count = this.document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER); - if (count) { - let value = this.getDescendantTabs(aTab).length; - if (this.counterRole == this.kCOUNTER_ROLE_ALL_TABS) - value += 1; - count.setAttribute('value', value); - } - if (!aDontUpdateAncestor) { - let parent = this.getParentTab(aTab); - if (parent) - this.updateTabsCount(parent); - } - }, - - updateAllTabsCount : function TSTBrowser_updateAllTabsCount() - { - var tabs = this.rootTabs; - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - this.updateTabsCount(tab, this); - } - }, - - promoteTooDeepLevelTabs : function TSTBrowser_promoteTooDeepLevelTabs(aParent) - { - if (this.maxTreeLevel < 0 || !this.maxTreeLevelPhisical) - return; - - var tabs = aParent ? this.getDescendantTabs(aParent) : this.getAllTabs(this.mTabBrowser) ; - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let level = parseInt(tab.getAttribute(this.kNEST) || 0); - if (level <= this.maxTreeLevel) - continue; - - let parent = this.getParentTab(tab); - let newParent = this.getParentTab(parent); - if (this.maxTreeLevel == 0 || !newParent) { - this.detachTab(aTab); - } - else { - let nextSibling = this.getNextTab(tab); - this.attachTabTo(tab, newParent, { - dontMove : true, - insertBefore : nextSibling - }); - } - } - }, - -/* move */ - - moveTabSubtreeTo : function TSTBrowser_moveTabSubtreeTo(aTab, aIndex) - { - if (!aTab || !aTab.parentNode) - return; - - var b = this.mTabBrowser; - this.subTreeMovingCount++; - - this.internallyTabMovingCount++; - b.moveTabTo(aTab, aIndex); - this.internallyTabMovingCount--; - - this.subTreeChildrenMovingCount++; - this.internallyTabMovingCount++; - var descendantTabs = this.getDescendantTabs(aTab); - for (let i = 0, maxi = descendantTabs.length; i < maxi; i++) - { - let descendantTab = descendantTabs[i]; - b.moveTabTo(descendantTab, aTab._tPos + i + (aTab._tPos < descendantTab._tPos ? 1 : 0 )); - } - this.internallyTabMovingCount--; - this.subTreeChildrenMovingCount--; - - this.subTreeMovingCount--; - }, - moveTabSubTreeTo : function(...aArgs) { - return this.moveTabSubtreeTo.apply(this, aArgs); - }, // obsolete, for backward compatibility - - moveTabLevel : function TSTBrowser_moveTabLevel(aEvent) - { - var b = this.mTabBrowser; - var parentTab = this.getParentTab(b.mCurrentTab); - if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) { - let prevTab = this.getPreviousSiblingTab(b.mCurrentTab); - if ((!parentTab && prevTab) || - (parentTab && b.mCurrentTab != this.getFirstChildTab(parentTab))) { - this.attachTabTo(b.mCurrentTab, prevTab); - b.mCurrentTab.focus(); - return true; - } - } - else if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT && parentTab) { - let grandParent = this.getParentTab(parentTab); - if (grandParent) { - this.attachTabTo(b.mCurrentTab, grandParent, { - insertBefore : this.getNextSiblingTab(parentTab) - }); - b.mCurrentTab.focus(); - return true; - } - else { - let nextTab = this.getNextSiblingTab(parentTab); - this.detachTab(b.mCurrentTab); - this.internallyTabMovingCount++; - if (nextTab) { - b.moveTabTo(b.mCurrentTab, nextTab._tPos - 1); - } - else { - b.moveTabTo(b.mCurrentTab, this.getLastTab(b)._tPos); - } - this.internallyTabMovingCount--; - b.mCurrentTab.focus(); - return true; - } - } - return false; - }, - - /** - * Imports tabs from another window with their tree structure. - * aOptions is an optional hash which can have two properties: - * * duplicate (boolean) - * * insertBefore (nsIDOMElement) - */ - importTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ - { - return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); - }, - duplicateTabs : function TSTBrowser_duplicateTabs(aTabs, aInsertBefore) /* PUBLIC API */ - { - return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore, duplicate : true }); - }, - moveTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ - { - return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); - }, - moveTabsInternal : function TSTBrowser_moveTabsInternal(aTabs, aOptions) - { - aOptions = aOptions || {}; - - var targetBrowser = this.mTabBrowser; - var sourceWindow = aTabs[0].ownerDocument.defaultView; - var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); - var sourceService = sourceBrowser.treeStyleTab; - - // prevent Multiple Tab Handler feature - targetBrowser.duplicatingSelectedTabs = true; - targetBrowser.movingSelectedTabs = true; - - var shouldClose = ( - !aOptions.duplicate && - sourceService.getAllTabs(sourceBrowser).length == aTabs.length - ); - var newTabs = []; - var treeStructure = sourceService.getTreeStructureFromTabs(aTabs); - - // Firefox fails to "move" collapsed tabs. So, expand them first - // and collapse them after they are moved. - var collapsedStates = sourceService.forceExpandTabs(aTabs);; - - var shouldResetSelection = ( - aTabs.every(function(aTab) { - return aTab.getAttribute('multiselected') == 'true'; - }) && - (sourceService != this || aOptions.duplicate) - ); - - var tabs = this.getTabs(targetBrowser); - var lastTabIndex = tabs[tabs.length -1]._tPos; - for (let i in aTabs) - { - let tab = aTabs[i]; - - if (shouldResetSelection) { - if ('MultipleTabService' in sourceWindow) - sourceWindow.MultipleTabService.setSelection(tab, false); - else - tab.removeAttribute('multiselected'); - } - - if (aOptions.duplicate) { - tab = this.duplicateTabAsOrphan(tab); - newTabs.push(tab); - } - else if (sourceService != this) { - tab = this.importTab(tab); - newTabs.push(tab); - } - - if (shouldResetSelection) { - if ('MultipleTabService' in sourceWindow) - sourceWindow.MultipleTabService.setSelection(tab, true); - else - tab.setAttribute('multiselected', true); - } - - lastTabIndex++; - - let newIndex = aOptions.insertBefore ? aOptions.insertBefore._tPos : lastTabIndex ; - if (aOptions.insertBefore && newIndex > tab._tPos) - newIndex--; - - this.internallyTabMovingCount++; - targetBrowser.moveTabTo(tab, newIndex); - this.collapseExpandTab(tab, false, true); - this.internallyTabMovingCount--; - } - - if (shouldClose) - sourceService.closeOwner(sourceBrowser); - - if (newTabs.length) - this.applyTreeStructureToTabs( - newTabs, - treeStructure, - collapsedStates.map(function(aCollapsed) { - return !aCollapsed - }) - ); - - for (let i = collapsedStates.length - 1; i > -1; i--) - { - sourceService.collapseExpandSubtree(aTabs[i], collapsedStates[i], true); - } - - // Multiple Tab Handler - targetBrowser.movingSelectedTabs = false; - targetBrowser.duplicatingSelectedTabs = false; - - return newTabs; - }, - - importTab : function TSTBrowser_importTab(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return null; - - var newTab = this.mTabBrowser.addTab(); - newTab.linkedBrowser.stop(); - newTab.linkedBrowser.docShell; - this.mTabBrowser.swapBrowsersAndCloseOther(newTab, aTab); - this.mTabBrowser.setTabTitle(newTab); - return newTab; - }, - - duplicateTabAsOrphan : function TSTBrowser_duplicateTabAsOrphan(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return null; - - var newTab = this.mTabBrowser.duplicateTab(aTab); - this.deleteTabValue(newTab, this.kCHILDREN); - this.deleteTabValue(newTab, this.kPARENT); - return newTab; - }, - - closeOwner : function TSTBrowser_closeOwner(aTabOwner) - { - var w = aTabOwner.ownerDocument.defaultView; - if (!w) - return; - if ('SplitBrowser' in w) { - if ('getSubBrowserFromChild' in w.SplitBrowser) { - var subbrowser = w.SplitBrowser.getSubBrowserFromChild(aTabOwner); - if (subbrowser) { - subbrowser.close(); - return; - } - } - if (w.SplitBrowser.browsers.length) - return; - } - w.close(); - }, - -/* collapse/expand */ - - collapseExpandSubtree : function TSTBrowser_collapseExpandSubtree(aTab, aCollapse, aJustNow) /* PUBLIC API */ - { - if (!aTab || !aTab.parentNode) - return; - - if (this.isSubtreeCollapsed(aTab) == aCollapse) - return; - - var b = this.mTabBrowser; - this.doingCollapseExpand = true; - - this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, aCollapse); - - var expandedTabs = this.getChildTabs(aTab); - var lastExpandedTabIndex = expandedTabs.length - 1; - for (let i = 0, maxi = expandedTabs.length; i < maxi; i++) - { - let childTab = expandedTabs[i]; - if (!aCollapse && !aJustNow && i == lastExpandedTabIndex) { - let self = this; - this.collapseExpandTab(childTab, aCollapse, aJustNow, function() { - self.scrollToTabSubtree(aTab); - }); - } - else - this.collapseExpandTab(childTab, aCollapse, aJustNow); - } - - if (aCollapse) - this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY); - - if (utils.getTreePref('indent.autoShrink') && - utils.getTreePref('indent.autoShrink.onlyForVisible')) - this.checkTabsIndentOverflow(); - - this.doingCollapseExpand = false; - }, - manualCollapseExpandSubtree : function(aTab, aCollapse, aJustNow) - { - this.collapseExpandSubtree(aTab, aCollapse, aJustNow); - if (!aCollapse) - this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true); - - if (utils.getTreePref('indent.autoShrink') && - utils.getTreePref('indent.autoShrink.onlyForVisible')) { - this.cancelCheckTabsIndentOverflow(); - if (!aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) { - var self = this; - var stillOver = false; - var id = this.getTabValue(aTab, this.kID); - aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave = function checkTabsIndentOverflowOnMouseLeave(aEvent, aDelayed) { - if (aEvent.type == 'mouseover') { - if (self.evaluateXPath( - 'ancestor-or-self::*[@' + self.kID + '="' + id + '"]', - aEvent.originalTarget || aEvent.target, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue) - stillOver = true; - return; - } - else if (!aDelayed) { - if (stillOver) { - stillOver = false; - } - self.Deferred.next(function() { - checkTabsIndentOverflowOnMouseLeave.call(null, aEvent, true); - }); - return; - } else if (stillOver) { - return; - } - var x = aEvent.clientX; - var y = aEvent.clientY; - var rect = aTab.getBoundingClientRect(); - if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) - return; - self.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - self.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave; - self.checkTabsIndentOverflow(); - }; - this.document.addEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - this.document.addEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); - } - } - }, - - collapseExpandTab : function TSTBrowser_collapseExpandTab(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation) - { - if (!aTab || !aTab.parentNode || !this.getParentTab(aTab)) - return; - - this.setTabValue(aTab, this.kCOLLAPSED, aCollapse); - this.updateTabCollapsed(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation); - - var data = { - collapsed : aCollapse - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, aTab, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED.replace(/^nsDOM/, ''), aTab, true, false, data); - - var b = this.mTabBrowser; - var parent; - if (aCollapse && aTab == b.selectedTab && (parent = this.getParentTab(aTab))) { - var newSelection = parent; - this.getAncestorTabs(aTab).some(function(aAncestor) { - if (!this.isCollapsed(aAncestor)) { - newSelection = aAncestor; - return true; - } - return false; - }, this); - b.selectedTab = newSelection; - } - - if (!this.isSubtreeCollapsed(aTab)) { - let tabs = this.getChildTabs(aTab); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - this.collapseExpandTab(tabs[i], aCollapse, aJustNow); - } - } - }, - updateTabCollapsed : function TSTBrowser_updateTabCollapsed(aTab, aCollapsed, aJustNow, aCallbackToRunOnStartAnimation) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - - this.stopTabCollapseAnimation(aTab); - - aTab.removeAttribute(this.kX_OFFSET); - aTab.removeAttribute(this.kY_OFFSET); - - if (!this.canCollapseSubtree(this.getRootTab(aTab))) - aCollapsed = false; - - aTab.setAttribute(this.kCOLLAPSING_PHASE, aCollapsed ? this.kCOLLAPSING_PHASE_TO_BE_COLLAPSED : this.kCOLLAPSING_PHASE_TO_BE_EXPANDED ); - - var CSSTransitionEnabled = ('transition' in aTab.style || 'MozTransition' in aTab.style); - - var maxMargin; - var offsetAttr; - var collapseProp = 'margin-'+this.collapseTarget; - let (firstTab = this.getFirstNormalTab(this.mTabBrowser)) { - if (this.isVertical) { - maxMargin = firstTab.boxObject.height; - offsetAttr = this.kY_OFFSET; - if (firstTab.style.height) - aTab.style.height = firstTab.style.height; - } - else { - maxMargin = firstTab.boxObject.width; - offsetAttr = this.kX_OFFSET; - if (firstTab.style.width) - aTab.style.width = firstTab.style.width; - } - } - - var startMargin, endMargin, startOpacity, endOpacity; - if (aCollapsed) { - startMargin = 0; - endMargin = maxMargin; - startOpacity = 1; - endOpacity = 0; - if (this.canStackTabs && this.getParentTab(aTab)) { - endOpacity = 1; - endMargin = this.kSTACKED_TAB_MARGIN; - } - } - else { - startMargin = maxMargin; - endMargin = 0; - startOpacity = 0; - endOpacity = 1; - if (this.canStackTabs && this.getParentTab(aTab)) { - startOpacity = 1; - startMargin = this.kSTACKED_TAB_MARGIN; - } - } - - if ( - !this.animationEnabled || - aJustNow || - this.collapseDuration < 1 // || -// !this.isVertical || -// !this.canCollapseSubtree(this.getParentTab(aTab)) - ) { - if (aCollapsed) - aTab.setAttribute(this.kCOLLAPSED_DONE, true); - else - aTab.removeAttribute(this.kCOLLAPSED_DONE); - aTab.removeAttribute(this.kCOLLAPSING_PHASE); - - // Pinned tabs are positioned by "margin-top", so - // we must not reset the property for pinned tabs. - // (However, we still must update "opacity".) - let pinned = aTab.getAttribute('pinned') == 'true'; - let canExpand = !pinned || this.collapseCSSProp != 'margin-top'; - - if (CSSTransitionEnabled) { - if (canExpand) - aTab.style.setProperty(this.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); - - if (endOpacity == 0) - aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); - else - aTab.style.removeProperty('opacity'); - } - else { - if (canExpand) - aTab.style.removeProperty(this.collapseCSSProp); - aTab.style.removeProperty('opacity'); - } - - if (aCallbackToRunOnStartAnimation) - aCallbackToRunOnStartAnimation(); - return; - } - - var deltaMargin = endMargin - startMargin; - var deltaOpacity = endOpacity - startOpacity; - - aTab.style.setProperty(this.collapseCSSProp, startMargin ? '-'+startMargin+'px' : '', 'important'); - aTab.style.setProperty('opacity', startOpacity == 1 ? '' : startOpacity, 'important'); - - if (!aCollapsed) { - aTab.setAttribute(offsetAttr, maxMargin); - aTab.removeAttribute(this.kCOLLAPSED_DONE); - } - - var radian = 90 * Math.PI / 180; - var self = this; - var firstFrame = true; - aTab.__treestyletab__updateTabCollapsedTask = function(aTime, aBeginning, aChange, aDuration) { - if (self.isDestroying) - return true; - if (firstFrame) { - // The callback must be started before offsetAttr is changed! - if (aCallbackToRunOnStartAnimation) - aCallbackToRunOnStartAnimation(); - if (CSSTransitionEnabled) { - aTab.style.setProperty(self.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); - aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); - } - } - firstFrame = false; - // If this is the last tab, negative scroll happens. - // Then, we shouldn't do animation. - var stopAnimation = false; - var scrollBox = self.scrollBox; - if (scrollBox) { - if (scrollBox._scrollbox) - scrollBox = scrollBox._scrollbox; - if ('scrollTop' in scrollBox && - (scrollBox.scrollTop < 0 || scrollBox.scrollLeft < 0)) { - scrollBox.scrollTop = 0; - scrollBox.scrollLeft = 0; - stopAnimation = true; - } - } - if (aTime >= aDuration || stopAnimation) { - delete aTab.__treestyletab__updateTabCollapsedTask; - if (aCollapsed) - aTab.setAttribute(self.kCOLLAPSED_DONE, true); - if (!CSSTransitionEnabled) { - aTab.style.removeProperty(self.collapseCSSProp); - aTab.style.removeProperty('opacity'); - } - aTab.removeAttribute(offsetAttr); - aTab.removeAttribute(self.kCOLLAPSING_PHASE); - - maxMargin = null; - offsetAttr = null; - startMargin = null; - endMargin = null; - startOpacity = null; - endOpacity = null; - deltaMargin = null; - deltaOpacity = null; - collapseProp = null; - radian = null; - self = null; - aTab = null; - - return true; - } - else { - if (!CSSTransitionEnabled) { - let power = Math.sin(aTime / aDuration * radian); - let margin = startMargin + (deltaMargin * power); - let opacity = startOpacity + (deltaOpacity * power); - aTab.style.setProperty(self.collapseCSSProp, margin ? '-'+margin+'px' : '', 'important'); - aTab.style.setProperty('opacity', opacity == 1 ? '' : opacity, 'important'); - } - aTab.setAttribute(offsetAttr, maxMargin); - return false; - } - }; - this.animationManager.addTask( - aTab.__treestyletab__updateTabCollapsedTask, - 0, 0, this.collapseDuration, this.window - ); - }, - kOPACITY_RULE_REGEXP : /opacity\s*:[^;]+;?/, - kSTACKED_TAB_MARGIN : 15, - stopTabCollapseAnimation : function TSTBrowser_stopTabCollapseAnimation(aTab) - { - if (!aTab.parentNode) - return; // do nothing for closed tab! - - this.animationManager.removeTask( - aTab.__treestyletab__updateTabCollapsedTask - ); - }, - - collapseExpandTreesIntelligentlyFor : function TSTBrowser_collapseExpandTreesIntelligentlyFor(aTab, aJustNow) - { - if (!aTab || - !aTab.parentNode || - this.doingCollapseExpand || - !this.canCollapseSubtree(aTab)) - return; - - var b = this.mTabBrowser; - var sameParentTab = this.getParentTab(aTab); - var expandedAncestors = [aTab].concat(this.getAncestorTabs(aTab)) - .map(function(aAncestor) { - return aAncestor.getAttribute(this.kID); - }, this) - .join('|'); - - var xpathResult = this.evaluateXPath( - 'child::xul:tab[@'+this.kCHILDREN+' and not(@'+this.kCOLLAPSED+'="true") and not(@'+this.kSUBTREE_COLLAPSED+'="true") and @'+this.kID+' and not(contains("'+expandedAncestors+'", @'+this.kID+')) and not(@hidden="true")]', - b.mTabContainer - ); - for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) - { - let dontCollapse = false; - let collapseTab = xpathResult.snapshotItem(i); - - let parentTab = this.getParentTab(collapseTab); - if (parentTab) { - dontCollapse = true; - if (!this.isSubtreeCollapsed(parentTab)) { - this.getAncestorTabs(collapseTab).some(function(aAncestor) { - if (expandedAncestors.indexOf(aAncestor.getAttribute(this.kID)) < 0) - return false; - dontCollapse = false; - return true; - }, this); - } - } - - let manuallyExpanded = this.getTabValue(collapseTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; - if (!dontCollapse && !manuallyExpanded) - this.collapseExpandSubtree(collapseTab, true, aJustNow); - } - - this.collapseExpandSubtree(aTab, false, aJustNow); - }, - - collapseExpandAllSubtree : function TSTBrowser_collapseExpandAllSubtree(aCollapse, aJustNow) - { - var tabs = this.mTabBrowser.mTabContainer.querySelectorAll( - 'tab['+this.kID+']['+this.kCHILDREN+']'+ - ( - aCollapse ? - ':not(['+this.kSUBTREE_COLLAPSED+'="true"])' : - '['+this.kSUBTREE_COLLAPSED+'="true"]' - ) - ); - for (var i = 0, maxi = tabs.length; i < maxi; i++) - { - this.collapseExpandSubtree(tabs[i], aCollapse, aJustNow); - } - }, - -/* scroll */ - - scrollTo : function TSTBrowser_scrollTo(aEndX, aEndY) - { - if (this.deferredTasks['cancelPerformingAutoScroll']) - return; - - if (this.animationEnabled || this.smoothScrollEnabled) { - this.smoothScrollTo(aEndX, aEndY); - } - else { - try { - this.cancelPerformingAutoScroll(); - this.scrollBoxObject.scrollTo(aEndX, aEndY); - } - catch(e) { - } - } - }, - - smoothScrollTo : function TSTBrowser_smoothScrollTo(aEndX, aEndY, aDuration) - { - this.cancelPerformingAutoScroll(true); - - var b = this.mTabBrowser; - var scrollBoxObject = this.scrollBoxObject; - var x = {}, y = {}; - scrollBoxObject.getPosition(x, y); - var startX = x.value; - var startY = y.value; - var deltaX = aEndX - startX; - var deltaY = aEndY - startY; - - var arrowscrollbox = scrollBoxObject.element.parentNode; - if ( - arrowscrollbox && - ( - arrowscrollbox.localName != 'arrowscrollbox' || - !('_isScrolling' in arrowscrollbox) - ) - ) - arrowscrollbox = null; - - var radian = 90 * Math.PI / 180; - var self = this; - this.smoothScrollTask = function(aTime, aBeginning, aChange, aDuration) { - if (self.isDestroying) - return true; - var scrollBoxObject = self.scrollBoxObject; - if (aTime >= aDuration || self.deferredTasks['cancelPerformingAutoScroll']) { - if (!self.deferredTasks['cancelPerformingAutoScroll']) { - scrollBoxObject.scrollTo(aEndX, aEndY); - - /** - * When there is any expanding tab, we have to retry to scroll. - * if the scroll box was expanded. - */ - let oldSize = self._getMaxScrollSize(scrollBoxObject); - let key = 'smoothScrollTo_'+parseInt(Math.random() * 65000); - (self.deferredTasks[key] = self.Deferred.next(function() { - let newSize = self._getMaxScrollSize(scrollBoxObject); - let lastTab = self.getLastVisibleTab(self.mTabBrowser); - if ( - // scroll size can be expanded by expanding tabs. - oldSize[0] < newSize[0] || oldSize[1] < newSize[1] || - // there are still animating tabs - self.getXOffsetOfTab(lastTab) || self.getYOffsetOfTab(lastTab) || - self.mTabBrowser.mTabContainer.querySelector('tab['+self.kCOLLAPSING_PHASE+'="'+self.kCOLLAPSING_PHASE_TO_BE_EXPANDED+'"]') - ) - self.smoothScrollTo(aEndX, aEndY, parseInt(aDuration * 0.5)); - self = null; - scrollBoxObject = null; - })).error(self.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks[key]; - }); - } - - b = null; - x = null; - y = null; - startX = null; - startY = null; - radian = null; - self.smoothScrollTask = null; - - return true; - } - - var power = Math.sin(aTime / aDuration * radian); - var newX = startX + parseInt(deltaX * power); - var newY = startY + parseInt(deltaY * power); - scrollBoxObject.scrollTo(newX, newY); - return false; - }; - this.animationManager.addTask( - this.smoothScrollTask, - 0, 0, this.smoothScrollDuration || aDuration, this.window - ); - }, - _getMaxScrollSize : function(aScrollBoxObject) { - var x = {}, y = {}; - aScrollBoxObject.getPosition(x, y); - var w = {}, h = {}; - aScrollBoxObject.getScrolledSize(w, h); - var maxX = Math.max(0, w.value - aScrollBoxObject.width); - var maxY = Math.max(0, h.value - aScrollBoxObject.height); - return [maxX, maxY]; - }, - smoothScrollTask : null, - - scrollToTab : function TSTBrowser_scrollToTab(aTab, aOnlyWhenCurrentTabIsInViewport) - { - if (!aTab || !aTab.parentNode || this.isTabInViewport(aTab)) - return; - - var b = this.mTabBrowser; - - var scrollBoxObject = this.scrollBoxObject; - var w = {}, h = {}; - try { - scrollBoxObject.getScrolledSize(w, h); - } - catch(e) { // Tab Mix Plus (or others) - return; - } - - var targetTabBox = this.getFutureBoxObject(aTab); - var baseTabBox = this.getFirstNormalTab(b).boxObject; - - var targetX = (targetTabBox.screenX < scrollBoxObject.screenX) ? - (targetTabBox.screenX - baseTabBox.screenX) - (targetTabBox.width * 0.5) : - (targetTabBox.screenX - baseTabBox.screenX) - scrollBoxObject.width + (targetTabBox.width * 1.5) ; - - var targetY = (targetTabBox.screenY < scrollBoxObject.screenY) ? - (targetTabBox.screenY - baseTabBox.screenY) - (targetTabBox.height * 0.5) : - (targetTabBox.screenY - baseTabBox.screenY) - scrollBoxObject.height + (targetTabBox.height * 1.5) ; - - if (aOnlyWhenCurrentTabIsInViewport && b.selectedTab != aTab) { - let box = b.selectedTab.boxObject; - if (targetTabBox.screenX - box.screenX + baseTabBox.width > scrollBoxObject.width || - targetTabBox.screenY - box.screenY + baseTabBox.height > scrollBoxObject.height) - return; - } - - this.scrollTo(targetX, targetY); - }, - - scrollToTabSubtree : function TSTBrowser_scrollToTabSubtree(aTab) - { - if (!aTab.parentNode) // do nothing for closed tab! - return; - var b = this.mTabBrowser; - var descendant = this.getDescendantTabs(aTab); - var parentTabBox = aTab.boxObject; - - var containerPosition = this.tabStrip.boxObject[this.screenPositionProp]; - var containerSize = this.tabStrip.boxObject[this.sizeProp]; - var parentPosition = parentTabBox[this.screenPositionProp]; - - var lastVisible = aTab; - for (let i = descendant.length-1; i > -1; i--) - { - let tab = descendant[i]; - if (this.isCollapsed(tab)) - continue; - - let box = this.getFutureBoxObject(tab); - if (box[this.screenPositionProp] + box[this.sizeProp] - parentPosition > containerSize) - continue; - - lastVisible = tab; - break; - } - - if (this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) - return; - - var lastPosition = lastVisible.boxObject[this.screenPositionProp]; - var tabSize = lastVisible.boxObject[this.sizeProp]; - - if (lastPosition - parentPosition + tabSize > containerSize - tabSize) { // out of screen - var endPos = parentPosition - this.getFirstNormalTab(b).boxObject[this.screenPositionProp] - tabSize * 0.5; - var endX = this.isVertical ? 0 : endPos ; - var endY = this.isVertical ? endPos : 0 ; - this.scrollTo(endX, endY); - } - else if (!this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) { - this.scrollToTab(aTab); - } - else if (this.isTabInViewport(aTab) && !this.isTabInViewport(lastVisible)) { - this.scrollToTab(lastVisible); - } - else if (parentPosition < containerPosition) { - this.scrollToTab(aTab); - } - else { - this.scrollToTab(lastVisible); - } - }, - - notifyBackgroundTab : function TSTBrowser_notifyBackgroundTab() - { - var animateElement = this.mTabBrowser.mTabContainer._animateElement; - var attrName = this.kBG_NOTIFY_PHASE; - if (!animateElement) - return; - - if (this.deferredTasks['notifyBackgroundTab']) - this.deferredTasks['notifyBackgroundTab'].cancel(); - - if (!animateElement.hasAttribute(attrName)) - animateElement.setAttribute(attrName, 'ready'); - - var self = this; - (this.deferredTasks['notifyBackgroundTab'] = this.Deferred - .next(function() { - animateElement.setAttribute(attrName, 'notifying'); - })) - .wait(0.15) - .next(function() { - animateElement.setAttribute(attrName, 'finish'); - }) - .wait(1) - .next(function() { - animateElement.removeAttribute(attrName); - }) - .error(this.defaultDeferredErrorHandler).next(function() { - delete self.deferredTasks['notifyBackgroundTab']; - }); - }, - - restoreTree : function TSTBrowser_restoreTree() - { - if (!this.needRestoreTree || this.useTMPSessionAPI) - return; - - this.needRestoreTree = false; - - if (this.useTMPSessionAPI && prefs.getPref('extensions.tabmix.sessions.manager')) - return; - - var level = utils.getTreePref('restoreTree.level'); - - var tabs = this.getAllTabs(this.mTabBrowser); - var tabsToRestore = 0; // it is the number of pending tabs. - if (utils.SessionStoreInternal && - utils.SessionStoreInternal._browserEpochs) { - let browserEpochs = utils.SessionStoreInternal._browserEpochs; - tabsToRestore = tabs.filter(function(aTab) { - return browserEpochs.has(aTab.linkedBrowser.permanentKey); - }).length; - } - else { - Components.utils.reportError(new Error('There is no property named "_browserEpochs"!!')); - } - - dump('TSTBrowser::restoreTree\n'); - dump(' level = '+level+'\n'); - dump(' tabsToRestore = '+tabsToRestore+'\n'); - if ( - level <= this.kRESTORE_TREE_LEVEL_NONE || - tabsToRestore <= 1 - ) - return; - - var onlyVisible = level <= this.kRESTORE_TREE_ONLY_VISIBLE; - tabs = tabs.filter(function(aTab) { - var toBeRestored = utils.isTabNotRestoredYet(aTab); - // This cache can be set to false by initTab(), but now we not the correct status. - // Because this cached status is used by other methods, we must update it now. - aTab.linkedBrowser.__treestyletab__toBeRestored = toBeRestored; - return ( - toBeRestored && - (!onlyVisible || !aTab.hidden) - ); - }); - dump(' restoring member tabs = '+tabs.length+'\n'); - if (tabs.length <= 1) - return; - - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - let currentId = tab.getAttribute(this.kID); - if (this.tabsHash[currentId] == tab) - delete this.tabsHash[currentId]; - - this.resetTabState(tab); - - tab.setAttribute(this.kID, currentId); // to fallback to it - let [id, duplicated] = this._restoreTabId(tab); - - this.setTabValue(tab, this.kID, id); - this.tabsHash[id] = tab; - - tab.__treestyletab__restoreState = this.RESTORE_STATE_READY_TO_RESTORE; - tab.__treestyletab__duplicated = duplicated; - } - - this.updateAllTabsIndent(true); - - // restore tree from bottom safely - tabs.reverse() - .filter(this.restoreOneTab, this) - .forEach(this.updateInsertionPositionInfo, this); - }, - restoreOneTab : function TSTBrowser_restoreOneTab(aTab) - { - if (aTab.__treestyletab__restoreState != this.RESTORE_STATE_READY_TO_RESTORE) - return false; - - let duplicated = aTab.__treestyletab__duplicated; - - let children = this.getTabValue(aTab, this.kCHILDREN); - if (children) { - this.deleteTabValue(aTab, this.kCHILDREN); - let manuallyExpanded = this.getTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; - let subTreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; - subTreeCollapsed = this._restoreSubtreeCollapsedState(aTab, subTreeCollapsed); - let self = this; - this._restoreChildTabsRelation(aTab, children, duplicated, function(aChild) { - /** - * When the child has the reference to the parent tab, attachTabTo() - * does nothing. To ensure they are correctly related, we have to - * clear the relation here. - */ - self.deleteTabValue(aChild, self.kPARENT); - let refId = self.getTabValue(aChild, self.kINSERT_BEFORE); - if (refId && duplicated) - refId = self.redirectId(refId); - return { - forceExpand : true, // to prevent to collapse the selected tab - dontAnimate : true, - insertBefore : self.getTabById(refId) - }; - }); - this.collapseExpandSubtree(aTab, subTreeCollapsed, true); - if (manuallyExpanded && !subTreeCollapsed) - this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true); - else - this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY); - } - - delete aTab.__treestyletab__duplicated; - aTab.__treestyletab__restoreState = this.RESTORE_STATE_STRUCTURE_RESTORED; - return true - }, - -/* sub modules */ - - get tabbarDNDObserver() - { - if (!this._tabbarDNDObserver) { - this._tabbarDNDObserver = new TabbarDNDObserver(this.mTabBrowser); - } - return this._tabbarDNDObserver; - }, - - get panelDNDObserver() - { - if (!this._panelDNDObserver) { - this._panelDNDObserver = new TabpanelDNDObserver(this.mTabBrowser); - } - return this._panelDNDObserver; - }, - -/* proxying for window service */ - _callWindowServiceMethod : function TSTBrowser_callWindowServiceMethod(aName, aArgs) - { - return this.windowService[aName].apply(this.windowService, aArgs); - }, - isPopupShown : function TSTBrowser_isPopupShown(...aArgs) { - return this._callWindowServiceMethod('isPopupShown', aArgs); - }, - updateTabsOnTop : function TSTBrowser_updateTabsOnTop(...aArgs) { - return this._callWindowServiceMethod('updateTabsOnTop', aArgs); - }, - registerTabFocusAllowance : function TSTBrowser_registerTabFocusAllowance(...aArgs) { - return this._callWindowServiceMethod('registerTabFocusAllowance', aArgs); - }, - isPopupShown : function TSTBrowser_isPopupShown(...aArgs) { - return this._callWindowServiceMethod('isPopupShown', aArgs); - }, - toggleAutoHide : function TSTBrowser_toggleAutoHide(...aArgs) { - return this._callWindowServiceMethod('toggleAutoHide', aArgs); - }, - -/* show/hide tab bar */ - get autoHide() - { - if (!this._autoHide) { - this._autoHide = new AutoHideBrowser(this.mTabBrowser); - } - return this._autoHide; - }, - - // for backward compatibility - get tabbarShown() { return this.autoHide.expanded; }, - set tabbarShown(aValue) { if (aValue) this.autoHide.show(); else this.autoHide.hide(); return aValue; }, - get tabbarExpanded() { return this.autoHide.expanded; }, - set tabbarExpanded(aValue) { return this.tabbarShown = aValue; }, - get tabbarResizing() { return this.autoHide.isResizing; }, - set tabbarResizing(aValue) { return this.autoHide.isResizing = aValue; }, - get togglerSize() { return this.autoHide.togglerSize; }, - set togglerSize(aValue) { return this.autoHide.togglerSize = aValue; }, - get sensitiveArea() { return this.autoHide.sensitiveArea; }, - set sensitiveArea(aValue) { return this.autoHide.sensitiveArea = aValue; }, - get lastMouseDownTarget() { return this.autoHide.lastMouseDownTarget; }, - set lastMouseDownTarget(aValue) { return this.autoHide.lastMouseDownTarget = aValue; }, - - get tabbarWidth() { return this.autoHide.width; }, - set tabbarWidth(aValue) { return this.autoHide.widthwidth = aValue; }, - get tabbarHeight() { return this.autoHide.height; }, - set tabbarHeight(aValue) { return this.autoHide.height = aValue; }, - get splitterWidth() { return this.autoHide.splitterWidth; }, - - get autoHideShown() { return this.autoHide.expanded; }, - set autoHideShown(aValue) { return this.tabbarShown = aValue; }, - get autoHideXOffset() { return this.autoHide.XOffset; }, - get autoHideYOffset() { return this.autoHide.YOffset; }, - get autoHideMode() { return this.autoHide.mode; }, - set autoHideMode(aValue) { return this.autoHide.mode = aValue; }, - - updateAutoHideMode : function TSTBrowser_updateAutoHideMode() { this.autoHide.updateAutoHideMode(); }, - showHideTabbarInternal : function TSTBrowser_showHideTabbarInternal(aReason) { this.autoHide.showHideInternal(aReason); }, - showTabbar : function TSTBrowser_showTabbar(aReason) { this.autoHide.show(aReason); }, - hideTabbar : function TSTBrowser_hideTabbar(aReason) { this.autoHide.hide(aReason); }, - redrawContentArea : function TSTBrowser_redrawContentArea() { this.autoHide.redrawContentArea(); }, - drawTabbarCanvas : function TSTBrowser_drawTabbarCanvas() { this.autoHide.drawBG(); }, - get splitterBorderColor() { this.autoHide.splitterBorderColor; }, - clearTabbarCanvas : function TSTBrowser_clearTabbarCanvas() { this.autoHide.clearBG(); }, - updateTabbarTransparency : function TSTBrowser_updateTabbarTransparency() { this.autoHide.updateTransparency(); }, - - get autoHideEnabled() { return this.autoHide.enabled; }, - set autoHideEnabled(aValue) { return this.autoHide.enabled = aValue; }, - startAutoHide : function TSTBrowser_startAutoHide() { this.autoHide.start(); }, - endAutoHide : function TSTBrowser_endAutoHide() { this.autoHide.end(); }, - startAutoHideForFullScreen : function TSTBrowser_startAutoHideForFullScreen() { this.autoHide.startForFullScreen(); }, - endAutoHideForFullScreen : function TSTBrowser_endAutoHideForFullScreen() { this.autoHide.endForFullScreen(); }, - - startListenMouseMove : function TSTBrowser_startListenMouseMove() { this.autoHide.startListenMouseMove(); }, - endListenMouseMove : function TSTBrowser_endListenMouseMove() { this.autoHide.endListenMouseMove(); }, - get shouldListenMouseMove() { return this.autoHide.shouldListenMouseMove; }, - showHideTabbarOnMousemove : function TSTBrowser_showHideTabbarOnMousemove() { this.autoHide.showHideOnMousemove(); }, - cancelShowHideTabbarOnMousemove : function TSTBrowser_cancelShowHideTabbarOnMousemove() { this.autoHide.cancelShowHideOnMousemove(); }, - showTabbarForFeedback : function TSTBrowser_showTabbarForFeedback() { this.autoHide.showForFeedback(); }, - delayedShowTabbarForFeedback : function TSTBrowser_delayedShowTabbarForFeedback() { this.autoHide.delayedShowForFeedback(); }, - cancelHideTabbarForFeedback : function TSTBrowser_cancelHideTabbarForFeedback() { this.autoHide.cancelHideForFeedback(); } - -}); - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2011-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * wanabe + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TreeStyleTabBrowser']; + +const DEBUG = false; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); +XPCOMUtils.defineLazyModuleGetter(this, 'FullTooltipManager', 'resource://treestyletab-modules/fullTooltip.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'TabbarDNDObserver', 'resource://treestyletab-modules/tabbarDNDObserver.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'TabpanelDNDObserver', 'resource://treestyletab-modules/tabpanelDNDObserver.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'AutoHideBrowser', 'resource://treestyletab-modules/autoHide.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUIShowHideObserver', 'resource://treestyletab-modules/browserUIShowHideObserver.js'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); + +Cu.import('resource://treestyletab-modules/window.js'); + +function TreeStyleTabBrowser(aWindowService, aTabBrowser) +{ + this.id = Date.now() + '-' + parseInt(Math.random() * 65000); + + this.windowService = aWindowService; + this.window = aWindowService.window; + this.document = aWindowService.document; + + this.mTabBrowser = aTabBrowser; + aTabBrowser.treeStyleTab = this; + + this.tabVisibilityChangedTabs = []; + this.updateTabsIndentWithDelayTabs = []; + this.deferredTasks = {}; + + this.tabVisibilityChangedTabs = []; + this._updateFloatingTabbarReason = 0; + this.internallyTabMovingCount = 0; + this.subTreeMovingCount = 0; + this.subTreeChildrenMovingCount = 0; + this._treeViewEnabled = true; +} + +TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, { + + kMENUITEM_RELOADSUBTREE : 'context-item-reloadTabSubtree', + kMENUITEM_RELOADCHILDREN : 'context-item-reloadDescendantTabs', + kMENUITEM_REMOVESUBTREE : 'context-item-removeTabSubtree', + kMENUITEM_REMOVECHILDREN : 'context-item-removeDescendantTabs', + kMENUITEM_REMOVEALLTABSBUT : 'context-item-removeAllTabsButThisTree', + kMENUITEM_COLLAPSEEXPAND_SEPARATOR : 'context-separator-collapseExpandAll', + kMENUITEM_COLLAPSE : 'context-item-collapseAllSubtree', + kMENUITEM_EXPAND : 'context-item-expandAllSubtree', + kMENUITEM_AUTOHIDE_SEPARATOR : 'context-separator-toggleAutoHide', + kMENUITEM_AUTOHIDE : 'context-item-toggleAutoHide', + kMENUITEM_FIXED : 'context-item-toggleFixed', + kMENUITEM_BOOKMARKSUBTREE : 'context-item-bookmarkTabSubtree', + + kMENUITEM_CLOSE_TABS_TO_END : 'context_closeTabsToTheEnd', + + mTabBrowser : null, + + indent : -1, + indentProp : 'margin', + indentTarget : 'left', + indentCSSProp : 'margin-left', + collapseTarget : 'top', + collapseCSSProp : 'margin-top', + screenPositionProp : 'screenY', + offsetProp : 'offsetY', + translateFunction : 'translateY', + sizeProp : 'height', + invertedScreenPositionProp : 'screenX', + invertedSizeProp : 'width', + startProp : 'top', + endProp : 'bottom', + + maxTreeLevelPhisical : false, + + needRestoreTree : false, + +/* elements */ + + get browser() + { + return this.mTabBrowser; + }, + + get container() + { + if (!this._container) { + this._container = this.document.getElementById('appcontent'); + } + return this._container; + }, + _container : null, + + get scrollBox() + { + return ( // Tab Mix Plus + utils.getTreePref('compatibility.TMP') && + this.document.getAnonymousElementByAttribute(this.mTabBrowser.mTabContainer, 'class', 'tabs-frame') + ) || + this.mTabBrowser.mTabContainer.mTabstrip; + }, + get scrollBoxObject() + { + var node = this.scrollBox; + if (node._scrollbox) + node = node._scrollbox; + return (node.scrollBoxObject || node.boxObject) + .QueryInterface(Ci.nsIScrollBoxObject); // for Tab Mix Plus (ensure scrollbox-ed) + }, + + get splitter() + { + var d = this.document; + return d.getAnonymousElementByAttribute(this.mTabBrowser, 'class', this.kSPLITTER) || + d.getAnonymousElementByAttribute(this.mTabBrowser, 'id', 'tabkit-splitter'); // Tab Kit + }, + + get tabStripPlaceHolder() + { + return this._tabStripPlaceHolder; + }, + set tabStripPlaceHolder(value) + { + return (this._tabStripPlaceHolder = value); + }, + +/* properties */ + + get maxTreeLevel() + { + return this._maxTreeLevel; + }, + set maxTreeLevel(aValue) + { + this._maxTreeLevel = aValue; + this.setTabbrowserAttribute(this.kMAX_LEVEL, this._maxTreeLevel || '0'); + this.enableSubtreeIndent = this._maxTreeLevel != 0; + return aValue; + }, + _maxTreeLevel : -1, + + get baseIndent() { + return this.isVertical ? this.baseIndentVertical : this.baseIndentHorizontal; + }, + + get enableSubtreeIndent() + { + return this._enableSubtreeIndent; + }, + set enableSubtreeIndent(aValue) + { + this._enableSubtreeIndent = aValue; + this.setTabbrowserAttribute(this.kINDENTED, this._enableSubtreeIndent ? 'true' : null); + return aValue; + }, + _enableSubtreeIndent : true, + + get allowSubtreeCollapseExpand() + { + return this._allowSubtreeCollapseExpand; + }, + set allowSubtreeCollapseExpand(aValue) + { + this._allowSubtreeCollapseExpand = aValue; + this.setTabbrowserAttribute(this.kALLOW_COLLAPSE, this._allowSubtreeCollapseExpand ? 'true' : null); + return aValue; + }, + _allowSubtreeCollapseExpand : true, + + get fixed() + { + var orient = this.isVertical ? 'vertical' : 'horizontal' ; + if (!this.windowService.preInitialized) + return utils.getTreePref('tabbar.fixed.'+orient); + + var b = this.mTabBrowser; + if (!b) + return false; + return b.getAttribute(this.kFIXED+'-'+orient) == 'true'; + }, + set fixed(aValue) + { + this.setTabbrowserAttribute(this.kFIXED, aValue || null, this.mTabBrowser); + return aValue; + }, + get isFixed() // for backward compatibility + { + return this.fixed; + }, + + get position() /* PUBLIC API */ + { + return ( + // Don't touch to the element before it is initialized by XBL constructor. + (this.windowService.preInitialized && this.browser.getAttribute(this.kTABBAR_POSITION)) || + this.base.position + ); + }, + set position(aValue) + { + var position = String(aValue).toLowerCase(); + if (!position || !/^(top|bottom|left|right)$/.test(position)) + position = 'top'; + + if (position == this.position) + return position; + + if ('UndoTabService' in this.window && this.window.UndoTabService.isUndoable()) { + var current = this.position; + var self = this; + this.window.UndoTabService.doOperation( + function() { + self._changeTabbarPosition(position); + }, + { + label : utils.treeBundle.getString('undo_changeTabbarPosition_label'), + name : 'treestyletab-changeTabbarPosition-private', + data : { + oldPosition : current, + newPosition : position, + target : self.mTabBrowser.id + } + } + ); + } + else { + this._changeTabbarPosition(position); + } + return position; + }, + _changeTabbarPosition : function TSTBrowser_changeTabbarPosition(aNewPosition) + { + if (this.deferredTasks['_changeTabbarPosition']) + this.deferredTasks['_changeTabbarPosition'].cancel(); + + var oldPosition = this.position; + this.fireTabbarPositionEvent(true, oldPosition, aNewPosition); + + this.initTabbar(aNewPosition, oldPosition); + this.reinitAllTabs(); + + var self = this; + (this.deferredTasks['_changeTabbarPosition'] = this.Deferred.next(function() { + self.checkTabsIndentOverflow(); + self.fireTabbarPositionEvent(false, oldPosition, aNewPosition); + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['_changeTabbarPosition']; + }); + }, + +/* status getters */ + + get isVertical() + { + if (!this.windowService.preInitialized) + return ['left', 'right'].indexOf(this.position) > -1; + + var b = this.mTabBrowser; + if (!b) + return false; + + if (b.hasAttribute(this.kMODE)) + return b.getAttribute(this.kMODE) == 'vertical'; + + var box = this.scrollBox || b.mTabContainer ; + return (box.getAttribute('orient') || this.window.getComputedStyle(box, '').getPropertyValue('-moz-box-orient')) == 'vertical'; + }, + + get isVisible() + { + var bar = this.ownerToolbar; + var style = this.window.getComputedStyle(bar, ''); + if (style.visibility != 'visible' || style.display == 'none') + return false; + + var box = bar.boxObject; + return !!(box.width || box.height); + }, + + isFloating : true, // for backward compatibility (but this should be removed) + + get ownerToolbar() + { + return this.evaluateXPath( + 'ancestor-or-self::xul:toolbar[1]', + this.mTabBrowser.tabContainer, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + get canStackTabs() + { + return ( + !this.isVertical && + this.canCollapseSubtree() && + utils.getTreePref('stackCollapsedTabs') + ); + }, + + get counterRole() + { + return this.isVertical ? this.counterRoleVertical : this.counterRoleHorizontal ; + }, + + get isDestroying() + { + return !this.mTabBrowser || !this.mTabBrowser.mTabContainer; + }, + +/* utils */ + +/* get tab contents */ + + getTabLabel : function TSTBrowser_getTabLabel(aTab) + { + var d = this.document; + var label = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-stack') || // Mac OS X + ( // Tab Mix Plus + utils.getTreePref('compatibility.TMP') && + d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-container') + ) || + d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text tab-label'); + return label; + }, + + getTabClosebox : function TSTBrowser_getTabClosebox(aTab) + { + var d = this.document; + var close = ( // Tab Mix Plus + utils.getTreePref('compatibility.TMP') && + d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button always-right') + ) || + d.getAnonymousElementByAttribute(aTab, 'anonid', 'close-button') || // with Australis + d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button'); + return close; + }, + + getTabTwisty : function TSTBrowser_getTabTwisty(aTab) + { + return this.document.getAnonymousElementByAttribute(aTab, 'class', this.kTWISTY); + }, + + getTabTwistyAnchorNode : function TSTBrowser_getTabTwistyAnchorNode(aTab) + { + return this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon') || // Tab Mix Plus + this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber'); + }, + + getTabFromTabbarEvent : function TSTBrowser_getTabFromTabbarEvent(aEvent) + { + if ( + !this.shouldDetectClickOnIndentSpaces || + !this.getAncestorTabbarFromEvent(aEvent) || + this.isEventFiredOnClickable(aEvent) || + this.getSplitterFromEvent(aEvent) + ) + return null; + return this.getTabFromCoordinates(aEvent); + }, + getTabFromCoordinates : function TSTBrowser_getTabFromCoordinates(aCoordinates, aTabs) + { + var tab = this.document.elementFromPoint(aCoordinates.clientX, aCoordinates.clientY); + if (tab && tab.localName == 'tab' && (!aTabs || aTabs.indexOf(tab) > -1)) + return tab; + + var positionCoordinate = aCoordinates[this.screenPositionProp]; + + var tabs = aTabs || this.getTabs(this.mTabBrowser); + if (!tabs.length || + this.getTabActualScreenPosition(tabs[0]) > positionCoordinate || + this.getTabActualScreenPosition(tabs[tabs.length-1]) < positionCoordinate) + return null; + + var low = 0; + var high = tabs.length - 1; + while (low <= high) { + let middle = Math.floor((low + high) / 2); + let position = this.getTabActualScreenPosition(tabs[middle]); + if (position > positionCoordinate) { + high = middle - 1; + } + else if (position + tabs[middle].boxObject[this.sizeProp] < positionCoordinate) { + low = middle + 1; + } + else { + return tabs[middle]; + } + } + return null; +/* + var tab = null; + this.getTabs(this.mTabBrowser).some(function(aTab) { + var box = aTab.boxObject; + if (box[this.screenPositionProp] > positionCoordinate || + box[this.screenPositionProp] + box[this.sizeProp] < positionCoordinate) { + return false; + } + tab = aTab; + return true; + }, this); + return tab; +*/ + }, + + getNextFocusedTab : function TSTBrowser_getNextFocusedTab(aTab) + { + return this.getNextSiblingTab(aTab) || + this.getPreviousVisibleTab(aTab); + }, + + isTabInViewport : function TSTBrowser_isTabInViewport(aTab) + { + if (!this.windowService.preInitialized || !aTab) + return false; + if (aTab.getAttribute('pinned') == 'true') + return true; + var tabBox = this.getFutureBoxObject(aTab); + var barBox = this.scrollBox.boxObject; + return ( + tabBox.screenX >= barBox.screenX && + tabBox.screenX + tabBox.width <= barBox.screenX + barBox.width && + tabBox.screenY >= barBox.screenY && + tabBox.screenY + tabBox.height <= barBox.screenY + barBox.height + ); + }, + + isMultiRow : function TSTBrowser_isMultiRow() + { + var w = this.window; + return ('tabberwocky' in w && utils.getTreePref('compatibility.Tabberwocky')) ? + (prefs.getPref('tabberwocky.multirow') && !this.isVertical) : + ('TabmixTabbar' in w && utils.getTreePref('compatibility.TMP')) ? + w.TabmixTabbar.isMultiRow : + false ; + }, + + positionPinnedTabs : function TSTBrowser_positionPinnedTabs(aWidth, aHeight, aJustNow) + { + var b = this.mTabBrowser; + var tabbar = b.tabContainer; + if ( + !tabbar || + !tabbar._positionPinnedTabs || + !tabbar.boxObject.width + ) + return; + + var count = this.pinnedTabsCount; + if (!this.isVertical || !count) { + this.resetPinnedTabs(); + b.mTabContainer._positionPinnedTabs(); + return; + } + + var tabbarPlaceHolderWidth = this._tabStripPlaceHolder.boxObject.width; + var tabbarWidth = this.tabStrip.boxObject.width; + + var maxWidth = tabbarPlaceHolderWidth || tabbarWidth; + + var faviconized = utils.getTreePref('pinnedTab.faviconized'); + var faviconizedSize = tabbar.childNodes[0].boxObject.height; + + var width = faviconized ? faviconizedSize : maxWidth ; + var height = faviconizedSize; + var maxCol = Math.max(1, Math.floor(maxWidth / width)); + var maxRow = Math.ceil(count / maxCol); + var col = 0; + var row = 0; + + var baseX = this.tabStrip.boxObject.screenX - this.document.documentElement.boxObject.screenX; + + var shrunkenOffset = (this.position == 'right' && tabbarPlaceHolderWidth) ? + tabbarWidth - tabbarPlaceHolderWidth : + 0 ; + + var removeFaviconizedClassPattern = new RegExp('\\s+'+this.kFAVICONIZED, 'g'); + + tabbar.style.MozMarginStart = ''; + tabbar.style.setProperty('margin-top', (height * maxRow)+'px', 'important'); + for (let i = 0; i < count; i++) + { + let item = tabbar.childNodes[i]; + + let style = item.style; + style.MozMarginStart = ''; + + let transitionStyleBackup = style.transition || style.MozTransition || ''; + if (aJustNow) + style.MozTransition = style.transition = 'none'; + + let className = item.className.replace(removeFaviconizedClassPattern, ''); + if (faviconized) + className += ' '+this.kFAVICONIZED; + if (className != item.className) + item.className = className; + + style.maxWidth = style.width = width+'px'; + style.setProperty('margin-left', ((width * col) + shrunkenOffset)+'px', 'important'); + style.left = baseX+'px'; + style.right = 'auto'; + style.marginRight = ''; + + style.setProperty('margin-top', (- height * (maxRow - row))+'px', 'important'); + style.top = style.bottom = ''; + + if (aJustNow) { + let key = 'positionPinnedTabs_tab_'+parseInt(Math.random() * 65000); + (this.deferredTasks[key] = this.Deferred.next(function() { // "transition" must be cleared after the reflow. + style.MozTransition = style.transition = transitionStyleBackup; + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + } + + col++; + if (col >= maxCol) { + col = 0; + row++; + } + } + }, + positionPinnedTabsWithDelay : function TSTBrowser_positionPinnedTabsWithDelay(...aArgs) + { + if (this.deferredTasks['positionPinnedTabsWithDelay']) + return; + + var lastArgs = this.deferredTasks['positionPinnedTabsWithDelay'] ? + this.deferredTasks['positionPinnedTabsWithDelay'].__treestyletab__args : + [null, null, false] ; + lastArgs[0] = lastArgs[0] || aArgs[0]; + lastArgs[1] = lastArgs[1] || aArgs[1]; + lastArgs[2] = lastArgs[2] || aArgs[2]; + + var self = this; + (this.deferredTasks['positionPinnedTabsWithDelay'] = this.Deferred.wait(0).next(function() { + return self.Deferred.next(function() { + // do with delay again, after Firefox's reposition was completely finished. + self.positionPinnedTabs.apply(self, lastArgs); + }); + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['positionPinnedTabsWithDelay']; + }); + this.deferredTasks['positionPinnedTabsWithDelay'].__treestyletab__args = lastArgs; + }, + + resetPinnedTabs : function TSTBrowser_resetPinnedTabs() + { + var b = this.mTabBrowser; + var tabbar = b.tabContainer; + tabbar.style.MozMarginStart = tabbar.style.marginTop = ''; + for (var i = 0, count = this.pinnedTabsCount; i < count; i++) + { + let style = tabbar.childNodes[i].style; + style.maxWidth = style.width = style.left = style.right = + style.MozMarginStart = style.marginLeft = style.marginRight = style.marginTop = ''; + } + }, + + updateTabsZIndex : function TSTBrowser_updateTabsZIndex(aStacked) + { + var tabs = this.getTabs(this.mTabBrowser); + var count = tabs.length; + for (let i = 0; i < count; i++) + { + let tab = tabs[i]; + if (aStacked) + tab.style.zIndex = count * 1000 - i; + else + tab.style.zIndex = ''; + } + }, + + fixTooNarrowTabbar : function TSTBrowser_fixTooNarrowTabbar() + { + /** + * The tab bar can become smaller than the actual size of the + * floating tab bar, and then, we cannot resize tab bar by + * dragging anymore. To avoid this problem, we have to enlarge + * the tab bar larger than the floating tab bar. + */ + if (this.isVertical) { + let key = this.autoHide.expanded ? + 'tabbar.width' : 'tabbar.shrunkenWidth' ; + let width = utils.getTreePref(key); + let minWidth = Math.max(this.MIN_TABBAR_WIDTH, this.scrollBox.boxObject.width); + if (minWidth > width) { + this.setPrefForActiveWindow(function() { + utils.setTreePref(key, minWidth); + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); + }); + } + } + else { + let height = utils.getTreePref('tabbar.height'); + let minHeight = Math.max(this.MIN_TABBAR_HEIGHT, this.scrollBox.boxObject.height); + if (minHeight > height) { + this.setPrefForActiveWindow(function() { + utils.setTreePref('tabbar.height', minHeight); + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); + }); + } + } + }, + +/* initialize */ + + init : function TSTBrowser_init() + { + this.stopRendering(); + + var w = this.window; + var d = this.document; + var b = this.mTabBrowser; + b.tabContainer.treeStyleTab = this; + + this.tabsHash = {}; + + if (b.tabContainer.parentNode.localName == 'toolbar') + b.tabContainer.parentNode.classList.add(this.kTABBAR_TOOLBAR); + + /** + * On secondary (and later) window, SSWindowStateBusy event can be fired + * before DOMContentLoad, on "domwindowopened". + */ + this.needRestoreTree = w.__treestyletab__WindowStateBusy || false; + delete w.__treestyletab__WindowStateBusy; + + this._initTabbrowserExtraContents(); + + var position = this.position; + this.fireTabbarPositionEvent(this.kEVENT_TYPE_TABBAR_POSITION_CHANGING, 'top', position); /* PUBLIC API */ + + this.setTabbrowserAttribute(this.kFIXED+'-horizontal', utils.getTreePref('tabbar.fixed.horizontal') ? 'true' : null, b); + this.setTabbrowserAttribute(this.kFIXED+'-vertical', utils.getTreePref('tabbar.fixed.vertical') ? 'true' : null, b); + this.setTabStripAttribute(this.kTAB_STRIP_ELEMENT, true); + + /** + * has its custom background color for itself, but it + * prevents to make transparent background of the vertical tab bar. + * So, I re-define the background color of content area for + * es via dynamically generated stylesheet. + * See: + * https://bugzilla.mozilla.org/show_bug.cgi?id=558585 + * http://hg.mozilla.org/mozilla-central/rev/e90bdd97d168 + */ + if (b.style.backgroundColor) { + let color = b.style.backgroundColor; + let pi = d.createProcessingInstruction( + 'xml-stylesheet', + 'type="text/css" href="data:text/css,'+encodeURIComponent( + ('.tabbrowser-tabbox > tabpanels > notificationbox {\n' + + ' background-color: %COLOR%;\n' + + '}').replace(/%COLOR%/, color) + )+'"' + ); + d.insertBefore(pi, d.documentElement); + b.style.backgroundColor = ''; + } + + this.initTabbar(null, this.kTABBAR_TOP); + + w.addEventListener('resize', this, true); + w.addEventListener('beforecustomization', this, true); + w.addEventListener('aftercustomization', this, false); + w.addEventListener('customizationchange', this, false); + w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); + w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); + w.addEventListener('tabviewframeinitialized', this, false); + w.addEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); + w.addEventListener('SSWindowStateBusy', this, false); + + b.addEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); + + w['piro.sakura.ne.jp'].tabsDragUtils.initTabBrowser(b); + + w.TreeStyleTabWindowHelper.initTabbrowserMethods(b); + this._initTabbrowserContextMenu(); + w.TreeStyleTabWindowHelper.updateTabDNDObserver(b); + + this.getAllTabs(b).forEach(this.initTab, this); + + this.onPrefChange('extensions.treestyletab.maxTreeLevel'); + this.onPrefChange('extensions.treestyletab.tabbar.style'); + this.onPrefChange('extensions.treestyletab.twisty.style'); + this.onPrefChange('extensions.treestyletab.showBorderForFirstTab'); + this.onPrefChange('extensions.treestyletab.tabbar.invertTabContents'); + this.onPrefChange('extensions.treestyletab.tabbar.invertClosebox'); + this.onPrefChange('extensions.treestyletab.tabbar.autoShow.mousemove'); + this.onPrefChange('extensions.treestyletab.tabbar.invertScrollbar'); + this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar'); + this.onPrefChange('browser.tabs.animate'); + + Services.obs.addObserver(this, this.kTOPIC_INDENT_MODIFIED, false); + Services.obs.addObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL, false); + Services.obs.addObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, false); + Services.obs.addObserver(this, 'lightweight-theme-styling-update', false); + prefs.addPrefListener(this); + + // Don't init these ovservers on this point to avoid needless initializations. + // this.tabbarDNDObserver; + // this.panelDNDObserver; + this._readyToInitDNDObservers(); + + // Init autohide service only if it have to be activated. + if (this.isAutoHide) + this.autoHide; + + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_INITIALIZE); + this.fixTooNarrowTabbar(); + + this.fireTabbarPositionEvent(false, 'top', position); /* PUBLIC API */ + + this.startRendering(); + + if (this.deferredTasks['init']) + this.deferredTasks['init'].cancel(); + + var self = this; + (this.deferredTasks['init'] = this.Deferred.next(function() { + // This command is always enabled and the TabsOnTop can be enabled + // by .updateVisibility(). + // So we have to reset TabsOnTop state on the startup. + var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop'); + var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ; + if (TabsOnTop && TabsOnTop.syncUI && toggleTabsOnTop && self.isVertical) { + toggleTabsOnTop.setAttribute('disabled', true); + if (TabsOnTop.enabled && TabsOnTop.toggle) + TabsOnTop.toggle(); + } + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['init']; + }); + }, + + _initTabbrowserExtraContents : function TSTBrowser_initTabbrowserExtraContents() + { + var d = this.document; + var b = this.mTabBrowser; + + var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); + if (!toggler) { + toggler = d.createElement('spacer'); + toggler.setAttribute(this.kTAB_STRIP_ELEMENT, true); + toggler.setAttribute('class', this.kTABBAR_TOGGLER); + toggler.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 + b.mTabBox.insertBefore(toggler, b.mTabBox.firstChild); + if (b.mTabDropIndicatorBar == toggler) + b.mTabDropIndicatorBar = d.getAnonymousElementByAttribute(b, 'class', 'tab-drop-indicator-bar'); + } + + var placeHolder = d.getAnonymousElementByAttribute(b, 'anonid', 'strip'); + if (!placeHolder) { + placeHolder = d.createElement('hbox'); + placeHolder.setAttribute(this.kTAB_STRIP_ELEMENT, true); + placeHolder.setAttribute('anonid', 'strip'); + placeHolder.setAttribute('class', 'tabbrowser-strip '+this.kTABBAR_PLACEHOLDER); + placeHolder.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 + b.mTabBox.insertBefore(placeHolder, toggler.nextSibling); + } + this.tabStripPlaceHolder = (placeHolder != this.tabStrip) ? placeHolder : null ; + + if (this.tabStripPlaceHolder) + this.tabStripPlaceHolderBoxObserver = new BrowserUIShowHideObserver(this, this.tabStripPlaceHolder.parentNode); + }, + + _initTabbrowserContextMenu : function TSTBrowser_initTabbrowserContextMenu() + { + var w = this.window; + var d = this.document; + var b = this.mTabBrowser; + + var tabContextMenu = b.tabContextMenu || + d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); + tabContextMenu.addEventListener('popupshowing', this, false); + if (!('MultipleTabService' in w)) { + w.setTimeout(function(aSelf, aTabBrowser, aPopup) { + let suffix = '-tabbrowser-'+(aTabBrowser.id || 'instance-'+parseInt(Math.random() * 65000)); + let ids = [ + aSelf.kMENUITEM_RELOADSUBTREE, + aSelf.kMENUITEM_RELOADCHILDREN, + aSelf.kMENUITEM_REMOVESUBTREE, + aSelf.kMENUITEM_REMOVECHILDREN, + aSelf.kMENUITEM_REMOVEALLTABSBUT, + aSelf.kMENUITEM_COLLAPSEEXPAND_SEPARATOR, + aSelf.kMENUITEM_COLLAPSE, + aSelf.kMENUITEM_EXPAND, + aSelf.kMENUITEM_AUTOHIDE_SEPARATOR, + aSelf.kMENUITEM_AUTOHIDE, + aSelf.kMENUITEM_FIXED, + aSelf.kMENUITEM_BOOKMARKSUBTREE + ]; + for (let i = 0, maxi = ids.length; i < maxi; i++) + { + let id = ids[i]; + let item = d.getElementById(id).cloneNode(true); + item.setAttribute('id', item.getAttribute('id')+suffix); + + let refNode = void(0); + let insertAfter = item.getAttribute('multipletab-insertafter'); + if (insertAfter) { + try { + eval('refNode = ('+insertAfter+').nextSibling'); + } + catch(e) { + } + } + let insertBefore = item.getAttribute('multipletab-insertbefore'); + if (refNode === void(0) && insertBefore) { + try { + eval('refNode = '+insertBefore); + } + catch(e) { + } + } + aPopup.insertBefore(item, refNode || null); + } + tabContextMenu = null; + }, 0, this, b, tabContextMenu); + } + + let closeTabsToEnd = d.getElementById(this.kMENUITEM_CLOSE_TABS_TO_END); + if (closeTabsToEnd) { + this._closeTabsToEnd_horizontalLabel = closeTabsToEnd.getAttribute('label'); + this._closeTabsToEnd_horizontalAccesskey = closeTabsToEnd.getAttribute('accesskey'); + } + + var removeTabItem = d.getAnonymousElementByAttribute(b, 'id', 'context_closeTab'); + if (removeTabItem) { + removeTabItem.setAttribute( + 'oncommand', + removeTabItem.getAttribute('oncommand').replace( + /(tabbrowser\.removeTab\(([^\)]+)\))/, + 'if (tabbrowser.treeStyleTab.warnAboutClosingTabSubtreeOf($2)) $1' + ) + ); + } + }, + + _initTooltipManager : function TSTBrowser_initTooltipManager() + { + if (this.tooltipManager) + return; + + this.tooltipManager = new FullTooltipManager(this); + }, + + _readyToInitDNDObservers : function TSTBrowser_readyToInitDNDObservers() + { + var w = this.window; + this._DNDObserversInitialized = false; + w.addEventListener('mouseover', this, true); + w.addEventListener('dragover', this, true); + }, + + _initDNDObservers : function TSTBrowser_initDNDObservers() + { + if (this._DNDObserversInitialized) + return; + + this.tabbarDNDObserver; + this.panelDNDObserver; + + var w = this.window; + w.removeEventListener('mouseover', this, true); + w.removeEventListener('dragover', this, true); + this._DNDObserversInitialized = true; + }, + + initTab : function TSTBrowser_initTab(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + if (!aTab.hasAttribute(this.kID)) { + let id = this.getTabValue(aTab, this.kID) || this.makeNewId(); + aTab.setAttribute(this.kID, id); + aTab.setAttribute(this.kID_NEW, id); + aTab.setAttribute(this.kSUBTREE_COLLAPSED, true); + aTab.setAttribute(this.kALLOW_COLLAPSE, true); + let self = this; + let key = 'initTab_'+id; + if (this.deferredTasks[key]) + this.deferredTasks[key].cancel(); + (this.deferredTasks[key] = this.Deferred.next(function() { + // changed by someone! + if (aTab.getAttribute(self.kID) != id) + return; + + aTab.removeAttribute(this.kID_NEW); + if (!self.getTabValue(aTab, self.kID)) { + self.setTabValue(aTab, self.kID, id); + if (!(id in self.tabsHash)) + self.tabsHash[id] = aTab; + } + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + if (!(id in this.tabsHash)) + this.tabsHash[id] = aTab; + } + else { + // if the tab is restored from session, it can be not-cached. + let id = aTab.getAttribute(this.kID); + if (!(id in this.tabsHash)) + this.tabsHash[id] = aTab; + } + + aTab.__treestyletab__linkedTabBrowser = this.mTabBrowser; + + if (!aTab.linkedBrowser.__treestyletab__toBeRestored) + aTab.linkedBrowser.__treestyletab__toBeRestored = utils.isTabNotRestoredYet(aTab); + + this.initTabAttributes(aTab); + this.initTabContents(aTab); + + if (!aTab.hasAttribute(this.kNEST)) + aTab.setAttribute(this.kNEST, 0); + }, + + isTabInitialized : function TSTBrowser_isTabInitialized(aTab) + { + return aTab.getAttribute(this.kID); + }, + + ensureTabInitialized : function TSTBrowser_ensureTabInitialized(aTab) + { + if (!aTab || this.isTabInitialized(aTab)) + return; + this.initTab(aTab); + }, + + initTabAttributes : function TSTBrowser_initTabAttributes(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var pos = this.position; + if (pos == 'left' || pos == 'right') { + aTab.setAttribute('align', 'stretch'); + aTab.removeAttribute('maxwidth'); + aTab.removeAttribute('minwidth'); + aTab.removeAttribute('width'); + aTab.removeAttribute('flex'); + aTab.maxWidth = 65000; + aTab.minWidth = 0; + if (utils.getTreePref('compatibility.TMP')) + aTab.setAttribute('dir', 'ltr'); // Tab Mix Plus + } + else { + aTab.removeAttribute('align'); + aTab.removeAttribute('maxwidth'); + aTab.removeAttribute('minwidth'); + if (utils.getTreePref('compatibility.TMP')) + aTab.removeAttribute('dir'); // Tab Mix Plus + } + }, + + initTabContents : function TSTBrowser_initTabContents(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var d = this.document; + + var twisty = this.getTabTwisty(aTab); + var anchor = this.getTabTwistyAnchorNode(aTab); + if (anchor && !twisty) { + twisty = d.createElement('image'); + twisty.setAttribute('class', this.kTWISTY); + anchor.parentNode.appendChild(twisty); + } + + var label = this.getTabLabel(aTab); + var counter = d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER); + if (label && !counter) { + counter = d.createElement('hbox'); + counter.setAttribute('class', this.kCOUNTER_CONTAINER); + + let startParen = counter.appendChild(d.createElement('label')); + startParen.setAttribute('class', this.kCOUNTER_PAREN); + startParen.setAttribute('value', '('); + + let counterLabel = counter.appendChild(d.createElement('label')); + counterLabel.setAttribute('class', this.kCOUNTER); + counterLabel.setAttribute('value', '0'); + + let endParen = counter.appendChild(d.createElement('label')); + endParen.setAttribute('class', this.kCOUNTER_PAREN); + endParen.setAttribute('value', ')'); + + /** XXX + * Insertion before an anonymous element breaks its "xbl:inherits". + * For example, "xbl:inherits" of the closebox in a tab (Tab Mix Plus + * defines it) doesn't work. So, I don't use insertBefore(). + * Instead, the counter will be rearranged by "ordinal" attribute + * given by initTabContentsOrder(). + */ + // label.parentNode.insertBefore(counter, label.nextSibling); + label.parentNode.appendChild(counter); + } + + this.initTabContentsOrder(aTab, true); + }, + + initTabContentsOrder : function TSTBrowser_initTabContentsOrder(aTab, aForce) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var d = this.document; + + var namedNodes = { + label : this.getTabLabel(aTab), + close : this.getTabClosebox(aTab), + twistyAnchor : this.getTabTwistyAnchorNode(aTab), + twisty : this.getTabTwisty(aTab), + counter : d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER) + }; + + namedNodes.closeAnchor = namedNodes.label; + if (namedNodes.closeAnchor.parentNode != namedNodes.close.parentNode) { + let containerFinder = d.createRange(); + containerFinder.selectNode(namedNodes.closeAnchor); + containerFinder.setEndAfter(namedNodes.close); + let container = containerFinder.commonAncestorContainer; + while (namedNodes.closeAnchor.parentNode != container) + { + namedNodes.closeAnchor = namedNodes.closeAnchor.parentNode; + } + while (namedNodes.close.parentNode != container) + { + namedNodes.close = namedNodes.close.parentNode; + } + } + + namedNodes.counterAnchor = namedNodes.label; + + var foundContainers = []; + var containers = [ + namedNodes.twistyAnchor.parentNode, + namedNodes.label.parentNode, + namedNodes.counter.parentNode, + namedNodes.closeAnchor.parentNode + ]; + for (let i = 0, maxi = containers.length; i < maxi; i++) + { + let container = containers[i]; + if (foundContainers.indexOf(container) > -1) + continue; + this.initTabContentsOrderInternal(container, namedNodes, aForce); + foundContainers.push(container); + } + }, + initTabContentsOrderInternal : function TSTBrowser_initTabContentsOrderInternal(aContainer, aNamedNodes, aForce) + { + if (this.window.getComputedStyle(aContainer, '').getPropertyValue('-moz-box-orient') == 'vertical') + return; + + var nodes = Array.slice(this.document.getAnonymousNodes(aContainer) || aContainer.childNodes); + + // reset order at first! + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + if (node.getAttribute('class') == 'informationaltab-thumbnail-container') + continue; + node.setAttribute('ordinal', i); + } + + // after that, rearrange contents + + var index = nodes.indexOf(aNamedNodes.close); + if (index > -1) { + nodes.splice(index, 1); + if (this.mTabBrowser.getAttribute(this.kCLOSEBOX_INVERTED) == 'true') + nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor), 0, aNamedNodes.close); + else + nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor)+1, 0, aNamedNodes.close); + } + + index = nodes.indexOf(aNamedNodes.twisty); + if (index > -1) { + nodes.splice(index, 1); + nodes.splice(nodes.indexOf(aNamedNodes.twistyAnchor), 0, aNamedNodes.twisty); + } + + if (this.mTabBrowser.getAttribute(this.kTAB_CONTENTS_INVERTED) == 'true') + nodes.reverse(); + + // counter must rightside of the label! + index = nodes.indexOf(aNamedNodes.counter); + if (index > -1) { + nodes.splice(index, 1); + nodes.splice(nodes.indexOf(aNamedNodes.counterAnchor)+1, 0, aNamedNodes.counter); + } + + var count = nodes.length; + nodes.reverse(); + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + if (node.getAttribute('class') == 'informationaltab-thumbnail-container') + continue; + node.setAttribute('ordinal', (count - i + 1) * 100); + } + + if (aForce) { + /** + * After the order of contents are changed dynamically, + * Gecko doesn't re-render them in the new order. + * Changing of "display" or "position" can fix this problem. + */ + let shouldHideTemporaryState = ( + 'TabmixTabbar' in this.window || // Tab Mix Plus + 'InformationalTabService' in this.window // Informational Tab + ); + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + if (shouldHideTemporaryState) + node.style.visibility = 'hidden'; + node.style.position = 'fixed'; + } + let key = 'initTabContentsOrderInternal_'+parseInt(Math.random() * 65000); + (this.deferredTasks[key] = this.Deferred.wait(0.1).next(function() { + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + node.style.position = ''; + if (shouldHideTemporaryState) + node.style.visibility = ''; + } + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + } + }, + + updateInvertedTabContentsOrder : function TSTBrowser_updateInvertedTabContentsOrder(aTarget) + { + var self = this; + let key = 'updateInvertedTabContentsOrder_'+parseInt(Math.random() * 65000); + (this.deferredTasks[key] = this.Deferred.next(function() { + var b = self.mTabBrowser; + var tabs = !aTarget ? + [b.selectedTab] : + (aTarget instanceof Ci.nsIDOMElement) ? + [aTarget] : + (typeof aTarget == 'object' && 'length' in aTarget) ? + Array.slice(aTarget) : + self.getAllTabs(b); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + self.initTabContentsOrder(tabs[i]); + } + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + }, + + initTabbar : function TSTBrowser_initTabbar(aNewPosition, aOldPosition) + { + var d = this.document; + var b = this.mTabBrowser; + + if (aNewPosition && typeof aNewPosition == 'string') + aNewPosition = this.getPositionFlag(aNewPosition); + if (aOldPosition && typeof aOldPosition == 'string') + aOldPosition = this.getPositionFlag(aOldPosition); + + this._startListenTabbarEvents(); + this.window.TreeStyleTabWindowHelper.initTabbarMethods(b); + + this.stopRendering(); + + var pos = aNewPosition || this.getPositionFlag(this.position); + if (b.getAttribute('id') != 'content' && + !utils.getTreePref('tabbar.position.subbrowser.enabled')) { + pos = this.kTABBAR_TOP; + } + + aOldPosition = aOldPosition || pos; + + // We have to use CSS property hack instead, because the stopRendering() + // doesn't effect on the first time of startup. + // * This hack works in a "stop"-"start" pair, so, people never see the side effect. + // * This hack works only when "ordinal" properties are modified. + // So, this is just for the case: "right" or "bottom" tab bar on the startup. + if ( + pos != aOldPosition && + ( + ((pos & this.kTABBAR_REGULAR) && (aOldPosition & this.kTABBAR_INVERTED)) || + ((pos & this.kTABBAR_INVERTED) && (aOldPosition & this.kTABBAR_REGULAR)) + ) + ) + b.style.visibility = 'hidden'; + + var strip = this.tabStrip; + var placeHolder = this.tabStripPlaceHolder || strip; + var splitter = this._ensureNewSplitter(); + var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); + + // Tab Mix Plus + var scrollFrame, newTabBox, tabBarMode; + if (utils.getTreePref('compatibility.TMP')) { + scrollFrame = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-frame') || + d.getAnonymousElementByAttribute(b.mTabContainer, 'anonid', 'scroll-tabs-frame'); + newTabBox = d.getAnonymousElementByAttribute(b.mTabContainer, 'id', 'tabs-newbutton-box'); + let newTabButton = d.getElementById('new-tab-button'); + if (newTabButton && newTabButton.parentNode == b.tabContainer._container) + newTabBox = newTabButton; + tabBarMode = prefs.getPref('extensions.tabmix.tabBarMode'); + } + + // All-in-One Sidebar + var toolboxContainer = d.getAnonymousElementByAttribute(strip, 'anonid', 'aiostbx-toolbox-tableft'); + if (toolboxContainer) + toolboxContainer = toolboxContainer.parentNode; + + var scrollInnerBox = b.mTabContainer.mTabstrip._scrollbox ? + d.getAnonymousNodes(b.mTabContainer.mTabstrip._scrollbox)[0] : + scrollFrame; // Tab Mix Plus + + this.removeTabbrowserAttribute(this.kRESIZING, b); + + this.removeTabStripAttribute('width'); + b.mPanelContainer.removeAttribute('width'); + + var delayedPostProcess; + + if (pos & this.kTABBAR_VERTICAL) { + this.collapseTarget = 'top'; + this.screenPositionProp = 'screenY'; + this.offsetProp = 'offsetY'; + this.translateFunction = 'translateY'; + this.sizeProp = 'height'; + this.invertedScreenPositionProp = 'screenX'; + this.invertedSizeProp = 'width'; + this.startProp = 'top'; + this.endProp = 'bottom'; + + b.mTabBox.orient = splitter.orient = 'horizontal'; + strip.orient = + placeHolder.orient = + toggler.orient = + b.mTabContainer.orient = + b.mTabContainer.mTabstrip.orient = + b.mTabContainer.mTabstrip.parentNode.orient = 'vertical'; + b.mTabContainer.setAttribute('align', 'stretch'); // for Mac OS X + if (scrollInnerBox) + scrollInnerBox.removeAttribute('flex'); + + if (utils.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus + d.getAnonymousNodes(scrollFrame)[0].removeAttribute('flex'); + scrollFrame.parentNode.orient = + scrollFrame.orient = 'vertical'; + if (newTabBox) + newTabBox.orient = 'horizontal'; + if (tabBarMode == 2) + prefs.setPref('extensions.tabmix.tabBarMode', 1); + } + + if (toolboxContainer) + toolboxContainer.orient = 'vertical'; + + this.setTabbrowserAttribute(this.kMODE, 'vertical'); + + let width = this.maxTabbarWidth(utils.getTreePref('tabbar.width'), b); + this.setTabStripAttribute('width', width); + this.removeTabStripAttribute('height'); + b.mPanelContainer.removeAttribute('height'); + + if (strip.localName == 'toolbar') { + let nodes = strip.childNodes; + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + if (node.localName == 'tabs') + continue; + if (node.hasAttribute('flex')) + node.setAttribute('treestyletab-backup-flex', node.getAttribute('flex')); + node.removeAttribute('flex'); + } + } + + if (pos == this.kTABBAR_RIGHT) { + this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'right'); + if (utils.getTreePref('tabbar.invertTab')) { + this.setTabbrowserAttribute(this.kTAB_INVERTED, 'true'); + this.indentTarget = 'right'; + } + else { + this.removeTabbrowserAttribute(this.kTAB_INVERTED); + this.indentTarget = 'left'; + } + delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { + /* in Firefox 3, the width of the rightside tab bar + unexpectedly becomes 0 on the startup. so, we have + to set the width again. */ + aSelf.setTabStripAttribute('width', width); + aSelf.setTabStripAttribute('ordinal', 30); + aSplitter.setAttribute('ordinal', 20); + aToggler.setAttribute('ordinal', 40); + aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); + aSplitter.setAttribute('collapse', 'after'); + }; + } + else { + this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'left'); + this.removeTabbrowserAttribute(this.kTAB_INVERTED); + this.indentTarget = 'left'; + delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { + aSelf.setTabStripAttribute('ordinal', 10); + aSplitter.setAttribute('ordinal', 20); + aToggler.setAttribute('ordinal', 5); + aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); + aSplitter.setAttribute('collapse', 'before'); + }; + } + } + else { + this.collapseTarget = 'left'; + this.screenPositionProp = 'screenX'; + this.offsetProp = 'offsetX'; + this.translateFunction = 'translateX'; + this.sizeProp = 'width'; + this.invertedScreenPositionProp = 'screenY'; + this.invertedSizeProp = 'height'; + this.startProp = 'left'; + this.endProp = 'right'; + + b.mTabBox.orient = splitter.orient = 'vertical'; + strip.orient = + placeHolder.orient = + toggler.orient = + b.mTabContainer.orient = + b.mTabContainer.mTabstrip.orient = + b.mTabContainer.mTabstrip.parentNode.orient = 'horizontal'; + b.mTabContainer.removeAttribute('align'); // for Mac OS X + if (scrollInnerBox) + scrollInnerBox.setAttribute('flex', 1); + + if (utils.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus + d.getAnonymousNodes(scrollFrame)[0].setAttribute('flex', 1); + scrollFrame.parentNode.orient = + scrollFrame.orient = 'horizontal'; + if (newTabBox) + newTabBox.orient = 'vertical'; + } + + if (toolboxContainer) + toolboxContainer.orient = 'horizontal'; + + this.setTabbrowserAttribute(this.kMODE, utils.getTreePref('tabbar.multirow') ? 'multirow' : 'horizontal'); + this.removeTabbrowserAttribute(this.kTAB_INVERTED); + + if (strip.localName == 'toolbar') { + let nodes = strip.childNodes; + for (let i = 0, maxi = nodes.length; i < maxi; i++) + { + let node = nodes[i]; + if (node.localName == 'tabs') + continue; + let flex = node.hasAttribute('treestyletab-backup-flex'); + if (!flex) + continue; + node.setAttribute('flex', flex); + node.removeAttribute('treestyletab-backup-flex'); + } + } + + if (pos == this.kTABBAR_BOTTOM) { + this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'bottom'); + this.indentTarget = 'bottom'; + delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { + aSelf.setTabStripAttribute('ordinal', 30); + aSplitter.setAttribute('ordinal', 20); + aToggler.setAttribute('ordinal', 40); + aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); + }; + } + else { + this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'top'); + this.indentTarget = 'top'; + delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { + aSelf.setTabStripAttribute('ordinal', 10); + aSplitter.setAttribute('ordinal', 20); + aToggler.setAttribute('ordinal', 5); + aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); + }; + } + } + + var tabs = this.getAllTabs(b); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + tab.style.removeProperty(this.indentCSSProp); + tab.style.removeProperty(this.collapseCSSProp); + } + + this.indentProp = utils.getTreePref('indent.property'); + this.indentCSSProp = this.indentProp+'-'+this.indentTarget; + this.collapseCSSProp = 'margin-'+this.collapseTarget; + + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + this.updateTabCollapsed(tab, tab.getAttribute(this.kCOLLAPSED) == 'true', true); + } + + // for updateTabbarOverflow(), we should reset the "overflow" now. + b.mTabContainer.removeAttribute('overflow'); + let (container = this.document.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container')) { + if (container) + container.removeAttribute('overflow'); + } + + this.updateTabbarState(false); + + if (this.deferredTasks['initTabbar']) + this.deferredTasks['initTabbar'].cancel(); + + var self = this; + (this.deferredTasks['initTabbar'] = this.Deferred.next(function() { + delayedPostProcess(self, b, splitter, toggler); + self.updateTabbarOverflow(); + self.updateAllTabsButton(b); + self.updateAllTabsCount(); + delayedPostProcess = null; + self.mTabBrowser.style.visibility = ''; + + var event = d.createEvent('Events'); + event.initEvent(self.kEVENT_TYPE_TABBAR_INITIALIZED, true, false); + self.mTabBrowser.dispatchEvent(event); + + self.startRendering(); + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['initTabbar']; + }); + + pos = null; + scrollFrame = null; + newTabBox = null + tabBarMode = null; + toolboxContainer = null; + scrollInnerBox = null; + scrollInnerBox = null; + }, + + _startListenTabbarEvents : function TSTBrowser_startListenTabbarEvents() + { + var b = this.mTabBrowser; + + var tabContainer = b.mTabContainer; + tabContainer.addEventListener('TabOpen', this, true); + tabContainer.addEventListener('TabClose', this, true); + tabContainer.addEventListener('TabMove', this, true); + tabContainer.addEventListener('TabShow', this, true); + tabContainer.addEventListener('TabHide', this, true); + tabContainer.addEventListener('SSTabRestoring', this, true); + tabContainer.addEventListener('SSTabRestored', this, true); + tabContainer.addEventListener('TabPinned', this, true); + tabContainer.addEventListener('TabUnpinned', this, true); + tabContainer.addEventListener('mouseover', this, true); + tabContainer.addEventListener('mouseout', this, true); + tabContainer.addEventListener('dblclick', this, true); + tabContainer.addEventListener('select', this, true); + tabContainer.addEventListener('scroll', this, true); + + var strip = this.tabStrip; + strip.addEventListener('MozMouseHittest', this, true); // to block default behaviors of the tab bar + strip.addEventListener('mousedown', this, true); + strip.addEventListener('click', this, true); + strip.addEventListener('DOMMouseScroll', this, true); + + this.scrollBox.addEventListener('overflow', this, true); + this.scrollBox.addEventListener('underflow', this, true); + }, + + _ensureNewSplitter : function TSTBrowser__ensureNewSplitter() + { + var d = this.document; + var splitter = this.splitter; + + // We always have to re-create splitter, because its "collapse" + // behavior becomes broken by repositioning of the tab bar. + if (splitter) { + try { + splitter.removeEventListener('mousedown', this.windowService, false); + splitter.removeEventListener('mouseup', this.windowService, false); + splitter.removeEventListener('dblclick', this.windowService, false); + } + catch(e) { + } + let oldSplitter = splitter; + splitter = oldSplitter.cloneNode(true); + oldSplitter.parentNode.removeChild(oldSplitter); + } + else { + splitter = d.createElement('splitter'); + splitter.setAttribute(this.kTAB_STRIP_ELEMENT, true); + splitter.setAttribute('state', 'open'); + splitter.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 + let grippy = d.createElement('grippy') + grippy.setAttribute(this.kTAB_STRIP_ELEMENT, true); + // Workaround for https://github.com/piroor/treestyletab/issues/593 + // When you click the grippy... + // 1. The grippy changes "state" of the splitter from "collapsed" + // to "open". + // 2. The splitter changes visibility of the place holder. + // 3. BrowserUIShowHideObserver detects the change of place + // holder's visibility and triggers updateFloatingTabbar(). + // 4. updateFloatingTabbar() copies the visibility of the + // actual tab bar to the place holder. However, the tab bar + // is still collapsed. + // 5. As the result, the place holder becomes collapsed and + // the splitter disappear. + // So, we have to turn the actual tab bar visible manually + // when the grippy is clicked. + let tabContainer = this.mTabBrowser.tabContainer; + grippy.addEventListener('click', function() { + tabContainer.ownerDocument.defaultView.setTimeout(function() { + var visible = grippy.getAttribute('state') != 'collapsed'; + if (visible != tabContainer.visible) + tabContainer.visible = visible; + }, 0); + }, false); + splitter.appendChild(grippy); + } + + var splitterClass = splitter.getAttribute('class') || ''; + if (splitterClass.indexOf(this.kSPLITTER) < 0) + splitterClass += (splitterClass ? ' ' : '' ) + this.kSPLITTER; + splitter.setAttribute('class', splitterClass); + + splitter.addEventListener('mousedown', this.windowService, false); + splitter.addEventListener('mouseup', this.windowService, false); + splitter.addEventListener('dblclick', this.windowService, false); + + var ref = this.mTabBrowser.mPanelContainer; + ref.parentNode.insertBefore(splitter, ref); + + return splitter; + }, + + fireTabbarPositionEvent : function TSTBrowser_fireTabbarPositionEvent(aChanging, aOldPosition, aNewPosition) + { + if (aOldPosition == aNewPosition) + return false; + + var type = aChanging ? this.kEVENT_TYPE_TABBAR_POSITION_CHANGING : this.kEVENT_TYPE_TABBAR_POSITION_CHANGED; + var data = { + oldPosition : aOldPosition, + newPosition : aNewPosition + }; + + /* PUBLIC API */ + this.fireCustomEvent(type, this.mTabBrowser, true, false, data); + // for backward compatibility + this.fireCustomEvent(type.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); + + return true; + }, + + updateTabbarState : function TSTBrowser_updateTabbarState(aCancelable) + { + if (!this._fireTabbarStateChangingEvent() && aCancelable) + return; + + this.stopRendering(); + + var self = this; + + var w = this.window; + var d = this.document; + var b = this.mTabBrowser; + var orient; + var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop'); + var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ; + if (this.isVertical) { + orient = 'vertical'; + this.fixed = this.fixed; // ensure set to the current orient + if (toggleTabsOnTop) + toggleTabsOnTop.setAttribute('disabled', true); + } + else { + orient = 'horizontal'; + if (this.fixed) { + this.fixed = true; // ensure set to the current orient + if (!this.isMultiRow()) { + this.removeTabStripAttribute('height'); + b.mPanelContainer.removeAttribute('height'); + } + // remove ordinal for "tabs on top" https://bugzilla.mozilla.org/show_bug.cgi?id=544815 + if (this.position == 'top') { + this.removeTabStripAttribute('ordinal'); + if (TabsOnTop && !this.windowService.isPopupWindow && + this.windowService.initialized) { + let currentState = TabsOnTop.enabled; + let originalState = utils.getTreePref('tabsOnTop.originalState'); + if (originalState !== null && + currentState != originalState && + this.windowService.tabsOnTopChangingByUI && + !this.windowService.changingTabsOnTop) + utils.setTreePref('tabsOnTop.originalState', currentState); + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=555987 + // This should be done when the value of the "ordinal" attribute + // is modified dynamically. So, we don' have to do it before + // the browser window is completely initialized. + TabsOnTop.enabled = !currentState; + if (this.deferredTasks['updateTabbarState_TabsOnTop']) + this.deferredTasks['updateTabbarState_TabsOnTop'].cancel(); + (this.deferredTasks['updateTabbarState_TabsOnTop'] = this.Deferred.next(function() { + TabsOnTop.enabled = currentState; + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['updateTabbarState_TabsOnTop']; + }); + } + } + } + else { + this.fixed = false; // ensure set to the current orient + this.setTabStripAttribute('height', this.maxTabbarHeight(utils.getTreePref('tabbar.height'), b)); + } + if (toggleTabsOnTop) { + if (this.position == 'top') + toggleTabsOnTop.removeAttribute('disabled'); + else + toggleTabsOnTop.setAttribute('disabled', true); + } + } + + if (TabsOnTop && !this.windowService.isPopupWindow) { + let updateTabsOnTop = function() { + self.windowService.updateTabsOnTop(); + }; + // TabsOnTop.enabled is always "false" before the browser window is + // completely initialized. So, we have to check it with delay only + // on the Startup. + if (this.initialized) + updateTabsOnTop(); + else + this.Deferred.next(updateTabsOnTop); + } + + if (this.deferredTasks['updateTabbarState']) + this.deferredTasks['updateTabbarState'].cancel(); + (this.deferredTasks['updateTabbarState'] = this.Deferred.next(function() { + self.updateFloatingTabbar(self.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); + self._fireTabbarStateChangedEvent(); + self.startRendering(); + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['updateTabbarState']; + }); + + var allowToCollapse = utils.getTreePref('allowSubtreeCollapseExpand.'+orient); + if (this.allowSubtreeCollapseExpand != allowToCollapse) + this.collapseExpandAllSubtree(false, false); + this.allowSubtreeCollapseExpand = allowToCollapse; + + this.maxTreeLevel = utils.getTreePref('maxTreeLevel.'+orient); + + this.setTabbrowserAttribute(this.kALLOW_STACK, this.canStackTabs ? 'true' : null); + this.updateTabsZIndex(this.canStackTabs); + + if (this.maxTreeLevelPhisical) + this.promoteTooDeepLevelTabs(); + + this.updateAllTabsIndent(); + }, + + _fireTabbarStateChangingEvent : function TSTBrowser_fireTabbarStateChangingEvent() + { + var b = this.mTabBrowser; + var orient = this.isVertical ? 'vertical' : 'horizontal' ; + var oldState = { + fixed : this.fixed, + maxTreeLevel : this.maxTreeLevel, + indented : this.maxTreeLevel != 0, + canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' + }; + var newState = { + fixed : utils.getTreePref('tabbar.fixed.'+orient), + maxTreeLevel : utils.getTreePref('maxTreeLevel.'+orient), + indented : utils.getTreePref('maxTreeLevel.'+orient) != 0, + canCollapse : utils.getTreePref('allowSubtreeCollapseExpand.'+orient) + }; + + if (oldState.fixed == newState.fixed && + oldState.maxTreeLevel == newState.maxTreeLevel && + oldState.indented == newState.indented && + oldState.canCollapse == newState.canCollapse) + return false; + + var data = { + oldState : oldState, + newState : newState + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING, this.mTabBrowser, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); + + return true; + }, + + _fireTabbarStateChangedEvent : function TSTBrowser_fireTabbarStateChangedEvent() + { + var b = this.mTabBrowser; + var state = { + fixed : this.fixed, + maxTreeLevel : this.maxTreeLevel, + indented : this.maxTreeLevel != 0, + canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' + }; + + var data = { + state : state + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this.mTabBrowser, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); + + return true; + }, + + updateFloatingTabbar : function TSTBrowser_updateFloatingTabbar(aReason) + { + var w = this.window; + if (this._updateFloatingTabbarTimer) { + w.clearTimeout(this._updateFloatingTabbarTimer); + this._updateFloatingTabbarTimer = null; + } + + this._updateFloatingTabbarReason |= aReason; + + if (this._updateFloatingTabbarReason & this.kTABBAR_UPDATE_NOW) { + this._updateFloatingTabbarInternal(this._updateFloatingTabbarReason); + this._updateFloatingTabbarReason = 0; + } + else { + this._updateFloatingTabbarTimer = w.setTimeout(function(aSelf) { + aSelf._updateFloatingTabbarTimer = null; + aSelf._updateFloatingTabbarInternal(aSelf._updateFloatingTabbarReason) + aSelf._updateFloatingTabbarReason = 0; + }, 0, this); + } + }, + + _updateFloatingTabbarInternal : function TSTBrowser_updateFloatingTabbarInternal(aReason) + { + aReason = aReason || this.kTABBAR_UPDATE_BY_UNKNOWN_REASON; + + if (DEBUG) { + let humanReadableReason = + (aReason & this.kTABBAR_UPDATE_BY_RESET ? 'reset ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_PREF_CHANGE ? 'prefchange ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE ? 'appearance-change ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR ? 'showhide ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE ? 'tabbar-resize ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_WINDOW_RESIZE ? 'window-resize ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_FULLSCREEN ? 'fullscreen ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE ? 'autohide ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_INITIALIZE ? 'initialize ' : '' ) + + (aReason & this.kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR ? 'toggle-sidebar ' : '' ); + dump('TSTBrowser_updateFloatingTabbarInternal: ' + humanReadableReason + '\n'); + } + + var d = this.document; + + // When the tab bar is invisible even if the tab bar is resizing, then + // now I'm trying to expand the tab bar from collapsed state. + // Then the tab bar must be shown. + if (aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE && + !this.browser.tabContainer.visible) + this.browser.tabContainer.visible = true; + + var splitter = this.splitter; + if (splitter.collapsed || splitter.getAttribute('state') != 'collapsed') { + // Synchronize visibility of the tab bar to the placeholder, + // because the tab bar can be shown/hidden by someone + // (Tab Mix Plus, Pale Moon, or some addons). + this._tabStripPlaceHolder.collapsed = + splitter.collapsed = + !this.browser.tabContainer.visible; + } + + var strip = this.tabStrip; + var collapsed = splitter.collapsed ? + strip.collapsed : + splitter.getAttribute('state') == 'collapsed' ; + var stripStyle = strip.style; + var tabContainerBox = this.getTabContainerBox(this.mTabBrowser); + var statusPanel = d.getElementById('statusbar-display'); + var statusPanelStyle = statusPanel ? statusPanel.style : null ; + var pos = this.position; + if (pos != 'top' || + this.mTabBrowser.getAttribute(this.kFIXED) != 'true') { + strip.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 + + if ( + this.autoHide.enabled && + this.autoHide.expanded && + (aReason & this.kTABBAR_UPDATE_SYNC_TO_PLACEHOLDER) && + this.autoHide.mode == this.autoHide.kMODE_SHRINK + ) + this.autoHide.hide(this.autoHide.kSHOWN_BY_ANY_REASON); + + let box = this._tabStripPlaceHolder.boxObject; + let root = d.documentElement.boxObject; + let realSize = this.getTabbarPlaceholderSize(); + + let width = (this.autoHide.expanded && this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? + this.maxTabbarWidth(utils.getTreePref('tabbar.width')) : + 0 + ) || realSize.width; + let height = (this.autoHide.expanded && !this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? + this.maxTabbarHeight(utils.getTreePref('tabbar.height')) : + 0 + ) || realSize.height; + let yOffset = pos == 'bottom' ? height - realSize.height : 0 ; + + stripStyle.top = (box.screenY - root.screenY + root.y - yOffset)+'px'; + stripStyle.left = pos == 'right' ? '' : + (box.screenX - root.screenX + root.x)+'px'; + stripStyle.right = pos != 'right' ? '' : + ((root.screenX + root.width) - (box.screenX + box.width))+'px'; + + stripStyle.width = (strip.width = tabContainerBox.width = width)+'px'; + stripStyle.height = (strip.height = tabContainerBox.height = height)+'px'; + + this._updateFloatingTabbarResizer({ + width : width, + realWidth : realSize.width, + height : height, + realHeight : realSize.height + }); + + this._lastTabbarPlaceholderSize = realSize; + + strip.collapsed = tabContainerBox.collapsed = collapsed; + + if (statusPanel && utils.getTreePref('repositionStatusPanel')) { + let offsetParentBox = this.base.findOffsetParent(statusPanel).boxObject; + let contentBox = this.mTabBrowser.mPanelContainer.boxObject; + let chromeMargins = (d.documentElement.getAttribute('chromemargin') || '0,0,0,0').split(','); + chromeMargins = chromeMargins.map(function(aMargin) { return parseInt(aMargin); }); + statusPanelStyle.marginTop = (pos == 'bottom') ? + '-moz-calc(0px - ' + (offsetParentBox.height - contentBox.height + chromeMargins[2]) + 'px - 3em)' : + '' ; + statusPanelStyle.marginLeft = (contentBox.screenX - offsetParentBox.screenX + chromeMargins[3])+'px'; + statusPanelStyle.marginRight = ((offsetParentBox.screenX + offsetParentBox.width) - (contentBox.screenX + contentBox.width) + chromeMargins[1])+'px'; + statusPanelStyle.maxWidth = this.isVertical ? + (contentBox.width-5)+'px' : // emulate the margin defined on https://bugzilla.mozilla.org/show_bug.cgi?id=632634 + '' ; + statusPanel.__treestyletab__repositioned = true; + } + + this.mTabBrowser.tabContainer.setAttribute('context', this.mTabBrowser.tabContextMenu.id); + } + else { + strip.collapsed = tabContainerBox.collapsed = collapsed; + stripStyle.top = stripStyle.left = stripStyle.right = stripStyle.width = stripStyle.height = ''; + + if ( + statusPanel && + ( + utils.getTreePref('repositionStatusPanel') || + statusPanel.__treestyletab__repositioned + ) + ) { + statusPanelStyle.marginTop = statusPanelStyle.marginLeft = + statusPanelStyle.marginRight = statusPanelStyle.maxWidth = ''; + statusPanel.__treestyletab__repositioned = false; + } + + strip.removeAttribute('layer'); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 + + this.mTabBrowser.tabContainer.removeAttribute('context'); + } + + if (tabContainerBox.boxObject.width) + this.positionPinnedTabs(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); + else + this.positionPinnedTabsWithDelay(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); + + if (!collapsed && aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE) { + let self = this; + this.Deferred.next(function() { + self.scrollToTab(self.browser.selectedTab); + }); + } + }, + getTabbarPlaceholderSize: function TSTBrowser_getTabbarPlaceholderSize() + { + var box = this._tabStripPlaceHolder.boxObject; + return { + width: parseInt(this._tabStripPlaceHolder.getAttribute('width') || box.width), + height: parseInt(this._tabStripPlaceHolder.getAttribute('height') || box.height) + }; + }, + getExistingTabsCount : function TSTBrowser_getTabsCount() + { + return this.getAllTabs(this.mTabBrowser).length - this.mTabBrowser._removingTabs.length; + }, + + _updateFloatingTabbarResizer : function TSTBrowser_updateFloatingTabbarResizer(aSize) + { + var d = this.document; + + var width = aSize.width; + var realWidth = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realWidth ; + var height = aSize.height; + var realHeight = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realHeight ; + var pos = this.position; + var vertical = this.isVertical; + + var splitter = d.getElementById('treestyletab-tabbar-resizer-splitter'); + if (!splitter) { + let box = d.createElement('box'); + box.setAttribute('id', 'treestyletab-tabbar-resizer-box'); + box.setAttribute(this.kTAB_STRIP_ELEMENT, true); + splitter = d.createElement('splitter'); + splitter.setAttribute(this.kTAB_STRIP_ELEMENT, true); + splitter.setAttribute('id', 'treestyletab-tabbar-resizer-splitter'); + splitter.setAttribute('class', this.kSPLITTER); + splitter.setAttribute('onmousedown', 'TreeStyleTabService.handleEvent(event);'); + splitter.setAttribute('onmouseup', 'TreeStyleTabService.handleEvent(event);'); + splitter.setAttribute('ondblclick', 'TreeStyleTabService.handleEvent(event);'); + box.appendChild(splitter); + this.tabStrip.appendChild(box); + } + + var box = splitter.parentNode; + + box.orient = splitter.orient = vertical ? 'horizontal' : 'vertical' ; + box.width = (width - realWidth) || width; + box.height = (height - realHeight) || height; + + var boxStyle = box.style; + boxStyle.top = pos == 'top' ? realHeight+'px' : '' ; + boxStyle.right = pos == 'right' ? realWidth+'px' : '' ; + boxStyle.left = pos == 'left' ? realWidth+'px' : '' ; + boxStyle.bottom = pos == 'bottom' ? realHeight+'px' : '' ; + + if (vertical) { + splitter.removeAttribute('width'); + splitter.setAttribute('height', height); + } + else { + splitter.setAttribute('width', width); + splitter.removeAttribute('height'); + } + + var splitterWidth = splitter.boxObject.width; + var splitterHeight = splitter.boxObject.height; + var splitterStyle = splitter.style; + splitterStyle.marginTop = pos == 'bottom' ? (-splitterHeight)+'px' : + vertical ? '0' : + box.height+'px' ; + splitterStyle.marginRight = pos == 'left' ? (-splitterWidth)+'px' : + !vertical ? '0' : + box.width+'px' ; + splitterStyle.marginLeft = pos == 'right' ? (-splitterWidth)+'px' : + !vertical ? '0' : + box.width+'px' ; + splitterStyle.marginBottom = pos == 'top' ? (-splitterHeight)+'px' : + vertical ? '0' : + box.height+'px' ; + }, + + updateTabbarOverflow : function TSTBrowser_updateTabbarOverflow() + { + var d = this.document; + var b = this.mTabBrowser; + b.mTabContainer.removeAttribute('overflow'); + var container = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container') || b.mTabContainer; + + if (container != b.mTabContainer) + container.removeAttribute('overflow'); + + var scrollBox = this.scrollBox; + scrollBox = d.getAnonymousElementByAttribute(scrollBox, 'anonid', 'scrollbox'); + if (scrollBox) + scrollBox = d.getAnonymousNodes(scrollBox)[0]; + if ( + scrollBox && + ( + scrollBox.boxObject.width > container.boxObject.width || + scrollBox.boxObject.height > container.boxObject.height + ) + ) { + b.mTabContainer.setAttribute('overflow', true); + if (container != b.mTabContainer) + container.setAttribute('overflow', true); + } + else { + b.mTabContainer.removeAttribute('overflow'); + if (container != b.mTabContainer) + container.removeAttribute('overflow'); + } + }, + + reinitAllTabs : function TSTBrowser_reinitAllTabs(aSouldUpdateCount) + { + var tabs = this.getAllTabs(this.mTabBrowser); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + this.initTabAttributes(tab); + this.initTabContents(tab); + if (aSouldUpdateCount) + this.updateTabsCount(tab); + } + }, + + destroy : function TSTBrowser_destroy() + { + this.animationManager.removeTask(this.smoothScrollTask); + + Object.keys(this.deferredTasks).forEach(function(key) { + if (this.deferredTasks[key].cancel) { + this.deferredTasks[key].cancel(); + delete this.deferredTasks[key]; + } + }, this); + + this.autoHide.destroy(); + delete this._autoHide; + + this._initDNDObservers(); // ensure initialized + this.tabbarDNDObserver.destroy(); + delete this._tabbarDNDObserver; + this.panelDNDObserver.destroy(); + delete this._panelDNDObserver; + + if (this.tooltipManager) { + this.tooltipManager.destroy(); + delete this.tooltipManager; + } + + if (this.tabStripPlaceHolderBoxObserver) { + this.tabStripPlaceHolderBoxObserver.destroy(); + delete this.tabStripPlaceHolderBoxObserver; + } + + var w = this.window; + var d = this.document; + var b = this.mTabBrowser; + delete b.tabContainer.treeStyleTab; + + var tabs = this.getAllTabs(b); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + this.stopTabIndentAnimation(tab); + this.stopTabCollapseAnimation(tab); + this.destroyTab(tab); + } + + this._endListenTabbarEvents(); + + w.removeEventListener('resize', this, true); + w.removeEventListener('beforecustomization', this, true); + w.removeEventListener('aftercustomization', this, false); + w.removeEventListener('customizationchange', this, false); + w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); + w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); + w.removeEventListener('tabviewframeinitialized', this, false); + w.removeEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false); + w.removeEventListener('SSWindowStateBusy', this, false); + + b.removeEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); + + w['piro.sakura.ne.jp'].tabsDragUtils.destroyTabBrowser(b); + + var tabContextMenu = b.tabContextMenu || + d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); + tabContextMenu.removeEventListener('popupshowing', this, false); + + if (this.tabbarCanvas) { + this.tabbarCanvas.parentNode.removeChild(this.tabbarCanvas); + this.tabbarCanvas = null; + } + + Services.obs.removeObserver(this, this.kTOPIC_INDENT_MODIFIED); + Services.obs.removeObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL); + Services.obs.removeObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY); + Services.obs.removeObserver(this, 'lightweight-theme-styling-update'); + prefs.removePrefListener(this); + + delete this.windowService; + delete this.window; + delete this.document; + delete this.mTabBrowser.treeStyleTab; + delete this.mTabBrowser; + }, + + destroyTab : function TSTBrowser_destroyTab(aTab) + { + var id = aTab.getAttribute(this.kID); + if (id in this.tabsHash && + aTab == this.tabsHash[id]) + delete this.tabsHash[id]; + + if (aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) { + this.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + this.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave; + } + + delete aTab.__treestyletab__linkedTabBrowser; + }, + + _endListenTabbarEvents : function TSTBrowser_endListenTabbarEvents() + { + var b = this.mTabBrowser; + + var tabContainer = b.mTabContainer; + tabContainer.removeEventListener('TabOpen', this, true); + tabContainer.removeEventListener('TabClose', this, true); + tabContainer.removeEventListener('TabMove', this, true); + tabContainer.removeEventListener('TabShow', this, true); + tabContainer.removeEventListener('TabHide', this, true); + tabContainer.removeEventListener('SSTabRestoring', this, true); + tabContainer.removeEventListener('SSTabRestored', this, true); + tabContainer.removeEventListener('TabPinned', this, true); + tabContainer.removeEventListener('TabUnpinned', this, true); + tabContainer.removeEventListener('mouseover', this, true); + tabContainer.removeEventListener('mouseout', this, true); + tabContainer.removeEventListener('dblclick', this, true); + tabContainer.removeEventListener('select', this, true); + tabContainer.removeEventListener('scroll', this, true); + + var strip = this.tabStrip; + strip.removeEventListener('MozMouseHittest', this, true); + strip.removeEventListener('mousedown', this, true); + strip.removeEventListener('click', this, true); + strip.removeEventListener('DOMMouseScroll', this, true); + + this.scrollBox.removeEventListener('overflow', this, true); + this.scrollBox.removeEventListener('underflow', this, true); + }, + + saveCurrentState : function TSTBrowser_saveCurrentState() + { + this.autoHide.saveCurrentState(); + + var b = this.mTabBrowser; + var floatingBox = this.getTabStrip(b).boxObject; + var fixedBox = (this.tabStripPlaceHolder || this.getTabStrip(b)).boxObject; + var prefs = { + 'tabbar.fixed.horizontal' : b.getAttribute(this.kFIXED+'-horizontal') == 'true', + 'tabbar.fixed.vertical' : b.getAttribute(this.kFIXED+'-vertical') == 'true', + 'tabbar.width' : this.isVertical && this.autoHide.expanded && floatingBox.width ? floatingBox.width : void(0), + 'tabbar.shrunkenWidth' : this.isVertical && !this.autoHide.expanded && fixedBox.width ? fixedBox.width : void(0), + 'tabbar.height' : !this.isVertical && this.autoHide.expanded && floatingBox.height ? floatingBox.height : void(0) + }; + for (var i in prefs) + { + if (prefs[i] !== void(0) && utils.getTreePref(i) != prefs[i]) + utils.setTreePref(i, prefs[i]); + } + this.position = this.position; + }, + +/* toolbar customization */ + + syncDestroyTabbar : function TSTBrowser_syncDestroyTabbar() + { + this.stopRendering(); + + this._lastTreeViewEnabledBeforeDestroyed = this.treeViewEnabled; + this.treeViewEnabled = false; + this.maxTreeLevel = 0; + + this._lastTabbarPositionBeforeDestroyed = this.position; + if (this.position != 'top') { + let self = this; + this.doAndWaitDOMEvent( + this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, + this.window, + 100, + function() { + self.position = 'top'; + } + ); + } + + this.fixed = true; + + var tabbar = this.mTabBrowser.tabContainer; + tabbar.removeAttribute('width'); + tabbar.removeAttribute('height'); + tabbar.removeAttribute('ordinal'); + + this.removeTabStripAttribute('width'); + this.removeTabStripAttribute('height'); + this.removeTabStripAttribute('ordinal'); + this.removeTabStripAttribute('orient'); + + var toolbar = this.ownerToolbar; + this.destroyTabStrip(toolbar); + toolbar.classList.add(this.kTABBAR_TOOLBAR_READY); + + this._endListenTabbarEvents(); + + this.tabbarDNDObserver.endListenEvents(); + + this.window.setTimeout(function(aSelf) { + aSelf.updateCustomizedTabsToolbar(); + }, 100, this); + + this.startRendering(); + }, + destroyTabStrip : function TSTBrowser_destroyTabStrip(aTabStrip) + { + aTabStrip.classList.remove(this.kTABBAR_TOOLBAR); + aTabStrip.style.top = aTabStrip.style.left = aTabStrip.style.width = aTabStrip.style.height = ''; + aTabStrip.removeAttribute('height'); + aTabStrip.removeAttribute('width'); + aTabStrip.removeAttribute('ordinal'); + aTabStrip.removeAttribute('orient'); + }, + + syncReinitTabbar : function TSTBrowser_syncReinitTabbar() + { + this.stopRendering(); + + this.ownerToolbar.classList.add(this.kTABBAR_TOOLBAR); + this.ownerToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); + Array.slice(this.document.querySelectorAll('.'+this.kTABBAR_TOOLBAR_READY_POPUP)) + .forEach(this.safeRemovePopup, this); + + var position = this._lastTabbarPositionBeforeDestroyed || this.position; + delete this._lastTabbarPositionBeforeDestroyed; + + var self = this; + this.doAndWaitDOMEvent( + this.kEVENT_TYPE_TABBAR_INITIALIZED, + this.window, + 100, + function() { + self.initTabbar(position, 'top'); + } + ); + this.reinitAllTabs(true); + + this.tabbarDNDObserver.startListenEvents(); + + this.treeViewEnabled = this._lastTreeViewEnabledBeforeDestroyed; + delete this._lastTreeViewEnabledBeforeDestroyed; + + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET); + + this.startRendering(); + }, + + updateCustomizedTabsToolbar : function TSTBrowser_updateCustomizedTabsToolbar() + { + var d = this.document; + + var newToolbar = this.ownerToolbar; + newToolbar.classList.add(this.kTABBAR_TOOLBAR_READY); + + var oldToolbar = d.querySelector('.'+this.kTABBAR_TOOLBAR_READY); + if (oldToolbar == newToolbar) + return; + + if (oldToolbar && oldToolbar != newToolbar) { + this.safeRemovePopup(d.getElementById(oldToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP)); + oldToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); + } + + var id = newToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP; + var panel = d.getElementById(id); + if (!panel) { + panel = d.createElement('panel'); + panel.setAttribute('id', id); + panel.setAttribute('class', this.kTABBAR_TOOLBAR_READY_POPUP); + panel.setAttribute('noautohide', true); + panel.setAttribute('onmouseover', 'this.hidePopup()'); + panel.setAttribute('ondragover', 'this.hidePopup()'); + panel.appendChild(d.createElement('label')); + let position = this._lastTabbarPositionBeforeDestroyed || this.position; + let label = utils.treeBundle.getString('toolbarCustomizing_tabbar_'+(position == 'left' || position == 'right' ? 'vertical' : 'horizontal' )); + panel.firstChild.appendChild(d.createTextNode(label)); + d.getElementById('mainPopupSet').appendChild(panel); + } + panel.openPopup(newToolbar, 'end_after', 0, 0, false, false); + }, + safeRemovePopup : function TSTBrowser_safeRemovePopup(aPopup) + { + if (!aPopup) + return; + if (aPopup.state == 'open') { + aPopup.addEventListener('popuphidden', function onPopuphidden(aEvent) { + aPopup.removeEventListener(aEvent.type, onPopuphidden, false); + aPopup.parentNode.removeChild(aPopup); + }, false); + aPopup.hidePopup(); + } + else { + aPopup.parentNode.removeChild(aPopup); + } + }, + +/* nsIObserver */ + + domains : [ + 'extensions.treestyletab.', + 'browser.tabs.closeButtons', + 'browser.tabs.closeWindowWithLastTab', + 'browser.tabs.autoHide', + 'browser.tabs.animate' + ], + + observe : function TSTBrowser_observe(aSubject, aTopic, aData) + { + switch (aTopic) + { + case this.kTOPIC_INDENT_MODIFIED: + if (this.indent > -1) + this.updateAllTabsIndent(); + return; + + case this.kTOPIC_COLLAPSE_EXPAND_ALL: + if (!aSubject || aSubject == this.window) { + aData = String(aData); + this.collapseExpandAllSubtree( + aData.indexOf('collapse') > -1, + aData.indexOf('now') > -1 + ); + } + return; + + case 'lightweight-theme-styling-update': + return this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); + + case this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY: + return this.treeViewEnabled = (aData != 'false'); + + case 'nsPref:changed': + return this.onPrefChange(aData); + + default: + return; + } + }, + + onPrefChange : function TSTBrowser_onPrefChange(aPrefName) + { + // ignore after destruction + if (!this.window || !this.window.TreeStyleTabService) + return; + + var b = this.mTabBrowser; + var value = prefs.getPref(aPrefName); + var tabContainer = b.mTabContainer; + var tabs = this.getAllTabs(b); + switch (aPrefName) + { + case 'extensions.treestyletab.tabbar.position': + if (this.shouldApplyNewPref) + this.position = value; + return; + + case 'extensions.treestyletab.tabbar.invertTab': + case 'extensions.treestyletab.tabbar.multirow': + this.initTabbar(); + this.updateAllTabsIndent(); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.initTabContents(tabs[i]); + } + return; + case 'extensions.treestyletab.tabbar.invertTabContents': + this.setTabbrowserAttribute(this.kTAB_CONTENTS_INVERTED, value); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.initTabContents(tabs[i]); + } + return; + + case 'extensions.treestyletab.tabbar.invertClosebox': + this.setTabbrowserAttribute(this.kCLOSEBOX_INVERTED, value); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.initTabContents(tabs[i]); + } + return; + + case 'extensions.treestyletab.tabbar.style': + case 'extensions.treestyletab.tabbar.style.aero': + this.setTabbarStyle(utils.getTreePref('tabbar.style')); + value = utils.getTreePref('twisty.style'); + if (value != 'auto') + return; + case 'extensions.treestyletab.twisty.style': + return this.setTwistyStyle(value); + + case 'extensions.treestyletab.showBorderForFirstTab': + return this.setTabbrowserAttribute(this.kFIRSTTAB_BORDER, value); + + case 'extensions.treestyletab.tabbar.fixed.horizontal': + if (!this.shouldApplyNewPref) + return; + this.setTabbrowserAttribute(this.kFIXED+'-horizontal', value ? 'true' : null, b); + case 'extensions.treestyletab.maxTreeLevel.horizontal': + case 'extensions.treestyletab.allowSubtreeCollapseExpand.horizontal': + if (!this.isVertical) + this.updateTabbarState(true); + return; + + case 'extensions.treestyletab.tabbar.fixed.vertical': + if (!this.shouldApplyNewPref) + return; + this.setTabbrowserAttribute(this.kFIXED+'-vertical', value ? 'true' : null, b); + case 'extensions.treestyletab.maxTreeLevel.vertical': + case 'extensions.treestyletab.allowSubtreeCollapseExpand.vertical': + if (this.isVertical) + this.updateTabbarState(true); + return; + + case 'extensions.treestyletab.tabbar.width': + case 'extensions.treestyletab.tabbar.shrunkenWidth': + if (!this.shouldApplyNewPref) + return; + if (!this.autoHide.isResizing && this.isVertical) { + this.removeTabStripAttribute('width'); + this.setTabStripAttribute('width', this.autoHide.placeHolderWidthFromMode); + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); + } + this.checkTabsIndentOverflow(); + return; + + case 'extensions.treestyletab.tabbar.height': + if (!this.shouldApplyNewPref) + return; + this._horizontalTabMaxIndentBase = 0; + this.checkTabsIndentOverflow(); + return; + + case 'extensions.treestyletab.tabbar.autoShow.mousemove': + let (toggler = this.document.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER)) { + if (toggler) { + if (value) + toggler.removeAttribute('hidden'); + else + toggler.setAttribute('hidden', true); + } + } + return; + + case 'extensions.treestyletab.tabbar.invertScrollbar': + this.setTabbrowserAttribute(this.kINVERT_SCROLLBAR, value); + this.positionPinnedTabs(); + return; + + case 'extensions.treestyletab.tabbar.narrowScrollbar': + return this.setTabbrowserAttribute(this.kNARROW_SCROLLBAR, value); + + case 'extensions.treestyletab.maxTreeLevel.phisical': + if (this.maxTreeLevelPhisical = value) + this.promoteTooDeepLevelTabs(); + return; + + case 'browser.tabs.animate': + this.setTabbrowserAttribute(this.kANIMATION_ENABLED, + prefs.getPref('browser.tabs.animate') !== false + ? 'true' : null + ); + return; + + case 'browser.tabs.closeButtons': + case 'browser.tabs.closeWindowWithLastTab': + return this.updateInvertedTabContentsOrder(true); + + case 'browser.tabs.autoHide': + if (this.getTabs(this.mTabBrowser).length == 1) + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); + return; + + case 'extensions.treestyletab.tabbar.autoHide.mode': + case 'extensions.treestyletab.tabbar.autoHide.mode.fullscreen': + return this.autoHide; // ensure initialized + + case 'extensions.treestyletab.pinnedTab.faviconized': + return this.positionPinnedTabsWithDelay(); + + case 'extensions.treestyletab.counter.role.horizontal': + if (!this.isVertical) { + let self = this; + if (this.deferredTasks[aPrefName]) + this.deferredTasks[aPrefName].cancel(); + (this.deferredTasks[aPrefName] = this.Deferred + .next(function() { self.updateAllTabsCount(); })) + .error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[aPrefName]; + }); + } + return; + case 'extensions.treestyletab.counter.role.vertical': + if (this.isVertical) { + let self = this; + if (this.deferredTasks[aPrefName]) + this.deferredTasks[aPrefName].cancel(); + (this.deferredTasks[aPrefName] = this.Deferred + .next(function() { self.updateAllTabsCount(); })) + .error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[aPrefName]; + }); + } + return; + + default: + return; + } + }, + setTabbarStyle : function TSTBrowser_setTabbarStyle(aStyle) + { + if (/^(default|plain|flat|mixed|vertigo|metal|sidebar)(-aero)?$/.test(aStyle)) + aStyle = aStyle.toLowerCase(); + + if (aStyle.indexOf('default') == 0) { // old name (for compatibility) + utils.setTreePref('tabbar.style', aStyle = aStyle.replace('default', 'plain')); + } + + if (aStyle) { + let additionalValues = []; + if (/^(plain|flat|mixed|vertigo)$/.test(aStyle)) + additionalValues.push('square'); + if (/^(plain|flat|mixed)$/.test(aStyle)) + additionalValues.push('border'); + if (/^(flat|mixed)$/.test(aStyle)) + additionalValues.push('color'); + if (/^(plain|mixed)$/.test(aStyle)) + additionalValues.push('shadow'); + if (utils.getTreePref('tabbar.style.aero')) + additionalValues.push('aero'); + if (additionalValues.length) + aStyle = additionalValues.join(' ')+' '+aStyle; + + this.setTabbrowserAttribute(this.kSTYLE, aStyle); + } + else { + this.removeTabbrowserAttribute(this.kSTYLE); + } + }, + setTwistyStyle : function TSTBrowser_setTwistyStyle(aStyle) + { + if (aStyle != 'auto') { + this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); + return; + } + + aStyle = 'modern-black'; + + if (utils.getTreePref('tabbar.style') == 'sidebar') { + aStyle = 'osx'; + } + else if ( + prefs.getPref('extensions.informationaltab.thumbnail.enabled') && + prefs.getPref('extensions.informationaltab.thumbnail.position') < 100 + ) { + let self = this; + this.extensions.isAvailable('informationaltab@piro.sakura.ne.jp', { + ok : function() { + aStyle = 'retro'; + self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); + }, + ng : function() { + self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); + } + }); + return; + } + + this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); + }, + +/* DOM Event Handling */ + + handleEvent : function TSTBrowser_handleEvent(aEvent) + { + switch (aEvent.type) + { + case 'TabOpen': + return this.onTabOpen(aEvent); + + case 'TabClose': + return this.onTabClose(aEvent); + + case 'TabMove': + return this.onTabMove(aEvent); + + case 'TabShow': + case 'TabHide': + return this.onTabVisibilityChanged(aEvent); + + case 'SSTabRestoring': + return this.onTabRestoring(aEvent); + + case 'SSTabRestored': + return this.onTabRestored(aEvent); + + case 'TabPinned': + return this.onTabPinned(aEvent.originalTarget); + + case 'TabUnpinned': + return this.onTabUnpinned(aEvent.originalTarget); + + case 'select': + return this.onTabSelect(aEvent); + + case 'click': + return this.onClick(aEvent); + + case 'dblclick': + return this.onDblClick(aEvent); + + case 'MozMouseHittest': // to block default behaviors of the tab bar + return this.onMozMouseHittest(aEvent); + + case 'mousedown': + return this.onMouseDown(aEvent); + + case 'DOMMouseScroll': + return this.onDOMMouseScroll(aEvent); + + case 'scroll': + return this.onScroll(aEvent); + + case 'popupshowing': + return this.onPopupShowing(aEvent); + + case 'popuphiding': + return this.onPopupHiding(aEvent); + + case 'mouseover': + if (!this.tooltipManager) + this._initTooltipManager(); + if (!this._DNDObserversInitialized) + this._initDNDObservers(); + let (tab = aEvent.target) { + if (tab.__treestyletab__twistyHoverTimer) + this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer); + if (this.isEventFiredOnTwisty(aEvent)) { + tab.setAttribute(this.kTWISTY_HOVER, true); + tab.__treestyletab__twistyHoverTimer = this.window.setTimeout(function(aSelf) { + tab.setAttribute(aSelf.kTWISTY_HOVER, true); + delete tab.__treestyletab__twistyHoverTimer; + }, 0, this); + } + } + return; + + case 'mouseout': + let (tab = aEvent.target) { + if (tab.__treestyletab__twistyHoverTimer) { + this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer); + delete tab.__treestyletab__twistyHoverTimer; + } + tab.removeAttribute(this.kTWISTY_HOVER); + } + return; + + case 'dragover': + if (!this.tooltipManager) + this._initTooltipManager(); + if (!this._DNDObserversInitialized) + this._initDNDObservers(); + return; + + case 'overflow': + case 'underflow': + return this.onTabbarOverflow(aEvent); + + case 'resize': + return this.onResize(aEvent); + + + // toolbar customizing + case 'beforecustomization': + this.toolbarCustomizing = true; + return this.syncDestroyTabbar(); + case 'aftercustomization': + // Ignore it, because 'aftercustomization' fired not + // following to 'beforecustomization' is invalid. + // Personal Titlebar addon (or others) fires a fake + // event on its startup process. + if (!this.toolbarCustomizing) + return; + this.toolbarCustomizing = false; + return this.syncReinitTabbar(); + case 'customizationchange': + return this.updateCustomizedTabsToolbar(); + + case 'tabviewframeinitialized': + return this.lastTabViewGroup = this.getTabViewGroupId(); + + + case this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED: + return this.onTreeStyleTabPrintPreviewEntered(aEvent); + case this.kEVENT_TYPE_PRINT_PREVIEW_EXITED: + return this.onTreeStyleTabPrintPreviewExited(aEvent); + + + case this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END: + return this.cancelDelayedExpandOnTabSelect(); + + + case 'SSWindowStateBusy': + return this.needRestoreTree = true; + + + case 'nsDOMMultipleTabHandlerTabsClosing': + if (!this.onTabsClosing(aEvent)) + aEvent.preventDefault(); + return; + } + }, + lastScrollX : -1, + lastScrollY : -1, + + restoreLastScrollPosition : function TSTBrowser_restoreLastScrollPosition() + { + if (this.lastScrollX < 0 || this.lastScrollY < 0 || !this.isVisible) + return; + var lastX = this.lastScrollX; + var lastY = this.lastScrollY; + this.clearLastScrollPosition(); + if (!this.smoothScrollTask && + !this.scrollBox._smoothScrollTimer) { // don't restore scroll position if another scroll is already running. + let x = {}, y = {}; + let scrollBoxObject = this.scrollBoxObject; + scrollBoxObject.getPosition(x, y); + if (x.value != lastX || y.value != lastY) + scrollBoxObject.scrollTo(lastX, lastY); + } + }, + + clearLastScrollPosition : function TSTBrowser_clearLastScrollPosition() + { + this.lastScrollX = this.lastScrollY = -1; + }, + + updateLastScrollPosition : function TSTBrowser_updateLastScrollPosition() + { + if (!this.isVertical || !this.isVisible) + return; + var x = {}, y = {}; + var scrollBoxObject = this.scrollBoxObject; + if (!scrollBoxObject) + return; + scrollBoxObject.getPosition(x, y); + this.lastScrollX = x.value; + this.lastScrollY = y.value; + }, + + cancelPerformingAutoScroll : function TSTBrowser_cancelPerformingAutoScroll(aOnlyCancel) + { + if (this.smoothScrollTask) { + this.animationManager.removeTask(this.smoothScrollTask); + this.smoothScrollTask = null; + } + this.clearLastScrollPosition(); + + if (this.deferredTasks['cancelPerformingAutoScroll']) { + this.deferredTasks['cancelPerformingAutoScroll'].cancel(); + delete this.deferredTasks['cancelPerformingAutoScroll']; + } + + if (aOnlyCancel) + return; + + var self = this; + (this.deferredTasks['cancelPerformingAutoScroll'] = this.Deferred.wait(0.3)) + .next(function() { + delete self.deferredTasks['cancelPerformingAutoScroll']; + }).error(this.defaultDeferredErrorHandler); + }, + + shouldCancelEnsureElementIsVisible : function TSTBRowser_shouldCancelEnsureElementIsVisible() + { + return ( + this.deferredTasks['cancelPerformingAutoScroll'] && + (new Error()).stack.indexOf('onxblDOMMouseScroll') < 0 + ); + }, + + onTabOpen : function TSTBrowser_onTabOpen(aEvent, aTab) + { + var tab = aTab || aEvent.originalTarget; + var b = this.mTabBrowser; + + if (this.isTabInitialized(tab)) + return false; + + this.initTab(tab); + + var hasStructure = this.treeStructure && this.treeStructure.length; + var pareintIndexInTree = hasStructure ? this.treeStructure.shift() : 0 ; + var lastRelatedTab = b._lastRelatedTab; + + if (this.readiedToAttachNewTab) { + if (pareintIndexInTree < 0) { // there is no parent, so this is a new parent! + this.parentTab = tab.getAttribute(this.kID); + } + + let parent = this.getTabById(this.parentTab); + if (parent) { + let tabs = [parent].concat(this.getDescendantTabs(parent)); + parent = pareintIndexInTree > -1 && pareintIndexInTree < tabs.length ? tabs[pareintIndexInTree] : parent ; + } + if (parent) { + this.attachTabTo(tab, parent, { + dontExpand : this.shouldExpandAllTree + }); + } + + let refTab; + let newIndex = -1; + if (hasStructure) { + } + else if (this.insertBefore && + (refTab = this.getTabById(this.insertBefore))) { + newIndex = refTab._tPos; + } + else if ( + parent && + utils.getTreePref('insertNewChildAt') == this.kINSERT_FISRT && + (this.multipleCount <= 0 || this._addedCountInThisLoop <= 0) + ) { + /* 複数の子タブを一気に開く場合、最初に開いたタブだけを + 子タブの最初の位置に挿入し、続くタブは「最初の開いたタブ」と + 「元々最初の子だったタブ」との間に挿入していく */ + newIndex = parent._tPos + 1; + if (refTab = this.getFirstChildTab(parent)) + this.insertBefore = refTab.getAttribute(this.kID); + } + + if (newIndex > -1) { + if (newIndex > tab._tPos) + newIndex--; + this.internallyTabMovingCount++; + b.moveTabTo(tab, newIndex); + this.internallyTabMovingCount--; + } + + if (this.shouldExpandAllTree) + this.collapseExpandSubtree(parent, false); + } + + this._addedCountInThisLoop++; + if (!this._addedCountClearTimer) { + this._addedCountClearTimer = this.window.setTimeout(function(aSelf) { + aSelf._addedCountInThisLoop = 0; + aSelf._addedCountClearTimer = null; + }, 0, this); + } + + if (!this.readiedToAttachMultiple) { + this.stopToOpenChildTab(b); + } + else { + this.multipleCount++; + } + + if (this.animationEnabled) { + this.updateTabCollapsed(tab, true, true); + let self = this; + this.updateTabCollapsed(tab, false, this.windowService.restoringTree, function() { + /** + * When the system is too slow, the animation can start after + * smooth scrolling is finished. The smooth scrolling should be + * started together with the start of the animation effect. + */ + self.scrollToNewTab(tab); + }); + } + else { + this.scrollToNewTab(tab); + } + + this.updateInsertionPositionInfo(tab); + + if (prefs.getPref('browser.tabs.autoHide')) + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); + + if (this.canStackTabs) + this.updateTabsZIndex(true); + + // if there is only one tab and new another tab is opened, + // closebox appearance is possibly changed. + var tabs = this.getTabs(b); + if (tabs.length == 2) + this.updateInvertedTabContentsOrder(tabs); + + /** + * gBrowser.addTab() resets gBrowser._lastRelatedTab.owner + * when a new background tab is opened from the current tab, + * but it will fail with TST because gBrowser.moveTab() (called + * by TST) clears gBrowser._lastRelatedTab. + * So, we have to restore gBrowser._lastRelatedTab manually. + */ + b._lastRelatedTab = lastRelatedTab; + + return true; + }, + _addedCountInThisLoop : 0, + _addedCountClearTimer : null, + _checkRestoringWindowTimerOnTabAdded : null, + + scrollToNewTab : function TSTBrowser_scrollToNewTab(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + if (this.scrollToNewTabMode > 0) + this.scrollToTab(aTab, this.scrollToNewTabMode < 2); + }, + + updateInsertionPositionInfo : function TSTBrowser_updateInsertionPositionInfo(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var prev = this.getPreviousSiblingTab(aTab); + if (prev) { + this.setTabValue(aTab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); + this.setTabValue(prev, this.kINSERT_BEFORE, aTab.getAttribute(this.kID)); + } + + var next = this.getNextSiblingTab(aTab); + if (next) { + this.setTabValue(aTab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); + this.setTabValue(next, this.kINSERT_AFTER, aTab.getAttribute(this.kID)); + } + }, + + onTabClose : function TSTBrowser_onTabClose(aEvent) + { + var tab = aEvent.originalTarget; + var d = this.document; + var b = this.mTabBrowser; + + tab.setAttribute(this.kREMOVED, true); + + this.stopTabIndentAnimation(tab); + this.stopTabCollapseAnimation(tab); + + var closeParentBehavior = this.getCloseParentBehaviorForTab(tab); + + var collapsed = this.isCollapsed(tab); + if (collapsed) + this.stopRendering(); + + var backupAttributes = this._collectBackupAttributes(tab); + if (DEBUG) + dump('onTabClose: backupAttributes = '+JSON.stringify(backupAttributes)+'\n'); + + if (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN || + this.isSubtreeCollapsed(tab)) + this._closeChildTabs(tab); + + this._saveAndUpdateReferenceTabsInfo(tab); + + var firstChild = this.getFirstChildTab(tab); + + this.detachAllChildren(tab, { + behavior : closeParentBehavior + }); + + var nextFocusedTab = null; + if (firstChild && + (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN || + closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD)) + nextFocusedTab = firstChild; + + var toBeClosedTabs = this._collectNeedlessGroupTabs(tab); + + var parentTab = this.getParentTab(tab); + if (parentTab) { + if (!nextFocusedTab && tab == this.getLastChildTab(parentTab)) { + if (tab == this.getFirstChildTab(parentTab)) // this is the really last child + nextFocusedTab = parentTab; + else + nextFocusedTab = this.getPreviousSiblingTab(tab); + } + + if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1) + nextFocusedTab = this.getNextFocusedTab(parentTab); + } + else if (!nextFocusedTab) { + nextFocusedTab = this.getNextFocusedTab(tab); + } + + if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1) + nextFocusedTab = this.getNextFocusedTab(nextFocusedTab); + + if (nextFocusedTab && nextFocusedTab.hasAttribute(this.kREMOVED)) + nextFocusedTab = null; + + this._reserveCloseRelatedTabs(toBeClosedTabs); + + this.detachTab(tab, { dontUpdateIndent : true }); + + this._restoreTabAttributes(tab, backupAttributes); + + if (b.selectedTab == tab) + this._tryMoveFocusFromClosingCurrentTab(nextFocusedTab); + + this.updateLastScrollPosition(); + + this.destroyTab(tab); + + if (tab.getAttribute('pinned') == 'true') + this.positionPinnedTabsWithDelay(); + + if (prefs.getPref('browser.tabs.autoHide')) + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); + + if (this.canStackTabs) + this.updateTabsZIndex(true); + + if (collapsed) + this.startRendering(); + }, + + _collectBackupAttributes : function TSTBrowser_collectBackupAttributes(aTab) + { + var attributes = {}; + + if (this.hasChildTabs(aTab)) { + attributes[this.kCHILDREN] = this.getTabValue(aTab, this.kCHILDREN); + attributes[this.kSUBTREE_COLLAPSED] = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED); + } + + var ancestors = this.getAncestorTabs(aTab); + if (ancestors.length) { + let next = this.getNextSiblingTab(aTab); + ancestors = ancestors.map(function(aAncestor) { + if (!next && (next = this.getNextSiblingTab(aAncestor))) + attributes[this.kINSERT_BEFORE] = next.getAttribute(this.kID); + return aAncestor.getAttribute(this.kID); + }, this); + attributes[this.kANCESTOR] = ancestors.join('|'); + } + + return attributes; + }, + + _closeChildTabs : function TSTBrowser_closeChildTabs(aTab) + { + var tabs = this.getDescendantTabs(aTab); + if (!this.fireTabSubtreeClosingEvent(aTab, tabs)) + return; + + var subtreeCollapsed = this.isSubtreeCollapsed(aTab); + if (subtreeCollapsed) + this.stopRendering(); + + this.markAsClosedSet([aTab].concat(tabs)); + + tabs.reverse(); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.mTabBrowser.removeTab(tabs[i], { animate : true }); + } + + this.fireTabSubtreeClosedEvent(this.mTabBrowser, aTab, tabs); + + if (subtreeCollapsed) + this.startRendering(); + }, + + _collectNeedlessGroupTabs : function TSTBrowser_collectNeedlessGroupTabs(aTab) + { + var tabs = []; + if (!aTab || !aTab.parentNode) + return tabs; + + var parent = this.getParentTab(aTab); + var siblings = this.getSiblingTabs(aTab); + var groupTabs = siblings.filter(function(aTab) { + return this.isTemporaryGroupTab(aTab); + }, this); + var groupTab = ( + groupTabs.length == 1 && + siblings.length == 1 && + this.hasChildTabs(groupTabs[0]) + ) ? groupTabs[0] : null ; + if (groupTab) + tabs.push(groupTab); + + var shouldCloseParentTab = ( + parent && + this.isTemporaryGroupTab(parent) && + this.getDescendantTabs(parent).length == 1 + ); + if (shouldCloseParentTab) + tabs.push(parent); + + return tabs; + }, + + _reserveCloseRelatedTabs : function TSTBrowser_reserveCloseRelatedTabs(aTabs) + { + if (!aTabs.length) + return; + + var key = 'onTabClose_'+parseInt(Math.random() * 65000); + var self = this; + (this.deferredTasks[key] = this.Deferred.next(function() { + aTabs.forEach(function(aTab) { + if (aTab.parentNode) + self.mTabBrowser.removeTab(aTab, { animate : true }); + }); + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }).next(function() { + aTabs = null; + self = null; + key = null; + }); + }, + + _saveAndUpdateReferenceTabsInfo : function TSTBrowser_saveAndUpdateReferenceTabsInfo(aTab) + { + var prev = this.getPreviousSiblingTab(aTab); + var next = this.getNextSiblingTab(aTab); + if (prev) { + this.setTabValue(aTab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); + + if (next) + this.setTabValue(prev, this.kINSERT_BEFORE, next.getAttribute(this.kID)); + else + this.deleteTabValue(prev, this.kINSERT_BEFORE); + } + if (next) { + this.setTabValue(aTab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); + + if (prev) + this.setTabValue(next, this.kINSERT_AFTER, prev.getAttribute(this.kID)); + else + this.deleteTabValue(next, this.kINSERT_AFTER); + } + }, + + _restoreTabAttributes : function TSTBrowser_restoreTabAttributes(aTab, aAttributes) + { + for (var i in aAttributes) + { + this.setTabValue(aTab, i, aAttributes[i]); + } + }, + + _tryMoveFocusFromClosingCurrentTab : function TSTBrowser_tryMoveFocusFromClosingCurrentTab(aNextFocusedTab) + { + if (!aNextFocusedTab || aNextFocusedTab.hidden) + return; + + var currentTab = this.mTabBrowser.selectedTab; + var d = this.document; + + var event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB, true, true); + var canFocus = currentTab.dispatchEvent(event); + + // for backward compatibility + event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB.replace(/^nsDOM/, ''), true, true); + canFocus = canFocus && currentTab.dispatchEvent(event); + + if (canFocus) { + this._focusChangedByCurrentTabRemove = true; + this.mTabBrowser.selectedTab = aNextFocusedTab; + } + }, + + onTabsClosing : function TSTBrowser_onTabsClosing(aEvent) + { + var tabs = aEvent.detail && aEvent.detail.tabs || + aEvent.getData('tabs') // for backward compatibility; + var b = this.getTabBrowserFromChild(tabs[0]); + + var trees = this.splitTabsToSubtrees(tabs); + if (trees.some(function(aTabs) { + return aTabs.length > 1 && + !this.fireTabSubtreeClosingEvent(aTabs[0], aTabs); + }, this)) + return false; + + trees.forEach(this.markAsClosedSet, this); + + var self = this; + let key = 'onTabClosing_'+parseInt(Math.random() * 65000); + (this.deferredTasks[key] = this.Deferred.next(function() { + for (let i = 0, maxi = trees.length; i < maxi; i++) + { + let tabs = trees[i]; + self.fireTabSubtreeClosedEvent(b, tabs[0], tabs); + } + })).error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + + return true; + }, + + onTabMove : function TSTBrowser_onTabMove(aEvent) + { + var tab = aEvent.originalTarget; + var b = this.mTabBrowser; + tab.__treestyletab__previousPosition = aEvent.detail; + + // When the tab was moved before TabOpen event is fired, we have to update manually. + var newlyOpened = !this.isTabInitialized(tab) && this.onTabOpen(null, tab); + + // twisty vanished after the tab is moved!! + this.initTabContents(tab); + + // On Firefox 29, 30 and laters, reopened (restored) tab can be + // placed in wrong place, because "TabMove" event fires before + // "SSTabRestoring" event and "kINSERT_BEFORE" information is + // unexpectedly cleared. So now I simulate the "SSTabRestoring" + // event here. + // See: https://github.com/piroor/treestyletab/issues/676#issuecomment-47700158 + if (tab.__SS_extdata) { + let storedId = tab.__SS_extdata[this.kID]; // getTabValue() doesn't get the value! + if (storedId && tab.getAttribute(this.kID) != storedId) + this.onTabRestoring(aEvent); + } + + if (this.hasChildTabs(tab) && !this.subTreeMovingCount) { + this.moveTabSubtreeTo(tab, tab._tPos); + } + + var parentTab = this.getParentTab(tab); + if (parentTab && !this.subTreeChildrenMovingCount) { + this.updateChildrenArray(parentTab); + } + + this.updateTabsCount(tab, true); + + var prev = this.getPreviousSiblingTab(tab); + var next = this.getNextSiblingTab(tab); + + if (prev) { + this.setTabValue(prev, this.kINSERT_BEFORE, tab.getAttribute(this.kID)); + this.setTabValue(tab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); + } + else + this.deleteTabValue(tab, this.kINSERT_AFTER); + + if (next) { + this.setTabValue(next, this.kINSERT_AFTER, tab.getAttribute(this.kID)); + this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); + } + else + this.deleteTabValue(tab, this.kINSERT_BEFORE); + + var old = aEvent.detail; + if (old > tab._tPos) + old--; + var tabs = this.getAllTabs(b); + old = tabs[old]; + + prev = this.getPreviousSiblingTab(old); + next = this.getNextSiblingTab(old); + + if (prev) { + this.setTabValue(prev, this.kINSERT_BEFORE, old.getAttribute(this.kID)); + this.setTabValue(old, this.kINSERT_AFTER, prev.getAttribute(this.kID)); + } + else + this.deleteTabValue(old, this.kINSERT_AFTER); + + if (next) { + this.setTabValue(next, this.kINSERT_AFTER, old.getAttribute(this.kID)); + this.setTabValue(old, this.kINSERT_BEFORE, next.getAttribute(this.kID)); + } + else + this.deleteTabValue(old, this.kINSERT_BEFORE); + + this.positionPinnedTabsWithDelay(); + + if (this.canStackTabs) + this.updateTabsZIndex(true); + + if ( + this.subTreeMovingCount || + this.internallyTabMovingCount || + // We don't have to fixup tree structure for a NEW TAB + // which has already been structured. + (newlyOpened && this.getParentTab(tab)) + ) + return; + + this.attachTabFromPosition(tab, aEvent.detail); + this.rearrangeTabViewItems(tab); + }, + + attachTabFromPosition : function TSTBrowser_attachTabFromPosition(aTab, aOldPosition) + { + var parent = this.getParentTab(aTab); + + if (aOldPosition === void(0)) + aOldPosition = aTab._tPos; + + var pos = this.getChildIndex(aTab, parent); + var oldPos = this.getChildIndex(this.getAllTabs(this.mTabBrowser)[aOldPosition], parent); + var delta; + if (pos == oldPos) { // no move? + return; + } + else if (pos < 0 || oldPos < 0) { + delta = 2; + } + else { + delta = Math.abs(pos - oldPos); + } + + var prevTab = this.getPreviousTab(aTab); + var nextTab = this.getNextTab(aTab); + + var tabs = this.getDescendantTabs(aTab); + if (tabs.length) { + nextTab = this.getNextTab(tabs[tabs.length-1]); + } + + var prevParent = this.getParentTab(prevTab); + var nextParent = this.getParentTab(nextTab); + + var prevLevel = prevTab ? Number(prevTab.getAttribute(this.kNEST)) : -1 ; + var nextLevel = nextTab ? Number(nextTab.getAttribute(this.kNEST)) : -1 ; + + var newParent; + + if (!prevTab) { // moved to topmost position + newParent = null; + } + else if (!nextTab) { // moved to last position + newParent = (delta > 1) ? prevParent : parent ; + } + else if (prevParent == nextParent) { // moved into existing tree + newParent = prevParent; + } + else if (prevLevel > nextLevel) { // moved to end of existing tree + if (this.mTabBrowser.selectedTab != aTab) { // maybe newly opened tab + newParent = prevParent; + } + else { // maybe drag and drop + var realDelta = Math.abs(aTab._tPos - aOldPosition); + newParent = realDelta < 2 ? prevParent : (parent || nextParent) ; + } + } + else if (prevLevel < nextLevel) { // moved to first child position of existing tree + newParent = parent || nextParent; + } + + if (newParent != parent) { + if (newParent) { + if (newParent.hidden == aTab.hidden) + this.attachTabTo(aTab, newParent, { insertBefore : nextTab }); + } + else { + this.detachTab(aTab); + } + } + }, + + updateChildrenArray : function TSTBrowser_updateChildrenArray(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var children = this.getChildTabs(aTab); + children.sort(this.sortTabsByOrder); + this.setTabValue( + aTab, + this.kCHILDREN, + children + .map(function(aItem) { + return aItem.getAttribute(this.kID); + }, this) + .join('|') + ); + }, + + // for TabView (Panorama aka Tab Candy) + rearrangeTabViewItems : function TSTBrowser_rearrangeTabViewItems(aTab) + { + if ( + !aTab.parentNode || // do nothing for closed tab! + !aTab.tabItem || + !aTab.tabItem.parent || + !aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder + ) + return; + + aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder(); + }, + + // for TabView (Panorama aka Tab Candy) + onTabVisibilityChanged : function TSTBrowser_onTabVisibilityChanged(aEvent) + { + /** + * Note: On this timing, we cannot know that which is the reason of this + * event, by exitting from Panorama or the "Move to Group" command in the + * context menu on tabs. So, we have to do operations with a delay to compare + * last and current group which is updated in the next event loop. + */ + + var tab = aEvent.originalTarget; + this.updateInvertedTabContentsOrder(tab); + this.tabVisibilityChangedTabs.push({ + tab : tab, + type : aEvent.type + }); + + if (this.tabVisibilityChangedTimer) { + this.window.clearTimeout(this.tabVisibilityChangedTimer); + this.tabVisibilityChangedTimer = null; + } + + this.tabVisibilityChangedTimer = this.window.setTimeout(function(aSelf) { + aSelf.tabVisibilityChangedTimer = null; + + var tabs = aSelf.tabVisibilityChangedTabs; + if (!tabs.length) + return; + + // restore tree from bottom safely + var restoreTabs = tabs.filter(function(aChanged) { + return aChanged.type == 'TabShow' && + aChanged.tab.__treestyletab__restoreState == aSelf.RESTORE_STATE_READY_TO_RESTORE; + }) + .map(function(aChanged) { + return aChanged.tab; + }) + .sort(function(aA, aB) { + return aB._tPos - aA._tPos; + }) + .filter(aSelf.restoreOneTab, aSelf); + for (let i = 0, maxi = restoreTabs.length; i < maxi; i++) + { + let tab = restoreTabs[i]; + aSelf.updateInsertionPositionInfo(tab); + delete tab.__treestyletab__restoreState; + } + + var currentGroupId = aSelf.getTabViewGroupId(); + if (aSelf.lastTabViewGroup && currentGroupId != aSelf.lastTabViewGroup) { + // We should clear it first, because updateTreeByTabVisibility() never change visibility of tabs. + aSelf.tabVisibilityChangedTabs = []; + aSelf.updateTreeByTabVisibility(tabs.map(function(aChanged) { return aChanged.tab; })); + } + else { + // For tabs moved by "Move to Group" command in the context menu on tabs + var processedTabs = {}; + /** + * subtreeFollowParentAcrossTabGroups() can change visibility of child tabs, so, + * we must not clear tabVisibilityChangedTabs here, and we have to use + * simple "for" loop instead of Array.prototype.forEach. + */ + for (let i = 0; i < aSelf.tabVisibilityChangedTabs.length; i++) + { + let changed = aSelf.tabVisibilityChangedTabs[i]; + let tab = changed.tab; + if (aSelf.getAncestorTabs(tab).some(function(aTab) { + return processedTabs[aTab.getAttribute(aSelf.kID)]; + })) + continue; + aSelf.subtreeFollowParentAcrossTabGroups(tab); + processedTabs[tab.getAttribute(aSelf.kID)] = true; + } + // now we can clear it! + aSelf.tabVisibilityChangedTabs = []; + } + aSelf.lastTabViewGroup = currentGroupId; + aSelf.checkTabsIndentOverflow(); + }, 0, this); + }, + tabVisibilityChangedTimer : null, + lastTabViewGroup : null, + + updateTreeByTabVisibility : function TSTBrowser_updateTreeByTabVisibility(aChangedTabs) + { + this.internallyTabMovingCount++; + + var allTabs = this.getAllTabs(this.mTabBrowser); + var normalTabs = allTabs.filter(function(aTab) { + return !aTab.hasAttribute('pinned'); + }); + aChangedTabs = aChangedTabs || normalTabs; + + var shownTabs = aChangedTabs.filter(function(aTab) { + return !aTab.hidden; + }); + + var movingTabToAnotherGroup = !shownTabs.length; + var switchingGroup = !movingTabToAnotherGroup; + + var lastIndex = allTabs.length - 1; + var lastMovedTab; + normalTabs = normalTabs.slice(0).reverse(); + for (let i = 0, maxi = normalTabs.length; i < maxi; i++) + { + let tab = normalTabs[i]; + let parent = this.getParentTab(tab); + let attached = false; + if (parent && (tab.hidden != parent.hidden)) { + let lastNextTab = null; + this.getAncestorTabs(tab).some(function(aAncestor) { + if (aAncestor.hidden == tab.hidden) { + this.attachTabTo(tab, aAncestor, { + dontMove : true, + insertBefore : lastNextTab + }); + attached = true; + return true; + } + lastNextTab = this.getNextSiblingTab(aAncestor); + return false; + }, this); + if (!attached) { + this.collapseExpandTab(tab, false, true); + this.detachTab(tab); + } + } + + if (aChangedTabs.indexOf(tab) < 0) + continue; + + if ( + switchingGroup && + !tab.hidden && + !attached && + !parent + ) { + let prev = this.getPreviousTab(tab); + let next = this.getNextTab(tab); + if ( + (prev && aChangedTabs.indexOf(prev) < 0 && !prev.hidden) || + (next && aChangedTabs.indexOf(next) < 0 && !next.hidden) + ) + this.attachTabFromPosition(tab, lastIndex); + } + + if (movingTabToAnotherGroup && tab.hidden) { + let index = lastMovedTab ? lastMovedTab._tPos - 1 : lastIndex ; + this.mTabBrowser.moveTabTo(tab, index); + lastMovedTab = tab; + } + } + this.internallyTabMovingCount--; + }, + + subtreeFollowParentAcrossTabGroups : function TSTBrowser_subtreeFollowParentAcrossTabGroups(aParent) + { + if (this.tabViewTreeIsMoving) + return; + + var id = this.getTabViewGroupId(aParent); + if (!id) + return; + + this.tabViewTreeIsMoving = true; + this.internallyTabMovingCount++; + var w = this.window; + var b = this.mTabBrowser; + var lastCount = this.getAllTabs(b).length - 1; + + this.detachTab(aParent); + b.moveTabTo(aParent, lastCount); + var descendantTabs = this.getDescendantTabs(aParent); + for (let i = 0, maxi = descendantTabs.length; i < maxi; i++) + { + let tab = descendantTabs[i]; + w.TabView.moveTabTo(tab, id); + b.moveTabTo(tab, lastCount); + } + this.internallyTabMovingCount--; + this.tabViewTreeIsMoving = false; + }, + tabViewTreeIsMoving : false, + + getTabViewGroupId : function TSTBrowser_getTabViewGroupId(aTab) + { + var tab = aTab || this.mTabBrowser.selectedTab; + var item = tab._tabViewTabItem; + if (!item) + return null; + + var group = item.parent; + if (!group) + return null; + + return group.id; + }, + + onRestoreTabContentStarted : function TSTBrowser_onRestoreTabContentStarted(aTab) + { + aTab.linkedBrowser.__treestyletab__toBeRestored = true; + }, + + onTabRestoring : function TSTBrowser_onTabRestoring(aEvent) + { + this.restoreTree(); + + var tab = aEvent.originalTarget; + + tab.linkedBrowser.__treestyletab__toBeRestored = false; + this.handleRestoredTab(tab); + + /** + * Updating of the counter which is used to know how many tabs were + * restored in a time. + */ + this.windowService.restoringCount++; + /** + * By nsSessionStore.js, the next "SSTabRestoring" event will be fined + * with "window.setTimeout()" following this "SSTabRestoring" event. + * So, we have to do "setTimeout()" twice, instead of "Deferred.next()". + */ + var self = this; + this.window.setTimeout(function() { + /** + * On this timing, the next "SSTabRestoring" is not fired yet. + * We only register the countdown task for the next event loop. + */ + let key = 'onTabRestoring_'+parseInt(Math.random() * 65000); + (self.deferredTasks[key] = self.windowService.Deferred.next(function() { + /** + * On this timing, the next "SSTabRestoring" was fired. + * Now we can decrement the counter. + */ + self.windowService.restoringCount--; + })).error(self.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + }, 0); + + if (!tab.selected && + this.mTabBrowser.currentURI.spec == 'about:sessionrestore') { + let frame = this.mTabBrowser.contentWindow; + frame = frame.wrappedJSObject || frame; + let tree = frame.document.getElementById('tabList'); + let data = frame.gTreeData; + if (tree && data) { + let item = data[tree.currentIndex]; + this.window.setTimeout(function(aSelf, aTab, aTitle, aParent) { + if (aTab.label== aTitle) + aSelf.attachTabTo(aTab, aParent); + }, 0, this, tab, item.label, this.mTabBrowser.selectedTab); + } + } + }, + + RESTORED_TREE_COLLAPSED_STATE_LAST_STATE : -1, + RESTORED_TREE_COLLAPSED_STATE_COLLAPSED : 0, + RESTORED_TREE_COLLAPSED_STATE_EXPANDED : 1, + RESTORE_STATE_INITIAL : 0, + RESTORE_STATE_READY_TO_RESTORE : 1, + RESTORE_STATE_STRUCTURE_RESTORED : 2, + handleRestoredTab : function TSTBrowser_handleRestoredTab(aTab) + { + if (aTab.__treestyletab__restoreState == this.RESTORE_STATE_READY_TO_RESTORE) { + // this is a hidden tab in the background group, and + // have to be restored by restoreOneTab() on "TabShown" event. + this.deleteTabValue(aTab, this.kCLOSED_SET_ID); + return; + } + + var [id, mayBeDuplicated] = this._restoreTabId(aTab); + var structureRestored = aTab.__treestyletab__restoreState == this.RESTORE_STATE_STRUCTURE_RESTORED; + var children = this.getTabValue(aTab, this.kCHILDREN); + if ( + !structureRestored && + ( + !mayBeDuplicated || + aTab.getAttribute(this.kCHILDREN) != children + ) + ) { + // failsafe + this.detachAllChildren(aTab, { + dontUpdateIndent : true, + dontAnimate : this.windowService.restoringTree + }); + } + + var closeSetId = !structureRestored && this._getCloseSetId(aTab, mayBeDuplicated); + + // remove temporary cache + var currentId = aTab.getAttribute(this.kID); + if (id != currentId && + currentId && + currentId in this.tabsHash && + this.tabsHash[currentId] == aTab) + delete this.tabsHash[currentId]; + + this.setTabValue(aTab, this.kID, id); + this.tabsHash[id] = aTab; + + if (structureRestored) { + this._fixMissingAttributesFromSessionData(aTab); + } + else { + let isSubtreeCollapsed = this._restoreSubtreeCollapsedState(aTab); + + let restoringMultipleTabs = this.windowService.restoringTree; + let options = { + dontExpand : restoringMultipleTabs, + dontUpdateIndent : true, + dontAnimate : restoringMultipleTabs + }; + let childTabs = this._restoreChildTabsRelation(aTab, children, mayBeDuplicated, options); + + this._restoreTabPositionAndIndent(aTab, childTabs, mayBeDuplicated); + + if (closeSetId) + this.restoreClosedSet(closeSetId, aTab); + + if (isSubtreeCollapsed) + this.collapseExpandSubtree(aTab, isSubtreeCollapsed); + } + + if (mayBeDuplicated) { + this.clearRedirectionTableWithDelay(); + this.clearRedirectbTabRelationsWithDelay(aTab); + } + + delete aTab.__treestyletab__restoreState; + }, + + _restoreTabId : function TSTBrowser_restoreTabId(aTab) + { + // kID can be overridden by nsSessionStore. kID_NEW is for failsafe. + var currentId = aTab.getAttribute(this.kID_NEW) || aTab.getAttribute(this.kID); + aTab.removeAttribute(this.kID_NEW); + var restoredId = this.getTabValue(aTab, this.kID); + var mayBeDuplicated = false; + + aTab.setAttribute(this.kID_RESTORING, restoredId); + if (this.isTabDuplicated(aTab)) { + mayBeDuplicated = true; + /** + * If the tab has its ID as the attribute, then we should use it + * instead of redirected ID, because the tab has been possibly + * attached to another tab. + */ + restoredId = currentId || this.redirectId(restoredId); + } + aTab.removeAttribute(this.kID_RESTORING); + + return [restoredId || currentId, mayBeDuplicated]; + }, + + _getCloseSetId : function TSTBrowser_getCloseSetId(aTab, aMayBeDuplicated) + { + var closeSetId = null; + if (!aMayBeDuplicated) { + closeSetId = this.getTabValue(aTab, this.kCLOSED_SET_ID); + /** + * If the tab is not a duplicated but it has a parent, then, + * it is wrongly attacched by tab moving on restoring. + * Restoring the old ID (the next statement) breaks the children + * list of the temporary parent and causes many problems. + * So, to prevent these problems, I detach the tab from the temporary + * parent manually. + * If the ID stored in the session equals to the value of the + * attribute stored in the element itself, then don't reset the + * tab, because the restoring session is got from the tab itself. + * ( like SS.setTabState(tab, SS.getTabState(tab)) ) + */ + if (this.getTabValue(aTab, this.kID) != aTab.getAttribute(this.kID)) + this.resetTab(aTab, false); + } + this.deleteTabValue(aTab, this.kCLOSED_SET_ID); + return closeSetId; + }, + + _fixMissingAttributesFromSessionData : function TSTBrowser_fixMissingAttributesFromSessionData(aTab) + { + /** + * By some reasons (ex. persistTabAttribute()), actual state of + * the tab (attributes) can be lost on SSTabRestoring. + * For failsafe, we must override actual attributes by stored + * values. + */ + var keys = [ + this.kINSERT_BEFORE, + this.kINSERT_AFTER + ]; + for (let i = 0, maxi = keys.length; i < maxi; i++) + { + let key = keys[i]; + let tab = this.getTabValue(aTab, key); + if (this.getTabById(tab)) + this.setTabValue(aTab, key, tab); + } + + let parentId = this.getTabValue(aTab, this.kPARENT); + let parentTab = this.getTabById(parentId); + if (parentTab && parentTab._tPos < aTab._tPos) + this.setTabValue(aTab, this.kPARENT, parentId); + else + this.deleteTabValue(aTab, this.kPARENT); + + let ancestors = [aTab].concat(this.getAncestorTabs(aTab)); + let children = this.getTabValue(aTab, this.kCHILDREN); + children = children.split('|').filter(function(aChild) { + let tab = this.getTabById(aChild); + return tab && ancestors.indexOf(tab) < 0; + }, this); + this.setTabValue(aTab, this.kCHILDREN, children.join('|')); + + let subtreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED); + if (subtreeCollapsed != aTab.getAttribute(this.kSUBTREE_COLLAPSED)) + this.collapseExpandSubtree(aTab, subtreeCollapsed == 'true', true); + }, + + _restoreSubtreeCollapsedState : function TSTBrowser_restoreSubtreeCollapsedState(aTab, aCollapsed) + { + var shouldCollapse = utils.getTreePref('collapseExpandSubtree.sessionRestore'); + + if (aCollapsed === void(0)) + aCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; + + var isSubtreeCollapsed = ( + this.windowService.restoringTree && + ( + shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_LAST_STATE ? + aCollapsed : + shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_COLLAPSED + ) + ); + this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, isSubtreeCollapsed); + return isSubtreeCollapsed; + }, + + _restoreChildTabsRelation : function TSTBrowser_restoreChildTabsRelation(aTab, aChildrenList, aMayBeDuplicated, aOptions) + { + var childTabs = []; + if (!aChildrenList) + return childTabs; + + aTab.removeAttribute(this.kCHILDREN); + + aChildrenList = aChildrenList.split('|'); + if (aMayBeDuplicated) + aChildrenList = aChildrenList.map(function(aChild) { + return this.redirectId(aChild); + }, this); + + for (let i = 0, maxi = aChildrenList.length; i < maxi; i++) + { + let childTab = aChildrenList[i]; + if (childTab && (childTab = this.getTabById(childTab))) { + let options = aOptions; + if (options && typeof options == 'function') + options = options(childTab); + this.attachTabTo(childTab, aTab, options); + childTabs.push(childTab); + } + } + aChildrenList = aChildrenList.join('|'); + if (aTab.getAttribute(this.kCHILDREN) == aChildrenList) + aTab.removeAttribute(this.kCHILDREN_RESTORING); + else + aTab.setAttribute(this.kCHILDREN_RESTORING, aChildrenList); + + return childTabs; + }, + + _restoreTabPositionAndIndent : function TSTBrowser_restoreTabPositionAndIndent(aTab, aChildTabs, aMayBeDuplicated) + { + var restoringMultipleTabs = this.windowService.restoringTree; + var position = this._prepareInsertionPosition(aTab, aMayBeDuplicated); + var parent = position.parent; + if (DEBUG) + dump('handleRestoredTab: found parent = ' + parent+'\n'); + if (parent) { + aTab.removeAttribute(this.kPARENT); + parent = this.getTabById(parent); + if (parent) { + this.attachTabTo(aTab, parent, { + dontExpand : restoringMultipleTabs, + insertBefore : position.next, + dontUpdateIndent : true, + dontAnimate : restoringMultipleTabs + }); + this.updateTabsIndent([aTab], undefined, restoringMultipleTabs); + this.checkTabsIndentOverflow(); + + if (parent.getAttribute(this.kCHILDREN_RESTORING)) + this.correctChildTabsOrderWithDelay(parent); + } + else { + this.deleteTabValue(aTab, this.kPARENT); + } + } + else { + if (aChildTabs.length) { + this.updateTabsIndent(aChildTabs, undefined, restoringMultipleTabs); + this.checkTabsIndentOverflow(); + } + this._restoreTabPosition(aTab, position.next); + } + }, + + _prepareInsertionPosition : function TSTBrowser_prepareInsertionPosition(aTab, aMayBeDuplicated) + { + var next = this.getTabValue(aTab, this.kINSERT_BEFORE); + if (next && aMayBeDuplicated) + next = this.redirectId(next); + next = this.getTabById(next); + + if (!next) { + let prev = this.getTabValue(aTab, this.kINSERT_AFTER); + if (prev && aMayBeDuplicated) + prev = this.redirectId(prev); + prev = this.getTabById(prev); + next = this.getNextSiblingTab(prev); + } + + var ancestors = (this.getTabValue(aTab, this.kANCESTOR) || this.getTabValue(aTab, this.kPARENT)).split('|'); + if (DEBUG) + dump('handleRestoredTab: ancestors = ' + ancestors+'\n'); + var parent = null; + for (let i in ancestors) + { + if (aMayBeDuplicated) + ancestors[i] = this.redirectId(ancestors[i]); + parent = this.getTabById(ancestors[i]); + if (parent) { + parent = ancestors[i]; + break; + } + } + this.deleteTabValue(aTab, this.kANCESTOR); + + /** + * If the tab is a duplicated and the tab has already been + * attached, then reuse current status based on attributes. + * (Note, if the tab is not a duplicated tab, all attributes + * have been cleared.) + */ + if (!parent) { + parent = aTab.getAttribute(this.kPARENT); + if (DEBUG) + dump('handleRestoredTab: parent = ' + parent+'\n'); + if (parent && !next) + next = this.getNextSiblingTab(aTab); + } + + return { + parent : parent, + next : next + }; + }, + + _restoreTabPosition : function TSTBrowser_restoreTabPosition(aTab, aNextTab) + { + if (!aNextTab) + aNextTab = this.getNextTab(aTab); + var parentOfNext = this.getParentTab(aNextTab); + var newPos = -1; + if (parentOfNext) { + let descendants = this.getDescendantTabs(parentOfNext); + if (descendants.length) + newPos = descendants[descendants.length-1]._tPos; + } + else if (aNextTab) { + newPos = aNextTab._tPos; + if (newPos > aTab._tPos) + newPos--; + } + if (newPos > -1) + this.mTabBrowser.moveTabTo(aTab, newPos); + }, + + correctChildTabsOrderWithDelay : function TSTBrowser_correctChildTabsOrderWithDelay(aTab) + { + if (aTab.correctChildTabsOrderWithDelayTimer) + this.window.clearTimeout(aTab.correctChildTabsOrderWithDelayTimer); + + aTab.correctChildTabsOrderWithDelayTimer = this.window.setTimeout(function(aSelf) { + aSelf.correctChildTabsOrder(aTab); + }, 10, this); + }, + + correctChildTabsOrder : function TSTBrowser_correctChildTabsOrder(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING); + if (!restoringChildren) return; + + var children = aTab.getAttribute(this.kCHILDREN); + if (restoringChildren != children) { + var restoringChildrenIDs = restoringChildren.split('|').reverse(); + for (let i = 0, maxi = restoringChildrenIDs.length; i < maxi; i++) + { + let child = this.getTabById(restoringChildrenIDs[i]); + if (!child) + continue; + + let nextTab = i > 0 ? + this.getTabById(restoringChildrenIDs[i-1]) : + this.getNextSiblingTab(aTab) ; + if (nextTab == this.getNextSiblingTab(child)) + continue; + + let newPos = -1; + if (nextTab) { + newPos = nextTab._tPos; + if (newPos > child._tPos) + newPos--; + } + if (newPos > -1) + this.moveTabSubtreeTo(child, newPos); + } + children = aTab.getAttribute(this.kCHILDREN); + } + + if (restoringChildren == children) + aTab.removeAttribute(this.kCHILDREN_RESTORING); + + aTab.correctChildTabsOrderWithDelayTimer = null; + }, + + redirectId : function TSTBrowser_redirectId(aId) + { + if (!(aId in this._redirectionTable)) + this._redirectionTable[aId] = this.makeNewId(); + return this._redirectionTable[aId]; + }, + _redirectionTable : {}, + + clearRedirectionTableWithDelay : function TSTBrowser_clearRedirectionTableWithDelay() + { + if (this._clearRedirectionTableTimer) { + this.window.clearTimeout(this._clearRedirectionTableTimer); + this._clearRedirectionTableTimer = null; + } + this._clearRedirectionTableTimer = this.window.setTimeout(function(aSelf) { + aSelf._redirectionTable = {}; + }, 1000, this); + }, + _clearRedirectionTableTimer : null, + + clearRedirectbTabRelationsWithDelay : function TSTBrowser_clearRedirectbTabRelationsWithDelay(aTab) + { + if (aTab._clearRedirectbTabRelationsTimer) { + this.window.clearTimeout(aTab._clearRedirectbTabRelationsTimer); + aTab._clearRedirectbTabRelationsTimer = null; + } + aTab._clearRedirectbTabRelationsTimer = this.window.setTimeout(function(aSelf) { + aSelf.clearRedirectbTabRelations(aTab); + delete aTab._clearRedirectbTabRelationsTimer; + }, 1500, this); + }, + clearRedirectbTabRelations : function TSTBrowser_clearRedirectbTabRelations(aTab) + { + if (!aTab || !aTab.parentNode) + return; + + var redirectingIds = Object.keys(this._redirectionTable).map(function(aId) { + return this._redirectionTable[aId]; + }, this); + var existingIds = this.getAllTabs(this.mTabBrowser).map(function(aTab) { + return this.getTabValue(aTab, this.kID); + }, this); + var validIds = redirectingIds.concat(existingIds); + validIds = validIds.filter(function(aId) { + return !!aId; + }); + + var ancestors = this.getTabValue(aTab, this.kANCESTORS); + if (ancestors) { + ancestors = ancestors.split('|'); + let actualAncestors = this.getAncestorTabs(aTab).map(function(aTab) { + return aTab.getAttribute(this.kID); + }, this); + ancestors = ancestors.filter(function(aAncestor) { + if (actualAncestors.indexOf(aAncestor) < 0) + return false; + else + return validIds.indexOf(aAncestor) > -1; + }, this); + if (ancestors.length) + this.setTabValue(aTab, this.kANCESTORS, ancestors.join('|')); + else + this.deleteTabValue(aTab, this.kANCESTORS); + } + + var children = this.getTabValue(aTab, this.kCHILDREN); + if (children) { + children = children.split('|'); + children = children.filter(function(aChild) { + if (this.getParentTab(this.getTabById(aChild)) != aTab) + return false; + else + return validIds.indexOf(aChild) > -1; + }, this); + if (children.length) + this.setTabValue(aTab, this.kCHILDREN, children.join('|')); + else + this.deleteTabValue(aTab, this.kCHILDREN); + } + + var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING); + if (restoringChildren) { + restoringChildren = restoringChildren.split('|'); + restoringChildren = restoringChildren.filter(function(aChild) { + return validIds.indexOf(aChild) > -1; + }, this); + if (restoringChildren.length) + aTab.setAttribute(this.kCHILDREN_RESTORING, restoringChildren.join('|')); + else + aTab.removeAttribute(this.kCHILDREN_RESTORING); + } + }, + + restoreClosedSet : function TSTBrowser_restoreClosedSet(aId, aRestoredTab) + { + var behavior = this.undoCloseTabSetBehavior; + if ( + aRestoredTab.__treestyletab__restoredByUndoCloseTab || + !this.browser.__treestyletab__readyToUndoCloseTab || + this.useTMPSessionAPI || + this._restoringClosedSet || + !(behavior & this.kUNDO_CLOSE_SET || behavior & this.kUNDO_ASK) + ) + return; + + var indexes = []; + var items = utils.evalInSandbox('('+this.SessionStore.getClosedTabData(this.window)+')'); + for (let i = 0, maxi = items.length; i < maxi; i++) + { + let item = items[i]; + if (item.state.extData && + item.state.extData[this.kCLOSED_SET_ID] && + item.state.extData[this.kCLOSED_SET_ID] == aId) + indexes.push(i); + } + + var count = parseInt(aId.split('::')[1]); + + if ( + !indexes.length || + ( + indexes.length+1 < count && + behavior & this.kUNDO_CLOSE_FULL_SET + ) + ) + return; + + if (behavior & this.kUNDO_ASK) { + let self = this; + aRestoredTab.addEventListener('SSTabRestoring', function onSSTabRestoring(aEvent) { + aRestoredTab.removeEventListener(aEvent.type, onSSTabRestoring, false); + self.askUndoCloseTabSetBehavior(aRestoredTab, indexes.length) + .next(function(aBehavior) { + if (aBehavior & self.kUNDO_CLOSE_SET) + self.doRestoreClosedSet(aRestoredTab, indexes); + }); + }, false); + } + else if (behavior & this.kUNDO_CLOSE_SET) { + this.doRestoreClosedSet(aRestoredTab, indexes); + } + }, + + doRestoreClosedSet : function TSTBrowser_doRestoreClosedSet(aRestoredTab, aIndexes) + { + if (!this.window.PlacesUIUtils._confirmOpenInTabs(aIndexes.length)) + return; + + this._restoringClosedSet = true; + this.stopRendering(); + + this.windowService.restoringTree = true; + + var offset = 0; + for (let i = 0, maxi = aIndexes.length; i < maxi; i++) + { + this.window.undoCloseTab(aIndexes[i] - (offset++)); + } + + this.window.setTimeout(function(aSelf, aNextFocused) { + aSelf.windowService.restoringTree = false; + aSelf.mTabBrowser.selectedTab = aNextFocused; + }, 0, this, aRestoredTab || aSelf.mTabBrowser.selectedTab); + + this.startRendering(); + this._restoringClosedSet = false; + }, + _restoringClosedSet : false, + + onTabRestored : function TSTBrowser_onTabRestored(aEvent) + { + delete aEvent.originalTarget.__treestyletab__restoredByUndoCloseTab; + }, + + onTabPinned : function TSTBrowser_onTabPinned(aTab) + { + var parentTab = this.getParentTab(aTab); + + this.collapseExpandSubtree(aTab, false); + + /** + * Children of the newly pinned tab are possibly + * moved to the top of the tab bar, by TabMove event + * from the newly pinned tab. So, we have to + * reposition unexpectedly moved children. + */ + if (!parentTab) { + /** + * Universal but dangerous logic. "__treestyletab__previousPosition" + * can be broken by multiple movings. + */ + let b = this.browser; + this.internallyTabMovingCount++; + let children = this.getDescendantTabs(aTab).reverse(); + for (let i = 0, maxi = children.length; i < maxi; i++) + { + let childTab = children[i]; + if (childTab.__treestyletab__previousPosition > childTab._tPos) + b.moveTabTo(childTab, childTab.__treestyletab__previousPosition); + } + this.internallyTabMovingCount--; + } + else { + /** + * Safer logic. This cannot be available for "root" tabs because + * their children (already moved) have no way to know the anchor + * position (the next sibling of the pinned tab itself). + */ + let b = this.browser; + this.internallyTabMovingCount++; + let children = this.getChildTabs(aTab).reverse(); + for (let i = 0, maxi = children.length; i < maxi; i++) + { + let childTab = children[i]; + if (childTab._tPos < parentTab._tPos) + b.moveTabTo(childTab, parentTab._tPos); + } + this.internallyTabMovingCount--; + } + + this.detachAllChildren(aTab, { + behavior : this.getCloseParentBehaviorForTab( + aTab, + this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD + ) + }); + this.detachTab(aTab); + + this.collapseExpandTab(aTab, false); + if (this.isVertical) + this.positionPinnedTabsWithDelay(); + }, + + onTabUnpinned : function TSTBrowser_onTabUnpinned(aTab) + { + var style = aTab.style; + style.marginLeft = style.marginRight = style.marginTop = ''; + + this.updateInvertedTabContentsOrder(aTab); + if (this.isVertical) + this.positionPinnedTabsWithDelay(); + }, + + onTabSelect : function TSTBrowser_onTabSelect(aEvent) + { + var b = this.mTabBrowser; + var tab = b.selectedTab + + this.cancelDelayedExpandOnTabSelect(); + + if ( + /** + * .previewTab() focuses to the tab internally, + * so we should ignore this event if it is fired from previewTab(). + */ + b._previewMode || + /** + * Ignore selected tabs which is being closed. For example, + * when a collapsed tree is closed, Firefox unexpectedly gives + * focus to a collapsed child in the tree. + */ + (b._removingTabs && b._removingTabs.indexOf(tab) > -1) + ) + return; + + var shouldCollapseExpandNow = utils.getTreePref('autoCollapseExpandSubtreeOnSelect'); + var newActiveTabOptions = { + canCollapseTree : shouldCollapseExpandNow, + canExpandTree : shouldCollapseExpandNow + }; + if (this.isCollapsed(tab)) { + if (utils.getTreePref('autoExpandSubtreeOnCollapsedChildFocused')) { + this.getAncestorTabs(tab).forEach(function(aAncestor) { + this.collapseExpandSubtree(aAncestor, false); + }, this); + this.handleNewActiveTab(tab, newActiveTabOptions); + } + else { + b.selectedTab = this.getRootTab(tab); + } + } + else if ( + ( + /** + * Focus movings by arrow keys should not be handled on TabSelect, + * because they are already handled by handleAdvanceSelectedTab(). + */ + this.windowService.arrowKeyEventOnTab && + this.windowService.arrowKeyEventOnTab.advanceFocus + ) || + ( + /** + * Focus movings by closing of the old current tab should be handled + * only when it is activated by user preference expressly. + */ + this._focusChangedByCurrentTabRemove && + !utils.getTreePref('autoCollapseExpandSubtreeOnSelect.onCurrentTabRemove') + ) + ) { + // do nothing! + } + else if (this.hasChildTabs(tab) && this.isSubtreeCollapsed(tab)) { + if ( + this._focusChangedByShortcut && + this.windowService.accelKeyPressed + ) { + if (utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut')) { + newActiveTabOptions.canExpandTree = true; + newActiveTabOptions.canCollapseTree = ( + newActiveTabOptions.canCollapseTree && + utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.collapseOthers') + ); + let delay = utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay'); + if (delay > 0) { + this._autoExpandOnTabSelectTimer = this.window.setTimeout(function(aSelf) { + if (tab && tab.parentNode) + aSelf.handleNewActiveTab(tab, newActiveTabOptions); + }, delay, this); + } + else { + this.handleNewActiveTab(tab, newActiveTabOptions); + } + } + else if (newActiveTabOptions.canExpandTree) { + this.windowService.expandTreeAfterKeyReleased(tab); + } + } + else { + this.handleNewActiveTab(tab, newActiveTabOptions); + } + } + + this._focusChangedByCurrentTabRemove = false; + this._focusChangedByShortcut = false; + + this.updateInvertedTabContentsOrder(); + + if (!this.isTabInViewport(tab)) { + this.scrollToTab(tab); + aEvent.stopPropagation(); + } + }, + cancelDelayedExpandOnTabSelect : function TSTBrowser_cancelDelayedExpandOnTabSelect() { + if (this._autoExpandOnTabSelectTimer) { + this.window.clearTimeout(this._autoExpandOnTabSelectTimer); + this._autoExpandOnTabSelectTimer = null; + } + }, + handleNewActiveTab : function TSTBrowser_handleNewActiveTab(aTab, aOptions) + { + if (this.doingCollapseExpand || !aTab || !aTab.parentNode) + return; + + aOptions = aOptions || {}; + + if (this._handleNewActiveTabTimer) + this.window.clearTimeout(this._handleNewActiveTabTimer); + + /** + * First, we wait until all event listeners for the TabSelect + * event were processed. + */ + this._handleNewActiveTabTimer = this.window.setTimeout(function(aSelf) { + aSelf.window.clearTimeout(aSelf._handleNewActiveTabTimer); + aSelf._handleNewActiveTabTimer = null; + + if (aOptions.canExpandTree) { + if (aOptions.canCollapseTree && + utils.getTreePref('autoExpand.intelligently')) + aSelf.collapseExpandTreesIntelligentlyFor(aTab); + else + aSelf.collapseExpandSubtree(aTab, false); + } + }, 0, this); + }, + _handleNewActiveTabTimer : null, + + handleAdvanceSelectedTab : function TSTBrowser_handleAdvanceSelectedTab(aDir, aWrap) + { + this._focusChangedByShortcut = this.windowService.accelKeyPressed; + + if (!this.canCollapseSubtree(this.mTabBrowser.selectedTab) || + utils.getTreePref('focusMode') != this.kFOCUS_VISIBLE) + return false; + + if (this.processArrowKeyOnFocusAdvanced()) + return true; + + return this.advanceSelectedTab(aDir, aWrap); + }, + + processArrowKeyOnFocusAdvanced : function TSTBrowser_processArrowKeyOnFocusAdvanced() + { + var event = this.windowService.arrowKeyEventOnTab; + if (!event) + return false; + + if ( + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey || + (this.isVertical ? (event.up || event.down) : (event.left || event.right)) + ) { + event.advanceFocus = true; + return false; + } + + var collapse, expand; + switch (this.position) + { + case 'top': + collapse = event.up; + expand = event.down; + break; + + case 'bottom': + collapse = event.down; + expand = event.up; + break; + + case 'left': + collapse = event.left; + expand = event.right; + break; + + case 'right': + if (utils.getTreePref('tabbar.invertTab')) { + collapse = event.right; + expand = event.left; + } + else { + collapse = event.left; + expand = event.right; + } + break; + } + + var tab = this.mTabBrowser.selectedTab; + + var collapsed = this.isSubtreeCollapsed(tab); + if (this.hasChildTabs(tab) && (collapsed ? expand : collapse )) { + event.collapse = collapse; + event.expand = expand; + this.collapseExpandSubtree(tab, !collapsed); + return true; + } + + var nextSelected; + if (expand) + nextSelected = this.getFirstChildTab(tab); + else if (collapse) + nextSelected = this.getParentTab(tab); + + if (nextSelected) { + event.advanceFocus = true; + this.mTabBrowser.selectedTab = nextSelected; + return true; + } + + return true; + }, + + advanceSelectedTab : function TSTBrowser_advanceSelectedTab(aDir, aWrap) + { + var tab = this.mTabBrowser.selectedTab; + var tabbar = this.mTabBrowser.mTabContainer; + + var nextTab = (aDir < 0) ? this.getPreviousVisibleTab(tab) : this.getNextVisibleTab(tab) ; + if (!nextTab && aWrap) { + let tabs = tabbar.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"])'); + nextTab = tabs[aDir < 0 ? tabs.length-1 : 0 ]; + } + if (nextTab && nextTab != tab) + tabbar._selectNewTab(nextTab, aDir, aWrap); + + return true; + }, + + onTabClick : function TSTBrowser_onTabClick(aEvent, aTab) + { + aTab = aTab || this.getTabFromEvent(aEvent); + + if (aEvent.button == 1) { + if (!this.warnAboutClosingTabSubtreeOf(aTab)) { + aEvent.preventDefault(); + aEvent.stopPropagation(); + } + return; + } + + if (aEvent.button != 0) + return; + + if (this.isEventFiredOnTwisty(aEvent)) { + if (this.hasChildTabs(aTab) && this.canCollapseSubtree(aTab)) { + this.manualCollapseExpandSubtree(aTab, aTab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); + aEvent.preventDefault(); + aEvent.stopPropagation(); + } + return; + } + + if (this.isEventFiredOnClosebox(aEvent)) { + if (!this.warnAboutClosingTabSubtreeOf(aTab)) { + aEvent.preventDefault(); + aEvent.stopPropagation(); + } + return; + } + }, + + onClick : function TSTBrowser_onClick(aEvent) + { + if ( + aEvent.target.ownerDocument != this.document || + aEvent.button != 0 || + this.isAccelKeyPressed(aEvent) + ) + return; + + var tab = this.getTabFromEvent(aEvent); + if (tab) { + this.onTabClick(aEvent, tab); + } + else { + // click on indented space on the tab bar + tab = this.getTabFromTabbarEvent(aEvent); + if (tab) + this.mTabBrowser.selectedTab = tab; + } + }, + + onDblClick : function TSTBrowser_onDblClick(aEvent) + { + let tab = this.getTabFromEvent(aEvent); + if (tab && + this.hasChildTabs(tab) && + utils.getTreePref('collapseExpandSubtree.dblclick')) { + this.manualCollapseExpandSubtree(tab, tab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); + aEvent.preventDefault(); + aEvent.stopPropagation(); + } + }, + + onMozMouseHittest : function TSTBrowser_onMozMouseHittest(aEvent) + { + // block default behaviors of the tab bar (dragging => window move, etc.) + if ( + !this.getTabFromEvent(aEvent) && + !this.isEventFiredOnClickable(aEvent) && + ( + this.position != 'top' || + aEvent.shiftKey || + this.tabbarDNDObserver.canDragTabbar(aEvent) + ) + ) + aEvent.stopPropagation(); + }, + + onMouseDown : function TSTBrowser_onMouseDown(aEvent) + { + if (this.isEventFiredOnScrollbar(aEvent)) + this.cancelPerformingAutoScroll(); + + if ( + aEvent.button == 0 && + this.isEventFiredOnTwisty(aEvent) + ) { + // prevent to select the tab for clicking on twisty + aEvent.stopPropagation(); + // prevent to focus to the tab element itself + aEvent.preventDefault(); + } + else { + this.onMozMouseHittest(aEvent); + } + }, + + onDOMMouseScroll : function TSTBrowser_onDOMMouseScroll(aEvent) + { + this.cancelPerformingAutoScroll(); + }, + + onScroll : function TSTBrowser_onScroll(aEvent) + { + // restore scroll position when a tab is closed. + this.restoreLastScrollPosition(); + }, + + onTabbarOverflow : function TSTBrowser_onTabbarOverflow(aEvent) + { + var tabs = this.mTabBrowser.mTabContainer; + var horizontal = tabs.orient == 'horizontal'; + if (horizontal) + return; + aEvent.stopPropagation(); + this.positionPinnedTabsWithDelay(); + if (aEvent.detail == 1) { + /** + * By horizontal overflow/underflow, Firefox can wrongly + * removes "overflow" attribute for vertical tab bar. + * We have to override the result. + */ + this.updateTabbarOverflow(); + } + else { + if (aEvent.type == 'overflow') { + tabs.setAttribute('overflow', 'true'); + this.scrollBoxObject.ensureElementIsVisible(tabs.selectedItem); + } + else { + tabs.removeAttribute('overflow'); + } + } + }, + + onResize : function TSTBrowser_onResize(aEvent) + { + if ( + !aEvent.originalTarget || + !(aEvent.originalTarget instanceof Ci.nsIDOMWindow) + ) + return; + + var resizedTopFrame = aEvent.originalTarget.top; + var isContentResize = resizedTopFrame == this.mTabBrowser.contentWindow; + var isChromeResize = resizedTopFrame == this.window; + + if (isChromeResize && aEvent.originalTarget != resizedTopFrame) { + // ignore resizing of sub frames in "position:fixed" box + let target = aEvent.target; + try { + let node = target.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + let root = node.ownerDocument.documentElement; + while (node && node != root) { + if (node.boxObject && !node.boxObject.parentBox) { + isChromeResize = false; + break; + } + node = node.parentNode; + } + } + catch(e) { + } + } + + // Ignore events when a background tab raises to the foreground. + if (isContentResize && this._lastTabbarPlaceholderSize) { + let newSize = this.getTabbarPlaceholderSize(); + isContentResize = + newSize.width != this._lastTabbarPlaceholderSize.width || + newSize.height != this._lastTabbarPlaceholderSize.height; + } + + if (isContentResize || isChromeResize) { + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_WINDOW_RESIZE); + this.updateInvertedTabContentsOrder(true); + this.mTabBrowser.mTabContainer.adjustTabstrip(); + } + }, + + onPopupShowing : function TSTBrowser_onPopupShowing(aEvent) + { + if (aEvent.target == aEvent.currentTarget) + this.initTabContextMenu(aEvent); + }, + + initTabContextMenu : function TSTBrowser_initTabContextMenu(aEvent) + { + var b = this.mTabBrowser; + var sep, items = {}; + + var ids = [ + this.kMENUITEM_RELOADSUBTREE, + this.kMENUITEM_RELOADCHILDREN, + this.kMENUITEM_REMOVESUBTREE, + this.kMENUITEM_REMOVECHILDREN, + this.kMENUITEM_REMOVEALLTABSBUT, + this.kMENUITEM_COLLAPSE, + this.kMENUITEM_EXPAND, + this.kMENUITEM_AUTOHIDE, + this.kMENUITEM_FIXED, + this.kMENUITEM_BOOKMARKSUBTREE + ]; + for (let i = 0, maxi = ids.length; i < maxi; i++) + { + let id = ids[i]; + let item = aEvent.currentTarget.querySelector('*[id^="'+id+'"]'); + if (!item) + continue; + items[id] = item; + if (utils.getTreePref('show.'+id)) + item.removeAttribute('hidden'); + else + item.setAttribute('hidden', true); + switch (id) + { + case this.kMENUITEM_RELOADSUBTREE: + case this.kMENUITEM_RELOADCHILDREN: + case this.kMENUITEM_REMOVESUBTREE: + case this.kMENUITEM_REMOVECHILDREN: + case this.kMENUITEM_REMOVEALLTABSBUT: + case this.kMENUITEM_COLLAPSE: + case this.kMENUITEM_EXPAND: + case this.kMENUITEM_BOOKMARKSUBTREE: + this.showHideSubtreeMenuItem(item, [b.mContextTab]); + continue; + default: + continue; + } + } + + // collapse/expand all + sep = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_COLLAPSEEXPAND_SEPARATOR+'"]'); + let collapseItem = items[this.kMENUITEM_COLLAPSE]; + let expandItem = items[this.kMENUITEM_EXPAND]; + if (this.canCollapseSubtree(b) && + b.mTabContainer.querySelector('tab['+this.kCHILDREN+']')) { + if (collapseItem) { + if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']:not(['+this.kSUBTREE_COLLAPSED+'="true"])')) + collapseItem.removeAttribute('disabled'); + else + collapseItem.setAttribute('disabled', true); + } + + if (expandItem) { + if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']['+this.kSUBTREE_COLLAPSED+'="true"]')) + expandItem.removeAttribute('disabled'); + else + expandItem.setAttribute('disabled', true); + } + } + else { + if (collapseItem) + collapseItem.setAttribute('hidden', true); + if (expandItem) + expandItem.setAttribute('hidden', true); + } + if (sep) { + if ( + (!collapseItem || collapseItem.getAttribute('hidden') == 'true') && + (!expandItem || expandItem.getAttribute('hidden') == 'true') + ) { + sep.setAttribute('hidden', true); + } + else { + sep.removeAttribute('hidden'); + } + } + + // close all tabs but this tree + let removeAllTabsBut = items[this.kMENUITEM_REMOVEALLTABSBUT]; + if (removeAllTabsBut) { + let rootTabs = this.visibleRootTabs; + if (rootTabs.length == 1 && rootTabs[0] == b.mContextTab) + removeAllTabsBut.setAttribute('disabled', true); + else + removeAllTabsBut.removeAttribute('disabled'); + } + + // auto hide + let autohide = items[this.kMENUITEM_AUTOHIDE]; + if (autohide) + this.autoHide.updateMenuItem(autohide); + + // fix + let fixedPref; + let fixedLabel; + if (this.isVertical) { + fixedPref = b.getAttribute(this.kFIXED+'-vertical') == 'true'; + fixedLabel = 'label-vertical'; + } + else { + fixedPref = b.getAttribute(this.kFIXED+'-horizontal') == 'true'; + fixedLabel = 'label-horizontal'; + } + let fixed = items[this.kMENUITEM_FIXED]; + if (fixed) { + fixed.setAttribute('label', fixed.getAttribute(fixedLabel)); + if (fixedPref) + fixed.setAttribute('checked', true); + else + fixed.removeAttribute('checked'); + } + + sep = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_AUTOHIDE_SEPARATOR+'"]'); + if (sep) { + if ( + (autohide && autohide.getAttribute('hidden') != 'true') || + (fixed && fixed.getAttribute('hidden') != 'true') + ) { + sep.removeAttribute('hidden'); + } + else { + sep.setAttribute('hidden', true); + } + } + + let closeTabsToEnd = aEvent.currentTarget.querySelector('*[id^="'+this.kMENUITEM_CLOSE_TABS_TO_END+'"]'); + if (closeTabsToEnd) { // https://bugzilla.mozilla.org/show_bug.cgi?id=866880 + let label, accesskey; + if (this.isVertical) { + label = utils.treeBundle.getString('closeTabsToTheEnd_vertical_label'); + accesskey = utils.treeBundle.getString('closeTabsToTheEnd_vertical_accesskey'); + } + else { + label = this._closeTabsToEnd_horizontalLabel; + accesskey = this._closeTabsToEnd_horizontalAccesskey; + } + closeTabsToEnd.setAttribute('label', label); + closeTabsToEnd.setAttribute('accesskey', accesskey); + } + }, + + onTabsOnTopSyncCommand : function TSTBrowser_onTabsOnTopSyncCommand(aEnabled) + { + if ( + this.windowService.tabsOnTopChangingByUI || + !aEnabled || + this.position != 'top' || + this.fixed || + this.windowService.isPopupWindow + ) + return; + this.windowService.tabsOnTopChangingByUI = true; + var self = this; + if (this.deferredTasks['onTabsOnTopSyncCommand']) + this.deferredTasks['onTabsOnTopSyncCommand'].cancel(); + (this.deferredTasks['onTabsOnTopSyncCommand'] = this.Deferred + .next(function() { + self.windowService.toggleFixed(self.mTabBrowser); + })) + .next(function() { + if (self.window.TabsOnTop.enabled != aEnabled) + self.window.TabsOnTop.enabled = aEnabled; + }) + .error(this.defaultDeferredErrorHandler) + .next(function() { + self.windowService.tabsOnTopChangingByUI = false; + delete self.deferredTasks['onTabsOnTopSyncCommand']; + }); + }, + + onBeforeFullScreenToggle : function TSTBrowser_onBeforeFullScreenToggle() + { + if (this.position != 'top') { + var isEnteringFullScreenMode = !this.window.fullScreen; + // entering to the DOM-fullscreen (ex. YouTube Player) + if (this.document.mozFullScreen && isEnteringFullScreenMode) { + this.setTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED, true); + } + else { + if (this.document.documentElement.getAttribute(this.kDOM_FULLSCREEN_ACTIVATED) != 'true') { + if (isEnteringFullScreenMode) + this.autoHide.startForFullScreen(); + else + this.autoHide.endForFullScreen(); + } + this.removeTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED); + } + } + }, + + onTreeStyleTabPrintPreviewEntered : function TSTBrowser_onTreeStyleTabPrintPreviewEntered(aEvent) + { + this.setTabbrowserAttribute(this.kPRINT_PREVIEW, true); + }, + + onTreeStyleTabPrintPreviewExited : function TSTBrowser_onTreeStyleTabPrintPreviewExited(aEvent) + { + this.removeTabbrowserAttribute(this.kPRINT_PREVIEW); + }, + +/* commands */ + +/* reset */ + + resetTab : function TSTBrowser_resetTab(aTab, aDetachAllChildren) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + if (aDetachAllChildren) + this.detachAllChildren(aTab, { + dontUpdateIndent : true, + dontAnimate : true + }); + + this.detachTab(aTab, { + dontUpdateIndent : true, + dontAnimate : true + }); + + this.resetTabState(aTab); + this.updateTabsIndent([aTab], undefined, true); + }, + + resetTabState : function TSTBrowser_resetTabState(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + aTab.removeAttribute(this.kID); + aTab.removeAttribute(this.kID_RESTORING); + aTab.removeAttribute(this.kPARENT); + aTab.removeAttribute(this.kCHILDREN); + aTab.removeAttribute(this.kCHILDREN_RESTORING); + aTab.removeAttribute(this.kSUBTREE_COLLAPSED); + aTab.removeAttribute(this.kSUBTREE_EXPANDED_MANUALLY); + aTab.removeAttribute(this.kCOLLAPSED); + aTab.removeAttribute(this.kNEST); + this.updateTabCollapsed(aTab, false, true); + }, + + resetAllTabs : function TSTBrowser_resetAllTabs(aDetachAllChildren) + { + var tabs = this.getAllTabs(this.mTabBrowser); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.resetTab(tabs[i], aDetachAllChildren); + } + }, + + resetTabbarSize : function TSTBrowser_resetTabbarSize() + { + if (this.isVertical) { + utils.setTreePref('tabbar.shrunkenWidth', utils.getTreePref('tabbar.shrunkenWidth.default')); + utils.setTreePref('tabbar.width', utils.getTreePref('tabbar.width.default')); + } + else { + utils.setTreePref('tabbar.height', utils.getTreePref('tabbar.height.default')); + let tabContainerBox = this.getTabContainerBox(this.mTabBrowser); + tabContainerBox.removeAttribute('height'); + this._tabStripPlaceHolder.height = tabContainerBox.boxObject.height; + } + this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET); + }, + + get treeViewEnabled() /* PUBLIC API */ + { + return this._treeViewEnabled; + }, + set treeViewEnabled(aValue) + { + var newValue = !!aValue; + if (newValue == this._treeViewEnabled) + return aValue; + + this._treeViewEnabled = newValue; + if (this._treeViewEnabled) { + if (this._lastAllowSubtreeCollapseExpand) + this.allowSubtreeCollapseExpand = true; + delete this._lastAllowSubtreeCollapseExpand; + + let tabs = this.getAllTabs(this.browser); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + if (tab._TSTLastSubtreeCollapsed) + this.collapseExpandSubtree(tab, true, true); + if (tab._TSTLastSubtreeExpandedManually) + this.setTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY, true); + delete tab._TSTLastSubtreeCollapsed; + delete tab._TSTLastSubtreeExpandedManually; + this.updateTabIndent(tab, 0, true); + } + this.updateTabsIndent(this.rootTabs, undefined, true); + } + else { + let tabs = this.getAllTabs(this.browser); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + this.updateTabIndent(tab, 0, true); + tab._TSTLastSubtreeCollapsed = this.isSubtreeCollapsed(tab); + tab._TSTLastSubtreeExpandedManually = this.getTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; + this.collapseExpandSubtree(tab, false, true); + } + + this._lastAllowSubtreeCollapseExpand = this.allowSubtreeCollapseExpand; + this.allowSubtreeCollapseExpand = false; + } + return aValue; + }, +// _treeViewEnabled : true, + +/* attach/detach */ + + attachTabTo : function TSTBrowser_attachTabTo(aChild, aParent, aInfo) /* PUBLIC API */ + { + if (!aChild.parentNode || (aParent && !aParent.parentNode)) // do nothing for closed tab! + return; + + aInfo = aInfo || {}; + var newAncestors = []; + + if (aParent) { + newAncestors = [aParent].concat(this.getAncestorTabs(aParent)); + if (this.maxTreeLevelPhisical && this.maxTreeLevel > -1) { + let level = parseInt(aParent.getAttribute(this.kNEST) || 0) + 1; + newAncestors.some(function(aAncestor) { + if (level <= this.maxTreeLevel) + return true; + level--; + return false; + }, this); + } + } + + var currentParent; + if ( + !aChild || + !aParent || + aChild == aParent || + (currentParent = this.getParentTab(aChild)) == aParent || + aChild.getAttribute('pinned') == 'true' || + aParent.getAttribute('pinned') == 'true' + ) { + this.fireAttachedEvent(aChild, aParent); + return; + } + + // avoid recursive tree + var ancestors = [aParent].concat(this.getAncestorTabs(aChild)); + if (ancestors.indexOf(aChild) > -1) + return; + + currentParent = ancestors[ancestors.length-1]; + var shouldInheritIndent = ( + !currentParent || + (currentParent.getAttribute(this.kNEST) == aParent.getAttribute(this.kNEST)) + ); + + this.ensureTabInitialized(aChild); + this.ensureTabInitialized(aParent); + + if (!aInfo) + aInfo = {}; + + var id = aChild.getAttribute(this.kID); + + this.detachTab(aChild, { + dontUpdateIndent : true + }); + + var children = aParent.getAttribute(this.kCHILDREN) + .split('|').filter(function(aId) { + return this.getTabById(aId); + }, this); + + var newIndex; + + var oldIndex = children.indexOf(id); + if (oldIndex > -1) + children.splice(oldIndex, 1); + + var insertBefore = aInfo.insertBefore || + (aInfo.dontMove ? this.getNextTab(aChild) : null ); + var beforeTab = insertBefore ? insertBefore.getAttribute(this.kID) : null ; + var beforeIndex; + if (beforeTab && (beforeIndex = children.indexOf(beforeTab)) > -1) { + children.splice(beforeIndex, 0, id); + newIndex = insertBefore._tPos; + } + else { + children.push(id); + if (aInfo.dontMove && children.length > 1) { + children = children + .map(this.getTabById, this) + .sort(this.sortTabsByOrder) + .map(function(aTab) { + return aTab.getAttribute(this.kID); + }, this); + } + let refTab = aParent; + let descendant = this.getDescendantTabs(aParent); + if (descendant.length) { + let lastDescendant = descendant[descendant.length-1]; + /** + * The last descendant tab can be temporarilly moved + * upper than the root parent tab, in some cases. + * (the parent tab is pinned, etc.) + */ + if (!refTab || lastDescendant._tPos > refTab._tPos) + refTab = lastDescendant; + } + newIndex = refTab._tPos+1; + } + + this.setTabValue(aParent, this.kCHILDREN, children.join('|')); + this.setTabValue(aChild, this.kPARENT, aParent.getAttribute(this.kID)); + + this.updateTabsCount(aParent); + if (shouldInheritIndent && !aInfo.dontUpdateIndent) + this.inheritTabIndent(aChild, aParent); + + if (!aInfo.dontMove) { + if (newIndex > aChild._tPos) + newIndex--; + this.moveTabSubtreeTo(aChild, newIndex); + } + + if (aInfo.forceExpand) { + this.collapseExpandSubtree(aParent, false, aInfo.dontAnimate); + } + else if (!aInfo.dontExpand) { + if (utils.getTreePref('autoCollapseExpandSubtreeOnAttach') && + this.shouldTabAutoExpanded(aParent)) + this.collapseExpandTreesIntelligentlyFor(aParent); + + if (utils.getTreePref('autoCollapseExpandSubtreeOnSelect')) { + newAncestors.forEach(function(aAncestor) { + if (this.shouldTabAutoExpanded(aAncestor)) + this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate); + }, this); + } + else if (this.shouldTabAutoExpanded(aParent)) { + if (utils.getTreePref('autoExpandSubtreeOnAppendChild')) { + newAncestors.forEach(function(aAncestor) { + if (this.shouldTabAutoExpanded(aAncestor)) + this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate); + }, this); + } + else + this.collapseExpandTab(aChild, true, aInfo.dontAnimate); + } + + if (this.isCollapsed(aParent)) + this.collapseExpandTab(aChild, true, aInfo.dontAnimate); + } + else if (this.shouldTabAutoExpanded(aParent) || + this.isCollapsed(aParent)) { + this.collapseExpandTab(aChild, true, aInfo.dontAnimate); + } + + if (!aInfo.dontUpdateIndent) { + this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); + this.checkTabsIndentOverflow(); + } + + this.promoteTooDeepLevelTabs(aChild); + + this.fireAttachedEvent(aChild, aParent); + }, + fireAttachedEvent : function TSTBrowser_fireAttachedEvent(aChild, aParent) + { + var data = { + parentTab : aParent + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED, aChild, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED.replace(/^nsDOM/, ''), aChild, true, false, data); + }, + + shouldTabAutoExpanded : function TSTBrowser_shouldTabAutoExpanded(aTab) + { + return this.hasChildTabs(aTab) && + this.isSubtreeCollapsed(aTab); + }, + + detachTab : function TSTBrowser_detachTab(aChild, aInfo) /* PUBLIC API */ + { + if (!aChild || !aChild.parentNode) + return; + if (!aInfo) + aInfo = {}; + + var parentTab = this.getParentTab(aChild); + if (!parentTab) + return; + + var id = aChild.getAttribute(this.kID); + + this.setTabValue( + parentTab, + this.kCHILDREN, + parentTab.getAttribute(this.kCHILDREN) + .split('|') + .filter(function(aId) { + return this.getTabById(aId) && aId != id; + }, this).join('|') + ); + this.deleteTabValue(aChild, this.kPARENT); + + if (!this.hasChildTabs(parentTab)) + this.setTabValue(parentTab, this.kSUBTREE_COLLAPSED, true); + + this.updateTabsCount(parentTab); + + if (!aInfo.dontUpdateIndent) { + this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); + this.checkTabsIndentOverflow(); + } + + var data = { + parentTab : parentTab + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_DETACHED, aChild, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_DETACHED.replace(/^nsDOM/, ''), aChild, true, false, data); + + if (this.isTemporaryGroupTab(parentTab) && !this.hasChildTabs(parentTab)) { + this.window.setTimeout(function(aTabBrowser) { + if (parentTab.parentNode) + aTabBrowser.removeTab(parentTab, { animate : true }); + parentTab = null; + }, 0, this.getTabBrowserFromChild(parentTab)); + } + }, + partTab : function TSTBrowser_partTab(aChild, aInfo) /* PUBLIC API, for backward compatibility */ + { + return this.detachTab(aChild, aInfo); + }, + + detachAllChildren : function TSTBrowser_detachAllChildren(aTab, aInfo) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var children = this.getChildTabs(aTab); + if (!children.length) + return; + + aInfo = aInfo || {}; + if (!('behavior' in aInfo)) + aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN; + if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN) + aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD; + + var b = this.mTabBrowser; + var parentTab = this.getParentTab(aTab); + + if ( + this.isGroupTab(aTab) && + this.getTabs(b).filter(function(aTab) { + return !b._removingTabs || b._removingTabs.indexOf(aTab) < 0; + }).length == children.length + ) { + aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; + aInfo.dontUpdateIndent = false; + } + + var insertBefore = null; + if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN && + !utils.getTreePref('closeParentBehavior.moveDetachedTabsToBottom')) { + insertBefore = this.getNextSiblingTab(this.getRootTab(aTab)); + } + for (let i = 0, maxi = children.length; i < maxi; i++) + { + let tab = children[i]; + if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN) { + this.detachTab(tab, aInfo); + this.moveTabSubtreeTo(tab, insertBefore ? insertBefore._tPos - 1 : this.getLastTab(b)._tPos ); + } + else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD) { + this.detachTab(tab, aInfo); + if (i == 0) { + if (parentTab) { + this.attachTabTo(tab, parentTab, inherit(aInfo, { + dontExpand : true, + dontMove : true + })); + } + this.collapseExpandSubtree(tab, false); + this.deleteTabValue(tab, this.kSUBTREE_COLLAPSED); + } + else { + this.attachTabTo(tab, children[0], inherit(aInfo, { + dontExpand : true, + dontMove : true + })); + } + } + else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN && parentTab) { + this.attachTabTo(tab, parentTab, inherit(aInfo, { + dontExpand : true, + dontMove : true + })); + } + else { // aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN + this.detachTab(tab, aInfo); + } + } + }, + partAllChildren : function TSTBrowser_partAllChildren(aTab, aInfo) /* for backward compatibility */ + { + return this.detachAllChildren(aTab, aInfo); + }, + + detachTabs : function TSTBrowser_detachTabs(aTabs) + { + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (aTabs.indexOf(this.getParentTab(tab)) > -1) + continue; + this.detachAllChildren(tab, { + behavior : this.getCloseParentBehaviorForTab( + tab, + this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD + ) + }); + } + }, + partTabs : function TSTBrowser_partTabs(aTabs) /* for backward compatibility */ + { + return this.detachTabs(aTabs); + }, + + getCloseParentBehaviorForTab : function TSTBrowser_getCloseParentBehaviorForTab(aTab, aDefaultBehavior) + { + var closeParentBehavior = utils.getTreePref('closeParentBehavior'); + var closeRootBehavior = utils.getTreePref('closeRootBehavior'); + + var parentTab = this.getParentTab(aTab); + var behavior = aDefaultBehavior ? + aDefaultBehavior : + (!parentTab && closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN) ? + closeRootBehavior : + closeParentBehavior ; + if (behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD && + parentTab && + this.getChildTabs(parentTab).length == 1) + behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; + + return behavior; + }, + + updateTabsIndent : function TSTBrowser_updateTabsIndent(aTabs, aLevel, aJustNow) + { + if (!aTabs || !aTabs.length || !this._treeViewEnabled) + return; + + if (aLevel === void(0)) + aLevel = this.getAncestorTabs(aTabs[0]).length; + + var b = this.mTabBrowser; + var margin = this.indent < 0 ? this.baseIndent : this.indent ; + var indent = (this.maxTreeLevel < 0 ? aLevel : Math.min(aLevel, this.maxTreeLevel) ) * margin; + + var multirow = this.isMultiRow(); + if (multirow) { + let maxIndent = parseInt(aTabs[0].boxObject.height / 2); + indent = Math.min(aLevel * 3, maxIndent); + } + + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (!tab.parentNode) + continue; // ignore removed tabs + this.updateTabIndent(tab, indent, aJustNow); + tab.setAttribute(this.kNEST, aLevel); + this.updateCanCollapseSubtree(tab, aLevel); + this.updateTabsIndent(this.getChildTabs(tab), aLevel+1, aJustNow); + } + }, + updateTabsIndentWithDelay : function TSTBrowser_updateTabsIndentWithDelay(aTabs) + { + if (this.updateTabsIndentWithDelayTimer) + this.window.clearTimeout(this.updateTabsIndentWithDelayTimer); + + this.updateTabsIndentWithDelayTabs = this.updateTabsIndentWithDelayTabs.concat(aTabs); + this.updateTabsIndentWithDelayTimer = this.window.setTimeout(function(aSelf) { + var tabs = []; + for (let i = 0, maxi = aSelf.updateTabsIndentWithDelayTabs.length; i < maxi; i++) + { + let tab = aSelf.updateTabsIndentWithDelayTabs[i]; + if (tabs.indexOf(tab) < 0 && tab.parentNode) + tabs.push(tab); + } + aSelf.updateTabsIndentWithDelayTabs = []; + aSelf.updateTabsIndent(tabs); + aSelf.window.clearTimeout(aSelf.updateTabsIndentWithDelayTimer); + aSelf.updateTabsIndentWithDelayTimer = null; + tabs = null; + }, 0, this); + }, + updateTabsIndentWithDelayTimer : null, + + updateTabIndent : function TSTBrowser_updateTabIndent(aTab, aIndent, aJustNow) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + this.stopTabIndentAnimation(aTab); + + if (aTab.hasAttribute('pinned')) + return; + + if (!this.enableSubtreeIndent) + aIndent = 0; + + if (this.isMultiRow()) { + let colors = '-moz-border-'+this.indentTarget+'-colors:'+(function() { + var retVal = []; + for (var i = 1; i < aIndent; i++) + { + retVal.push('transparent'); + } + retVal.push('ThreeDShadow'); + return retVal.length == 1 ? 'none' : retVal.join(' ') ; + })()+' !important;'; + let boxes = this.document.getAnonymousNodes(aTab); + for (let i = 0, box = boxes.length; i < maxi; i++) + { + let box = boxes[i]; + if (box.nodeType != Node.ELEMENT_NODE) + continue; + box.setAttribute( + 'style', + box.getAttribute('style') + .replace(/(-moz-)?border-(top|bottom)(-[^:]*)?.*:[^;]+;?/g, '') + + '; border-'+this.indentTarget+': solid transparent '+aIndent+'px !important;'+colors + ); + } + return; + } + + if ( + !this.animationEnabled || + aJustNow || + this.indentDuration < 1 || + this.isCollapsed(aTab) + ) { + aTab.style.setProperty(this.indentCSSProp, aIndent+'px', 'important'); + return; + } + + var self = this; + var CSSTransitionEnabled = ('transition' in aTab.style || 'MozTransition' in aTab.style); + if (CSSTransitionEnabled) { + aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { + delete aTab.__treestyletab__updateTabIndentTask; + if (!self.isDestroying) + aTab.style.setProperty(self.indentCSSProp, aIndent+'px', 'important'); + return true; + }; + this.animationManager.addTask( + aTab.__treestyletab__updateTabIndentTask, + 0, 0, 1, this.window + ); + return; + } + + var startIndent = this.getPropertyPixelValue(aTab, this.indentCSSProp); + var delta = aIndent - startIndent; + var radian = 90 * Math.PI / 180; + aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { + if (self.isDestroying) + return true; + var indent, finished; + if (aTime >= aDuration) { + delete aTab.__treestyletab__updateTabIndentTask; + indent = aIndent; + finished = true; + } + else { + indent = startIndent + (delta * Math.sin(aTime / aDuration * radian)); + finished = false; + } + aTab.style.setProperty(self.indentCSSProp, indent+'px', 'important'); + if (finished) { + startIndent = null; + delta = null; + radian = null; + self = null; + aTab = null; + } + return finished; + }; + this.animationManager.addTask( + aTab.__treestyletab__updateTabIndentTask, + 0, 0, this.indentDuration, this.window + ); + }, + stopTabIndentAnimation : function TSTBrowser_stopTabIndentAnimation(aTab) + { + if (!aTab.parentNode) + return; // do nothing for closed tab! + this.animationManager.removeTask( + aTab.__treestyletab__updateTabIndentTask + ); + delete aTab.__treestyletab__updateTabIndentTask; + }, + + inheritTabIndent : function TSTBrowser_inheritTabIndent(aNewTab, aExistingTab) + { + var indent = this.getPropertyPixelValue(aExistingTab, this.indentCSSProp); + if (indent) + aNewTab.style.setProperty(this.indentCSSProp, indent+'px', 'important'); + else + aNewTab.style.removeProperty(this.indentCSSProp); + }, + + updateAllTabsIndent : function TSTBrowser_updateAllTabsIndent(aJustNow) + { + this.updateTabsIndent(this.rootTabs, 0, aJustNow); +// this.checkTabsIndentOverflow(); + }, + + checkTabsIndentOverflow : function TSTBrowser_checkTabsIndentOverflow(aDelay) + { + this.cancelCheckTabsIndentOverflow(); + this.checkTabsIndentOverflowTimer = this.window.setTimeout(function(aSelf) { + aSelf.checkTabsIndentOverflowTimer = null; + aSelf.checkTabsIndentOverflowCallback(); + }, aDelay || 100, this); + }, + cancelCheckTabsIndentOverflow : function TSTBrowser_cancelCheckTabsIndentOverflow() + { + if (this.checkTabsIndentOverflowTimer) { + this.window.clearTimeout(this.checkTabsIndentOverflowTimer); + this.checkTabsIndentOverflowTimer = null; + } + }, + checkTabsIndentOverflowTimer : null, + checkTabsIndentOverflowCallback : function TSTBrowser_checkTabsIndentOverflowCallback() + { + if (!utils.getTreePref('indent.autoShrink')) { + this.indent = -1; + return; + } + + var b = this.mTabBrowser; + var tabbarSize = b.mTabContainer.boxObject[this.invertedSizeProp]; + if (!tabbarSize) // don't update indent for collapsed tab bar + return; + + var tabs = Array.slice(b.mTabContainer.querySelectorAll( + 'tab['+this.kNEST+']:not(['+this.kNEST+'="0"]):not(['+this.kNEST+'=""])'+ + ':not(['+this.kCOLLAPSED+'="true"])'+ + ':not([hidden="true"])'+ + ':not([collapsed="true"])' + )); + if (!tabs.length) + return; + + var self = this; + tabs.sort(function(aA, aB) { return Number(aA.getAttribute(self.kNEST)) - Number(aB.getAttribute(self.kNEST)); }); + var nest = tabs[tabs.length-1].getAttribute(this.kNEST); + if (this.maxTreeLevel > -1) + nest = Math.min(nest, this.maxTreeLevel); + if (!nest) + return; + + var oldIndent = this.indent; + var indent = (oldIndent < 0 ? this.baseIndent : oldIndent ) * nest; + var maxIndentBase = Math.min( + this.getFirstNormalTab(b).boxObject[this.invertedSizeProp], + tabbarSize + ); + var isVertical = this.isVertical; + if (!isVertical) { + if (this._horizontalTabMaxIndentBase) + maxIndentBase = this._horizontalTabMaxIndentBase; + else + this._horizontalTabMaxIndentBase = maxIndentBase; + } + var maxIndent = maxIndentBase * (isVertical ? 0.33 : 0.5 ); + + var indentMin = utils.getTreePref(isVertical ? 'indent.min.vertical' : 'indent.min.horizontal'); + var indentUnit = Math.max(Math.floor(maxIndent / nest), indentMin); + if (indent > maxIndent) { + this.indent = indentUnit; + } + else { + this.indent = -1; + if ((this.baseIndent * nest) > maxIndent) + this.indent = indentUnit; + } + + if (oldIndent != this.indent) { + this.updateAllTabsIndent(); + } + }, + _horizontalTabMaxIndentBase : 0, + + updateCanCollapseSubtree : function TSTBrowser_updateCanCollapseSubtree(aTab, aLevel) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + if ( + !aLevel || + this.maxTreeLevel < 0 || + this.maxTreeLevel > aLevel + ) { + aTab.setAttribute(this.kALLOW_COLLAPSE, true); + this.collapseExpandSubtree(aTab, this.isSubtreeCollapsed(aTab)); + } + else { + this.collapseExpandSubtree(aTab, false); + aTab.removeAttribute(this.kALLOW_COLLAPSE); + } + }, + + updateTabsCount : function TSTBrowser_updateTabsCount(aTab, aDontUpdateAncestor) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + var count = this.document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER); + if (count) { + let value = this.getDescendantTabs(aTab).length; + if (this.counterRole == this.kCOUNTER_ROLE_ALL_TABS) + value += 1; + count.setAttribute('value', value); + } + if (!aDontUpdateAncestor) { + let parent = this.getParentTab(aTab); + if (parent) + this.updateTabsCount(parent); + } + }, + + updateAllTabsCount : function TSTBrowser_updateAllTabsCount() + { + var tabs = this.rootTabs; + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + this.updateTabsCount(tab, this); + } + }, + + promoteTooDeepLevelTabs : function TSTBrowser_promoteTooDeepLevelTabs(aParent) + { + if (this.maxTreeLevel < 0 || !this.maxTreeLevelPhisical) + return; + + var tabs = aParent ? this.getDescendantTabs(aParent) : this.getAllTabs(this.mTabBrowser) ; + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let level = parseInt(tab.getAttribute(this.kNEST) || 0); + if (level <= this.maxTreeLevel) + continue; + + let parent = this.getParentTab(tab); + let newParent = this.getParentTab(parent); + if (this.maxTreeLevel == 0 || !newParent) { + this.detachTab(aTab); + } + else { + let nextSibling = this.getNextTab(tab); + this.attachTabTo(tab, newParent, { + dontMove : true, + insertBefore : nextSibling + }); + } + } + }, + +/* move */ + + moveTabSubtreeTo : function TSTBrowser_moveTabSubtreeTo(aTab, aIndex) + { + if (!aTab || !aTab.parentNode) + return; + + var b = this.mTabBrowser; + this.subTreeMovingCount++; + + this.internallyTabMovingCount++; + b.moveTabTo(aTab, aIndex); + this.internallyTabMovingCount--; + + this.subTreeChildrenMovingCount++; + this.internallyTabMovingCount++; + var descendantTabs = this.getDescendantTabs(aTab); + for (let i = 0, maxi = descendantTabs.length; i < maxi; i++) + { + let descendantTab = descendantTabs[i]; + b.moveTabTo(descendantTab, aTab._tPos + i + (aTab._tPos < descendantTab._tPos ? 1 : 0 )); + } + this.internallyTabMovingCount--; + this.subTreeChildrenMovingCount--; + + this.subTreeMovingCount--; + }, + moveTabSubTreeTo : function(...aArgs) { + return this.moveTabSubtreeTo.apply(this, aArgs); + }, // obsolete, for backward compatibility + + moveTabLevel : function TSTBrowser_moveTabLevel(aEvent) + { + var b = this.mTabBrowser; + var parentTab = this.getParentTab(b.mCurrentTab); + if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) { + let prevTab = this.getPreviousSiblingTab(b.mCurrentTab); + if ((!parentTab && prevTab) || + (parentTab && b.mCurrentTab != this.getFirstChildTab(parentTab))) { + this.attachTabTo(b.mCurrentTab, prevTab); + b.mCurrentTab.focus(); + return true; + } + } + else if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT && parentTab) { + let grandParent = this.getParentTab(parentTab); + if (grandParent) { + this.attachTabTo(b.mCurrentTab, grandParent, { + insertBefore : this.getNextSiblingTab(parentTab) + }); + b.mCurrentTab.focus(); + return true; + } + else { + let nextTab = this.getNextSiblingTab(parentTab); + this.detachTab(b.mCurrentTab); + this.internallyTabMovingCount++; + if (nextTab) { + b.moveTabTo(b.mCurrentTab, nextTab._tPos - 1); + } + else { + b.moveTabTo(b.mCurrentTab, this.getLastTab(b)._tPos); + } + this.internallyTabMovingCount--; + b.mCurrentTab.focus(); + return true; + } + } + return false; + }, + + /** + * Imports tabs from another window with their tree structure. + * aOptions is an optional hash which can have two properties: + * * duplicate (boolean) + * * insertBefore (nsIDOMElement) + */ + importTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ + { + return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); + }, + duplicateTabs : function TSTBrowser_duplicateTabs(aTabs, aInsertBefore) /* PUBLIC API */ + { + return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore, duplicate : true }); + }, + moveTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ + { + return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); + }, + moveTabsInternal : function TSTBrowser_moveTabsInternal(aTabs, aOptions) + { + aOptions = aOptions || {}; + + var targetBrowser = this.mTabBrowser; + var sourceWindow = aTabs[0].ownerDocument.defaultView; + var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); + var sourceService = sourceBrowser.treeStyleTab; + + // prevent Multiple Tab Handler feature + targetBrowser.duplicatingSelectedTabs = true; + targetBrowser.movingSelectedTabs = true; + + var shouldClose = ( + !aOptions.duplicate && + sourceService.getAllTabs(sourceBrowser).length == aTabs.length + ); + var newTabs = []; + var treeStructure = sourceService.getTreeStructureFromTabs(aTabs); + + // Firefox fails to "move" collapsed tabs. So, expand them first + // and collapse them after they are moved. + var collapsedStates = sourceService.forceExpandTabs(aTabs);; + + var shouldResetSelection = ( + aTabs.every(function(aTab) { + return aTab.getAttribute('multiselected') == 'true'; + }) && + (sourceService != this || aOptions.duplicate) + ); + + var tabs = this.getTabs(targetBrowser); + var lastTabIndex = tabs[tabs.length -1]._tPos; + for (let i in aTabs) + { + let tab = aTabs[i]; + + if (shouldResetSelection) { + if ('MultipleTabService' in sourceWindow) + sourceWindow.MultipleTabService.setSelection(tab, false); + else + tab.removeAttribute('multiselected'); + } + + if (aOptions.duplicate) { + tab = this.duplicateTabAsOrphan(tab); + newTabs.push(tab); + } + else if (sourceService != this) { + tab = this.importTab(tab); + newTabs.push(tab); + } + + if (shouldResetSelection) { + if ('MultipleTabService' in sourceWindow) + sourceWindow.MultipleTabService.setSelection(tab, true); + else + tab.setAttribute('multiselected', true); + } + + lastTabIndex++; + + let newIndex = aOptions.insertBefore ? aOptions.insertBefore._tPos : lastTabIndex ; + if (aOptions.insertBefore && newIndex > tab._tPos) + newIndex--; + + this.internallyTabMovingCount++; + targetBrowser.moveTabTo(tab, newIndex); + this.collapseExpandTab(tab, false, true); + this.internallyTabMovingCount--; + } + + if (shouldClose) + sourceService.closeOwner(sourceBrowser); + + if (newTabs.length) + this.applyTreeStructureToTabs( + newTabs, + treeStructure, + collapsedStates.map(function(aCollapsed) { + return !aCollapsed + }) + ); + + for (let i = collapsedStates.length - 1; i > -1; i--) + { + sourceService.collapseExpandSubtree(aTabs[i], collapsedStates[i], true); + } + + // Multiple Tab Handler + targetBrowser.movingSelectedTabs = false; + targetBrowser.duplicatingSelectedTabs = false; + + return newTabs; + }, + + importTab : function TSTBrowser_importTab(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return null; + + var newTab = this.mTabBrowser.addTab(); + newTab.linkedBrowser.stop(); + newTab.linkedBrowser.docShell; + this.mTabBrowser.swapBrowsersAndCloseOther(newTab, aTab); + this.mTabBrowser.setTabTitle(newTab); + return newTab; + }, + + duplicateTabAsOrphan : function TSTBrowser_duplicateTabAsOrphan(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return null; + + var newTab = this.mTabBrowser.duplicateTab(aTab); + this.deleteTabValue(newTab, this.kCHILDREN); + this.deleteTabValue(newTab, this.kPARENT); + return newTab; + }, + + closeOwner : function TSTBrowser_closeOwner(aTabOwner) + { + var w = aTabOwner.ownerDocument.defaultView; + if (!w) + return; + if ('SplitBrowser' in w) { + if ('getSubBrowserFromChild' in w.SplitBrowser) { + var subbrowser = w.SplitBrowser.getSubBrowserFromChild(aTabOwner); + if (subbrowser) { + subbrowser.close(); + return; + } + } + if (w.SplitBrowser.browsers.length) + return; + } + w.close(); + }, + +/* collapse/expand */ + + collapseExpandSubtree : function TSTBrowser_collapseExpandSubtree(aTab, aCollapse, aJustNow) /* PUBLIC API */ + { + if (!aTab || !aTab.parentNode) + return; + + if (this.isSubtreeCollapsed(aTab) == aCollapse) + return; + + var b = this.mTabBrowser; + this.doingCollapseExpand = true; + + this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, aCollapse); + + var expandedTabs = this.getChildTabs(aTab); + var lastExpandedTabIndex = expandedTabs.length - 1; + for (let i = 0, maxi = expandedTabs.length; i < maxi; i++) + { + let childTab = expandedTabs[i]; + if (!aCollapse && !aJustNow && i == lastExpandedTabIndex) { + let self = this; + this.collapseExpandTab(childTab, aCollapse, aJustNow, function() { + self.scrollToTabSubtree(aTab); + }); + } + else + this.collapseExpandTab(childTab, aCollapse, aJustNow); + } + + if (aCollapse) + this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY); + + if (utils.getTreePref('indent.autoShrink') && + utils.getTreePref('indent.autoShrink.onlyForVisible')) + this.checkTabsIndentOverflow(); + + this.doingCollapseExpand = false; + }, + manualCollapseExpandSubtree : function(aTab, aCollapse, aJustNow) + { + this.collapseExpandSubtree(aTab, aCollapse, aJustNow); + if (!aCollapse) + this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true); + + if (utils.getTreePref('indent.autoShrink') && + utils.getTreePref('indent.autoShrink.onlyForVisible')) { + this.cancelCheckTabsIndentOverflow(); + if (!aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) { + var self = this; + var stillOver = false; + var id = this.getTabValue(aTab, this.kID); + aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave = function checkTabsIndentOverflowOnMouseLeave(aEvent, aDelayed) { + if (aEvent.type == 'mouseover') { + if (self.evaluateXPath( + 'ancestor-or-self::*[@' + self.kID + '="' + id + '"]', + aEvent.originalTarget || aEvent.target, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue) + stillOver = true; + return; + } + else if (!aDelayed) { + if (stillOver) { + stillOver = false; + } + self.Deferred.next(function() { + checkTabsIndentOverflowOnMouseLeave.call(null, aEvent, true); + }); + return; + } else if (stillOver) { + return; + } + var x = aEvent.clientX; + var y = aEvent.clientY; + var rect = aTab.getBoundingClientRect(); + if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) + return; + self.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + self.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave; + self.checkTabsIndentOverflow(); + }; + this.document.addEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + this.document.addEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true); + } + } + }, + + collapseExpandTab : function TSTBrowser_collapseExpandTab(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation) + { + if (!aTab || !aTab.parentNode || !this.getParentTab(aTab)) + return; + + this.setTabValue(aTab, this.kCOLLAPSED, aCollapse); + this.updateTabCollapsed(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation); + + var data = { + collapsed : aCollapse + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, aTab, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED.replace(/^nsDOM/, ''), aTab, true, false, data); + + var b = this.mTabBrowser; + var parent; + if (aCollapse && aTab == b.selectedTab && (parent = this.getParentTab(aTab))) { + var newSelection = parent; + this.getAncestorTabs(aTab).some(function(aAncestor) { + if (!this.isCollapsed(aAncestor)) { + newSelection = aAncestor; + return true; + } + return false; + }, this); + b.selectedTab = newSelection; + } + + if (!this.isSubtreeCollapsed(aTab)) { + let tabs = this.getChildTabs(aTab); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + this.collapseExpandTab(tabs[i], aCollapse, aJustNow); + } + } + }, + updateTabCollapsed : function TSTBrowser_updateTabCollapsed(aTab, aCollapsed, aJustNow, aCallbackToRunOnStartAnimation) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + + this.stopTabCollapseAnimation(aTab); + + aTab.removeAttribute(this.kX_OFFSET); + aTab.removeAttribute(this.kY_OFFSET); + + if (!this.canCollapseSubtree(this.getRootTab(aTab))) + aCollapsed = false; + + aTab.setAttribute(this.kCOLLAPSING_PHASE, aCollapsed ? this.kCOLLAPSING_PHASE_TO_BE_COLLAPSED : this.kCOLLAPSING_PHASE_TO_BE_EXPANDED ); + + var CSSTransitionEnabled = ('transition' in aTab.style || 'MozTransition' in aTab.style); + + var maxMargin; + var offsetAttr; + var collapseProp = 'margin-'+this.collapseTarget; + let (firstTab = this.getFirstNormalTab(this.mTabBrowser)) { + if (this.isVertical) { + maxMargin = firstTab.boxObject.height; + offsetAttr = this.kY_OFFSET; + if (firstTab.style.height) + aTab.style.height = firstTab.style.height; + } + else { + maxMargin = firstTab.boxObject.width; + offsetAttr = this.kX_OFFSET; + if (firstTab.style.width) + aTab.style.width = firstTab.style.width; + } + } + + var startMargin, endMargin, startOpacity, endOpacity; + if (aCollapsed) { + startMargin = 0; + endMargin = maxMargin; + startOpacity = 1; + endOpacity = 0; + if (this.canStackTabs && this.getParentTab(aTab)) { + endOpacity = 1; + endMargin = this.kSTACKED_TAB_MARGIN; + } + } + else { + startMargin = maxMargin; + endMargin = 0; + startOpacity = 0; + endOpacity = 1; + if (this.canStackTabs && this.getParentTab(aTab)) { + startOpacity = 1; + startMargin = this.kSTACKED_TAB_MARGIN; + } + } + + if ( + !this.animationEnabled || + aJustNow || + this.collapseDuration < 1 // || +// !this.isVertical || +// !this.canCollapseSubtree(this.getParentTab(aTab)) + ) { + if (aCollapsed) + aTab.setAttribute(this.kCOLLAPSED_DONE, true); + else + aTab.removeAttribute(this.kCOLLAPSED_DONE); + aTab.removeAttribute(this.kCOLLAPSING_PHASE); + + // Pinned tabs are positioned by "margin-top", so + // we must not reset the property for pinned tabs. + // (However, we still must update "opacity".) + let pinned = aTab.getAttribute('pinned') == 'true'; + let canExpand = !pinned || this.collapseCSSProp != 'margin-top'; + + if (CSSTransitionEnabled) { + if (canExpand) + aTab.style.setProperty(this.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); + + if (endOpacity == 0) + aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); + else + aTab.style.removeProperty('opacity'); + } + else { + if (canExpand) + aTab.style.removeProperty(this.collapseCSSProp); + aTab.style.removeProperty('opacity'); + } + + if (aCallbackToRunOnStartAnimation) + aCallbackToRunOnStartAnimation(); + return; + } + + var deltaMargin = endMargin - startMargin; + var deltaOpacity = endOpacity - startOpacity; + + aTab.style.setProperty(this.collapseCSSProp, startMargin ? '-'+startMargin+'px' : '', 'important'); + aTab.style.setProperty('opacity', startOpacity == 1 ? '' : startOpacity, 'important'); + + if (!aCollapsed) { + aTab.setAttribute(offsetAttr, maxMargin); + aTab.removeAttribute(this.kCOLLAPSED_DONE); + } + + var radian = 90 * Math.PI / 180; + var self = this; + var firstFrame = true; + aTab.__treestyletab__updateTabCollapsedTask = function(aTime, aBeginning, aChange, aDuration) { + if (self.isDestroying) + return true; + if (firstFrame) { + // The callback must be started before offsetAttr is changed! + if (aCallbackToRunOnStartAnimation) + aCallbackToRunOnStartAnimation(); + if (CSSTransitionEnabled) { + aTab.style.setProperty(self.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); + aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); + } + } + firstFrame = false; + // If this is the last tab, negative scroll happens. + // Then, we shouldn't do animation. + var stopAnimation = false; + var scrollBox = self.scrollBox; + if (scrollBox) { + if (scrollBox._scrollbox) + scrollBox = scrollBox._scrollbox; + if ('scrollTop' in scrollBox && + (scrollBox.scrollTop < 0 || scrollBox.scrollLeft < 0)) { + scrollBox.scrollTop = 0; + scrollBox.scrollLeft = 0; + stopAnimation = true; + } + } + if (aTime >= aDuration || stopAnimation) { + delete aTab.__treestyletab__updateTabCollapsedTask; + if (aCollapsed) + aTab.setAttribute(self.kCOLLAPSED_DONE, true); + if (!CSSTransitionEnabled) { + aTab.style.removeProperty(self.collapseCSSProp); + aTab.style.removeProperty('opacity'); + } + aTab.removeAttribute(offsetAttr); + aTab.removeAttribute(self.kCOLLAPSING_PHASE); + + maxMargin = null; + offsetAttr = null; + startMargin = null; + endMargin = null; + startOpacity = null; + endOpacity = null; + deltaMargin = null; + deltaOpacity = null; + collapseProp = null; + radian = null; + self = null; + aTab = null; + + return true; + } + else { + if (!CSSTransitionEnabled) { + let power = Math.sin(aTime / aDuration * radian); + let margin = startMargin + (deltaMargin * power); + let opacity = startOpacity + (deltaOpacity * power); + aTab.style.setProperty(self.collapseCSSProp, margin ? '-'+margin+'px' : '', 'important'); + aTab.style.setProperty('opacity', opacity == 1 ? '' : opacity, 'important'); + } + aTab.setAttribute(offsetAttr, maxMargin); + return false; + } + }; + this.animationManager.addTask( + aTab.__treestyletab__updateTabCollapsedTask, + 0, 0, this.collapseDuration, this.window + ); + }, + kOPACITY_RULE_REGEXP : /opacity\s*:[^;]+;?/, + kSTACKED_TAB_MARGIN : 15, + stopTabCollapseAnimation : function TSTBrowser_stopTabCollapseAnimation(aTab) + { + if (!aTab.parentNode) + return; // do nothing for closed tab! + + this.animationManager.removeTask( + aTab.__treestyletab__updateTabCollapsedTask + ); + }, + + collapseExpandTreesIntelligentlyFor : function TSTBrowser_collapseExpandTreesIntelligentlyFor(aTab, aJustNow) + { + if (!aTab || + !aTab.parentNode || + this.doingCollapseExpand || + !this.canCollapseSubtree(aTab)) + return; + + var b = this.mTabBrowser; + var sameParentTab = this.getParentTab(aTab); + var expandedAncestors = [aTab].concat(this.getAncestorTabs(aTab)) + .map(function(aAncestor) { + return aAncestor.getAttribute(this.kID); + }, this) + .join('|'); + + var xpathResult = this.evaluateXPath( + 'child::xul:tab[@'+this.kCHILDREN+' and not(@'+this.kCOLLAPSED+'="true") and not(@'+this.kSUBTREE_COLLAPSED+'="true") and @'+this.kID+' and not(contains("'+expandedAncestors+'", @'+this.kID+')) and not(@hidden="true")]', + b.mTabContainer + ); + for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) + { + let dontCollapse = false; + let collapseTab = xpathResult.snapshotItem(i); + + let parentTab = this.getParentTab(collapseTab); + if (parentTab) { + dontCollapse = true; + if (!this.isSubtreeCollapsed(parentTab)) { + this.getAncestorTabs(collapseTab).some(function(aAncestor) { + if (expandedAncestors.indexOf(aAncestor.getAttribute(this.kID)) < 0) + return false; + dontCollapse = false; + return true; + }, this); + } + } + + let manuallyExpanded = this.getTabValue(collapseTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; + if (!dontCollapse && !manuallyExpanded) + this.collapseExpandSubtree(collapseTab, true, aJustNow); + } + + this.collapseExpandSubtree(aTab, false, aJustNow); + }, + + collapseExpandAllSubtree : function TSTBrowser_collapseExpandAllSubtree(aCollapse, aJustNow) + { + var tabs = this.mTabBrowser.mTabContainer.querySelectorAll( + 'tab['+this.kID+']['+this.kCHILDREN+']'+ + ( + aCollapse ? + ':not(['+this.kSUBTREE_COLLAPSED+'="true"])' : + '['+this.kSUBTREE_COLLAPSED+'="true"]' + ) + ); + for (var i = 0, maxi = tabs.length; i < maxi; i++) + { + this.collapseExpandSubtree(tabs[i], aCollapse, aJustNow); + } + }, + +/* scroll */ + + scrollTo : function TSTBrowser_scrollTo(aEndX, aEndY) + { + if (this.deferredTasks['cancelPerformingAutoScroll']) + return; + + if (this.animationEnabled || this.smoothScrollEnabled) { + this.smoothScrollTo(aEndX, aEndY); + } + else { + try { + this.cancelPerformingAutoScroll(); + this.scrollBoxObject.scrollTo(aEndX, aEndY); + } + catch(e) { + } + } + }, + + smoothScrollTo : function TSTBrowser_smoothScrollTo(aEndX, aEndY, aDuration) + { + this.cancelPerformingAutoScroll(true); + + var b = this.mTabBrowser; + var scrollBoxObject = this.scrollBoxObject; + var x = {}, y = {}; + scrollBoxObject.getPosition(x, y); + var startX = x.value; + var startY = y.value; + var deltaX = aEndX - startX; + var deltaY = aEndY - startY; + + var arrowscrollbox = scrollBoxObject.element.parentNode; + if ( + arrowscrollbox && + ( + arrowscrollbox.localName != 'arrowscrollbox' || + !('_isScrolling' in arrowscrollbox) + ) + ) + arrowscrollbox = null; + + var radian = 90 * Math.PI / 180; + var self = this; + this.smoothScrollTask = function(aTime, aBeginning, aChange, aDuration) { + if (self.isDestroying) + return true; + var scrollBoxObject = self.scrollBoxObject; + if (aTime >= aDuration || self.deferredTasks['cancelPerformingAutoScroll']) { + if (!self.deferredTasks['cancelPerformingAutoScroll']) { + scrollBoxObject.scrollTo(aEndX, aEndY); + + /** + * When there is any expanding tab, we have to retry to scroll. + * if the scroll box was expanded. + */ + let oldSize = self._getMaxScrollSize(scrollBoxObject); + let key = 'smoothScrollTo_'+parseInt(Math.random() * 65000); + (self.deferredTasks[key] = self.Deferred.next(function() { + let newSize = self._getMaxScrollSize(scrollBoxObject); + let lastTab = self.getLastVisibleTab(self.mTabBrowser); + if ( + // scroll size can be expanded by expanding tabs. + oldSize[0] < newSize[0] || oldSize[1] < newSize[1] || + // there are still animating tabs + self.getXOffsetOfTab(lastTab) || self.getYOffsetOfTab(lastTab) || + self.mTabBrowser.mTabContainer.querySelector('tab['+self.kCOLLAPSING_PHASE+'="'+self.kCOLLAPSING_PHASE_TO_BE_EXPANDED+'"]') + ) + self.smoothScrollTo(aEndX, aEndY, parseInt(aDuration * 0.5)); + self = null; + scrollBoxObject = null; + })).error(self.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks[key]; + }); + } + + b = null; + x = null; + y = null; + startX = null; + startY = null; + radian = null; + self.smoothScrollTask = null; + + return true; + } + + var power = Math.sin(aTime / aDuration * radian); + var newX = startX + parseInt(deltaX * power); + var newY = startY + parseInt(deltaY * power); + scrollBoxObject.scrollTo(newX, newY); + return false; + }; + this.animationManager.addTask( + this.smoothScrollTask, + 0, 0, this.smoothScrollDuration || aDuration, this.window + ); + }, + _getMaxScrollSize : function(aScrollBoxObject) { + var x = {}, y = {}; + aScrollBoxObject.getPosition(x, y); + var w = {}, h = {}; + aScrollBoxObject.getScrolledSize(w, h); + var maxX = Math.max(0, w.value - aScrollBoxObject.width); + var maxY = Math.max(0, h.value - aScrollBoxObject.height); + return [maxX, maxY]; + }, + smoothScrollTask : null, + + scrollToTab : function TSTBrowser_scrollToTab(aTab, aOnlyWhenCurrentTabIsInViewport) + { + if (!aTab || !aTab.parentNode || this.isTabInViewport(aTab)) + return; + + var b = this.mTabBrowser; + + var scrollBoxObject = this.scrollBoxObject; + var w = {}, h = {}; + try { + scrollBoxObject.getScrolledSize(w, h); + } + catch(e) { // Tab Mix Plus (or others) + return; + } + + var targetTabBox = this.getFutureBoxObject(aTab); + var baseTabBox = this.getFirstNormalTab(b).boxObject; + + var targetX = (targetTabBox.screenX < scrollBoxObject.screenX) ? + (targetTabBox.screenX - baseTabBox.screenX) - (targetTabBox.width * 0.5) : + (targetTabBox.screenX - baseTabBox.screenX) - scrollBoxObject.width + (targetTabBox.width * 1.5) ; + + var targetY = (targetTabBox.screenY < scrollBoxObject.screenY) ? + (targetTabBox.screenY - baseTabBox.screenY) - (targetTabBox.height * 0.5) : + (targetTabBox.screenY - baseTabBox.screenY) - scrollBoxObject.height + (targetTabBox.height * 1.5) ; + + if (aOnlyWhenCurrentTabIsInViewport && b.selectedTab != aTab) { + let box = b.selectedTab.boxObject; + if (targetTabBox.screenX - box.screenX + baseTabBox.width > scrollBoxObject.width || + targetTabBox.screenY - box.screenY + baseTabBox.height > scrollBoxObject.height) + return; + } + + this.scrollTo(targetX, targetY); + }, + + scrollToTabSubtree : function TSTBrowser_scrollToTabSubtree(aTab) + { + if (!aTab.parentNode) // do nothing for closed tab! + return; + var b = this.mTabBrowser; + var descendant = this.getDescendantTabs(aTab); + var parentTabBox = aTab.boxObject; + + var containerPosition = this.tabStrip.boxObject[this.screenPositionProp]; + var containerSize = this.tabStrip.boxObject[this.sizeProp]; + var parentPosition = parentTabBox[this.screenPositionProp]; + + var lastVisible = aTab; + for (let i = descendant.length-1; i > -1; i--) + { + let tab = descendant[i]; + if (this.isCollapsed(tab)) + continue; + + let box = this.getFutureBoxObject(tab); + if (box[this.screenPositionProp] + box[this.sizeProp] - parentPosition > containerSize) + continue; + + lastVisible = tab; + break; + } + + if (this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) + return; + + var lastPosition = lastVisible.boxObject[this.screenPositionProp]; + var tabSize = lastVisible.boxObject[this.sizeProp]; + + if (lastPosition - parentPosition + tabSize > containerSize - tabSize) { // out of screen + var endPos = parentPosition - this.getFirstNormalTab(b).boxObject[this.screenPositionProp] - tabSize * 0.5; + var endX = this.isVertical ? 0 : endPos ; + var endY = this.isVertical ? endPos : 0 ; + this.scrollTo(endX, endY); + } + else if (!this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) { + this.scrollToTab(aTab); + } + else if (this.isTabInViewport(aTab) && !this.isTabInViewport(lastVisible)) { + this.scrollToTab(lastVisible); + } + else if (parentPosition < containerPosition) { + this.scrollToTab(aTab); + } + else { + this.scrollToTab(lastVisible); + } + }, + + notifyBackgroundTab : function TSTBrowser_notifyBackgroundTab() + { + var animateElement = this.mTabBrowser.mTabContainer._animateElement; + var attrName = this.kBG_NOTIFY_PHASE; + if (!animateElement) + return; + + if (this.deferredTasks['notifyBackgroundTab']) + this.deferredTasks['notifyBackgroundTab'].cancel(); + + if (!animateElement.hasAttribute(attrName)) + animateElement.setAttribute(attrName, 'ready'); + + var self = this; + (this.deferredTasks['notifyBackgroundTab'] = this.Deferred + .next(function() { + animateElement.setAttribute(attrName, 'notifying'); + })) + .wait(0.15) + .next(function() { + animateElement.setAttribute(attrName, 'finish'); + }) + .wait(1) + .next(function() { + animateElement.removeAttribute(attrName); + }) + .error(this.defaultDeferredErrorHandler).next(function() { + delete self.deferredTasks['notifyBackgroundTab']; + }); + }, + + restoreTree : function TSTBrowser_restoreTree() + { + if (!this.needRestoreTree || this.useTMPSessionAPI) + return; + + this.needRestoreTree = false; + + if (this.useTMPSessionAPI && prefs.getPref('extensions.tabmix.sessions.manager')) + return; + + var level = utils.getTreePref('restoreTree.level'); + + var tabs = this.getAllTabs(this.mTabBrowser); + var tabsToRestore = 0; // it is the number of pending tabs. + if (utils.SessionStoreInternal && + utils.SessionStoreInternal._browserEpochs) { + let browserEpochs = utils.SessionStoreInternal._browserEpochs; + tabsToRestore = tabs.filter(function(aTab) { + return browserEpochs.has(aTab.linkedBrowser.permanentKey); + }).length; + } + else { + Components.utils.reportError(new Error('There is no property named "_browserEpochs"!!')); + } + + dump('TSTBrowser::restoreTree\n'); + dump(' level = '+level+'\n'); + dump(' tabsToRestore = '+tabsToRestore+'\n'); + if ( + level <= this.kRESTORE_TREE_LEVEL_NONE || + tabsToRestore <= 1 + ) + return; + + var onlyVisible = level <= this.kRESTORE_TREE_ONLY_VISIBLE; + tabs = tabs.filter(function(aTab) { + var toBeRestored = utils.isTabNotRestoredYet(aTab); + // This cache can be set to false by initTab(), but now we not the correct status. + // Because this cached status is used by other methods, we must update it now. + aTab.linkedBrowser.__treestyletab__toBeRestored = toBeRestored; + return ( + toBeRestored && + (!onlyVisible || !aTab.hidden) + ); + }); + dump(' restoring member tabs = '+tabs.length+'\n'); + if (tabs.length <= 1) + return; + + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + let currentId = tab.getAttribute(this.kID); + if (this.tabsHash[currentId] == tab) + delete this.tabsHash[currentId]; + + this.resetTabState(tab); + + tab.setAttribute(this.kID, currentId); // to fallback to it + let [id, duplicated] = this._restoreTabId(tab); + + this.setTabValue(tab, this.kID, id); + this.tabsHash[id] = tab; + + tab.__treestyletab__restoreState = this.RESTORE_STATE_READY_TO_RESTORE; + tab.__treestyletab__duplicated = duplicated; + } + + this.updateAllTabsIndent(true); + + // restore tree from bottom safely + tabs.reverse() + .filter(this.restoreOneTab, this) + .forEach(this.updateInsertionPositionInfo, this); + }, + restoreOneTab : function TSTBrowser_restoreOneTab(aTab) + { + if (aTab.__treestyletab__restoreState != this.RESTORE_STATE_READY_TO_RESTORE) + return false; + + let duplicated = aTab.__treestyletab__duplicated; + + let children = this.getTabValue(aTab, this.kCHILDREN); + if (children) { + this.deleteTabValue(aTab, this.kCHILDREN); + let manuallyExpanded = this.getTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true'; + let subTreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true'; + subTreeCollapsed = this._restoreSubtreeCollapsedState(aTab, subTreeCollapsed); + let self = this; + this._restoreChildTabsRelation(aTab, children, duplicated, function(aChild) { + /** + * When the child has the reference to the parent tab, attachTabTo() + * does nothing. To ensure they are correctly related, we have to + * clear the relation here. + */ + self.deleteTabValue(aChild, self.kPARENT); + let refId = self.getTabValue(aChild, self.kINSERT_BEFORE); + if (refId && duplicated) + refId = self.redirectId(refId); + return { + forceExpand : true, // to prevent to collapse the selected tab + dontAnimate : true, + insertBefore : self.getTabById(refId) + }; + }); + this.collapseExpandSubtree(aTab, subTreeCollapsed, true); + if (manuallyExpanded && !subTreeCollapsed) + this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true); + else + this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY); + } + + delete aTab.__treestyletab__duplicated; + aTab.__treestyletab__restoreState = this.RESTORE_STATE_STRUCTURE_RESTORED; + return true + }, + +/* sub modules */ + + get tabbarDNDObserver() + { + if (!this._tabbarDNDObserver) { + this._tabbarDNDObserver = new TabbarDNDObserver(this.mTabBrowser); + } + return this._tabbarDNDObserver; + }, + + get panelDNDObserver() + { + if (!this._panelDNDObserver) { + this._panelDNDObserver = new TabpanelDNDObserver(this.mTabBrowser); + } + return this._panelDNDObserver; + }, + +/* proxying for window service */ + _callWindowServiceMethod : function TSTBrowser_callWindowServiceMethod(aName, aArgs) + { + return this.windowService[aName].apply(this.windowService, aArgs); + }, + isPopupShown : function TSTBrowser_isPopupShown(...aArgs) { + return this._callWindowServiceMethod('isPopupShown', aArgs); + }, + updateTabsOnTop : function TSTBrowser_updateTabsOnTop(...aArgs) { + return this._callWindowServiceMethod('updateTabsOnTop', aArgs); + }, + registerTabFocusAllowance : function TSTBrowser_registerTabFocusAllowance(...aArgs) { + return this._callWindowServiceMethod('registerTabFocusAllowance', aArgs); + }, + isPopupShown : function TSTBrowser_isPopupShown(...aArgs) { + return this._callWindowServiceMethod('isPopupShown', aArgs); + }, + toggleAutoHide : function TSTBrowser_toggleAutoHide(...aArgs) { + return this._callWindowServiceMethod('toggleAutoHide', aArgs); + }, + +/* show/hide tab bar */ + get autoHide() + { + if (!this._autoHide) { + this._autoHide = new AutoHideBrowser(this.mTabBrowser); + } + return this._autoHide; + }, + + // for backward compatibility + get tabbarShown() { return this.autoHide.expanded; }, + set tabbarShown(aValue) { if (aValue) this.autoHide.show(); else this.autoHide.hide(); return aValue; }, + get tabbarExpanded() { return this.autoHide.expanded; }, + set tabbarExpanded(aValue) { return this.tabbarShown = aValue; }, + get tabbarResizing() { return this.autoHide.isResizing; }, + set tabbarResizing(aValue) { return this.autoHide.isResizing = aValue; }, + get togglerSize() { return this.autoHide.togglerSize; }, + set togglerSize(aValue) { return this.autoHide.togglerSize = aValue; }, + get sensitiveArea() { return this.autoHide.sensitiveArea; }, + set sensitiveArea(aValue) { return this.autoHide.sensitiveArea = aValue; }, + get lastMouseDownTarget() { return this.autoHide.lastMouseDownTarget; }, + set lastMouseDownTarget(aValue) { return this.autoHide.lastMouseDownTarget = aValue; }, + + get tabbarWidth() { return this.autoHide.width; }, + set tabbarWidth(aValue) { return this.autoHide.widthwidth = aValue; }, + get tabbarHeight() { return this.autoHide.height; }, + set tabbarHeight(aValue) { return this.autoHide.height = aValue; }, + get splitterWidth() { return this.autoHide.splitterWidth; }, + + get autoHideShown() { return this.autoHide.expanded; }, + set autoHideShown(aValue) { return this.tabbarShown = aValue; }, + get autoHideXOffset() { return this.autoHide.XOffset; }, + get autoHideYOffset() { return this.autoHide.YOffset; }, + get autoHideMode() { return this.autoHide.mode; }, + set autoHideMode(aValue) { return this.autoHide.mode = aValue; }, + + updateAutoHideMode : function TSTBrowser_updateAutoHideMode() { this.autoHide.updateAutoHideMode(); }, + showHideTabbarInternal : function TSTBrowser_showHideTabbarInternal(aReason) { this.autoHide.showHideInternal(aReason); }, + showTabbar : function TSTBrowser_showTabbar(aReason) { this.autoHide.show(aReason); }, + hideTabbar : function TSTBrowser_hideTabbar(aReason) { this.autoHide.hide(aReason); }, + redrawContentArea : function TSTBrowser_redrawContentArea() { this.autoHide.redrawContentArea(); }, + drawTabbarCanvas : function TSTBrowser_drawTabbarCanvas() { this.autoHide.drawBG(); }, + get splitterBorderColor() { this.autoHide.splitterBorderColor; }, + clearTabbarCanvas : function TSTBrowser_clearTabbarCanvas() { this.autoHide.clearBG(); }, + updateTabbarTransparency : function TSTBrowser_updateTabbarTransparency() { this.autoHide.updateTransparency(); }, + + get autoHideEnabled() { return this.autoHide.enabled; }, + set autoHideEnabled(aValue) { return this.autoHide.enabled = aValue; }, + startAutoHide : function TSTBrowser_startAutoHide() { this.autoHide.start(); }, + endAutoHide : function TSTBrowser_endAutoHide() { this.autoHide.end(); }, + startAutoHideForFullScreen : function TSTBrowser_startAutoHideForFullScreen() { this.autoHide.startForFullScreen(); }, + endAutoHideForFullScreen : function TSTBrowser_endAutoHideForFullScreen() { this.autoHide.endForFullScreen(); }, + + startListenMouseMove : function TSTBrowser_startListenMouseMove() { this.autoHide.startListenMouseMove(); }, + endListenMouseMove : function TSTBrowser_endListenMouseMove() { this.autoHide.endListenMouseMove(); }, + get shouldListenMouseMove() { return this.autoHide.shouldListenMouseMove; }, + showHideTabbarOnMousemove : function TSTBrowser_showHideTabbarOnMousemove() { this.autoHide.showHideOnMousemove(); }, + cancelShowHideTabbarOnMousemove : function TSTBrowser_cancelShowHideTabbarOnMousemove() { this.autoHide.cancelShowHideOnMousemove(); }, + showTabbarForFeedback : function TSTBrowser_showTabbarForFeedback() { this.autoHide.showForFeedback(); }, + delayedShowTabbarForFeedback : function TSTBrowser_delayedShowTabbarForFeedback() { this.autoHide.delayedShowForFeedback(); }, + cancelHideTabbarForFeedback : function TSTBrowser_cancelHideTabbarForFeedback() { this.autoHide.cancelHideForFeedback(); } + +}); + diff --git a/modules/browserUIShowHideObserver.js b/modules/browserUIShowHideObserver.js index 8a1fd5dc..b65eb69b 100644 --- a/modules/browserUIShowHideObserver.js +++ b/modules/browserUIShowHideObserver.js @@ -1,127 +1,127 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2013 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Infocatcher - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['BrowserUIShowHideObserver']; - -Components.utils.import('resource://treestyletab-modules/constants.js'); - -function BrowserUIShowHideObserver(aOwner, aBox) { - this.owner = aOwner; - this.box = aBox; - this.init(); -} -BrowserUIShowHideObserver.prototype = { - get MutationObserver() - { - var w = this.box.ownerDocument.defaultView; - return w.MutationObserver || w.MozMutationObserver; - }, - - init : function BrowserUIShowHideObserver_onInit() - { - if (!this.MutationObserver) - return; - this.observer = new this.MutationObserver((function(aMutations, aObserver) { - this.onMutation(aMutations, aObserver); - }).bind(this)); - this.observer.observe(this.box, { - childList : true, - attributes : true, - subtree : true, - attributeFilter : [ - 'hidden', - 'collapsed', - 'moz-collapsed', // Used in full screen mode - 'disablechrome' - ] - }); - }, - onMutation : function BrowserUIShowHideObserver_onMutation(aMutations, aObserver) - { - aMutations.forEach(function(aMutation) { - switch (aMutation.type) - { - case 'childList': - if (aMutation.target == this.box) - this.owner.browser.treeStyleTab.updateFloatingTabbar(TreeStyleTabConstants.kTABBAR_UPDATE_BY_WINDOW_RESIZE); - return; - - case 'attributes': - this.onAttributeModified(aMutation, aObserver); - return; - } - }, this); - }, - - destroy : function BrowserUIShowHideObserver_destroy() - { - if (this.observer) { - this.observer.disconnect(); - delete this.observer; - } - delete this.box; - delete this.owner; - }, - - onAttributeModified : function BrowserUIShowHideObserver_onAttributeModified(aMutation, aObserver) - { - if (this.handlingAttrChange) - return; - - var TST = this.owner.browser.treeStyleTab; - if ( - // I must ignore show/hide of elements managed by TST, - // to avoid infinity loop. - aMutation.target.hasAttribute(TreeStyleTabConstants.kTAB_STRIP_ELEMENT) && - // However, I have to synchronize visibility of the real - // tab bar and the placeholder's one. If they have - // different visibility, then the tab bar is shown or - // hidden by "auto hide tab bar" feature of someone - // (Pale Moon, Tab Mix Plus, etc.) - this.owner.browser.tabContainer.visible != TST.tabStripPlaceHolder.collapsed - ) - return; - - this.handlingAttrChange = true; - - TST.updateFloatingTabbar(TreeStyleTabConstants.kTABBAR_UPDATE_BY_WINDOW_RESIZE); - - var w = this.box.ownerDocument.defaultView; - w.setTimeout((function() { - this.handlingAttrChange = false; - }).bind(this), 10); - } -}; +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2013 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Infocatcher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['BrowserUIShowHideObserver']; + +Components.utils.import('resource://treestyletab-modules/constants.js'); + +function BrowserUIShowHideObserver(aOwner, aBox) { + this.owner = aOwner; + this.box = aBox; + this.init(); +} +BrowserUIShowHideObserver.prototype = { + get MutationObserver() + { + var w = this.box.ownerDocument.defaultView; + return w.MutationObserver || w.MozMutationObserver; + }, + + init : function BrowserUIShowHideObserver_onInit() + { + if (!this.MutationObserver) + return; + this.observer = new this.MutationObserver((function(aMutations, aObserver) { + this.onMutation(aMutations, aObserver); + }).bind(this)); + this.observer.observe(this.box, { + childList : true, + attributes : true, + subtree : true, + attributeFilter : [ + 'hidden', + 'collapsed', + 'moz-collapsed', // Used in full screen mode + 'disablechrome' + ] + }); + }, + onMutation : function BrowserUIShowHideObserver_onMutation(aMutations, aObserver) + { + aMutations.forEach(function(aMutation) { + switch (aMutation.type) + { + case 'childList': + if (aMutation.target == this.box) + this.owner.browser.treeStyleTab.updateFloatingTabbar(TreeStyleTabConstants.kTABBAR_UPDATE_BY_WINDOW_RESIZE); + return; + + case 'attributes': + this.onAttributeModified(aMutation, aObserver); + return; + } + }, this); + }, + + destroy : function BrowserUIShowHideObserver_destroy() + { + if (this.observer) { + this.observer.disconnect(); + delete this.observer; + } + delete this.box; + delete this.owner; + }, + + onAttributeModified : function BrowserUIShowHideObserver_onAttributeModified(aMutation, aObserver) + { + if (this.handlingAttrChange) + return; + + var TST = this.owner.browser.treeStyleTab; + if ( + // I must ignore show/hide of elements managed by TST, + // to avoid infinity loop. + aMutation.target.hasAttribute(TreeStyleTabConstants.kTAB_STRIP_ELEMENT) && + // However, I have to synchronize visibility of the real + // tab bar and the placeholder's one. If they have + // different visibility, then the tab bar is shown or + // hidden by "auto hide tab bar" feature of someone + // (Pale Moon, Tab Mix Plus, etc.) + this.owner.browser.tabContainer.visible != TST.tabStripPlaceHolder.collapsed + ) + return; + + this.handlingAttrChange = true; + + TST.updateFloatingTabbar(TreeStyleTabConstants.kTABBAR_UPDATE_BY_WINDOW_RESIZE); + + var w = this.box.ownerDocument.defaultView; + w.setTimeout((function() { + this.handlingAttrChange = false; + }).bind(this), 10); + } +}; diff --git a/modules/constants.js b/modules/constants.js index 0adb08bf..52317f59 100644 --- a/modules/constants.js +++ b/modules/constants.js @@ -1,206 +1,206 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TreeStyleTabConstants']; - -const TreeStyleTabConstants = Object.freeze({ -/* attributes */ - kID : 'treestyletab-id', - kCHILDREN : 'treestyletab-children', - kPARENT : 'treestyletab-parent', - kANCESTOR : 'treestyletab-ancestors', - kNEST : 'treestyletab-nest', - kINSERT_BEFORE : 'treestyletab-insert-before', - kINSERT_AFTER : 'treestyletab-insert-after', - kCLOSED_SET_ID : 'treestyletab-closed-set-id', - - kID_NEW : 'treestyletab-id-new', - kID_RESTORING : 'treestyletab-id-restoring', - kCHILDREN_RESTORING : 'treestyletab-children-restoring', - - kSUBTREE_COLLAPSED : 'treestyletab-subtree-collapsed', - kSUBTREE_EXPANDED_MANUALLY : 'treestyletab-subtree-expanded-manually', - kCOLLAPSED : 'treestyletab-collapsed', - kCOLLAPSED_DONE : 'treestyletab-collapsed-done', - kCOLLAPSING_PHASE : 'treestyletab-collapsing-phase', - kCOLLAPSING_PHASE_TO_BE_COLLAPSED : 'collapse', - kCOLLAPSING_PHASE_TO_BE_EXPANDED : 'expand', - kALLOW_COLLAPSE : 'treestyletab-allow-subtree-collapse', - kALLOW_STACK : 'treestyletab-stack-collapsed-tabs', - kREMOVED : 'treestyletab-removed', - - kX_OFFSET : 'treestyletab-x-offset', - kY_OFFSET : 'treestyletab-y-offset', - - kTABBAR_POSITION : 'treestyletab-tabbar-position', - kMODE : 'treestyletab-mode', - - kHIDE_NEWTAB : 'treestyletab-hide-newtab-button', - kSTYLE : 'treestyletab-style', - kFIRSTTAB_BORDER : 'treestyletab-firsttab-border', - kFIXED : 'treestyletab-tabbar-fixed', - kRESIZING : 'treestyletab-tabbar-resizing', - kINDENTED : 'treestyletab-tabs-indented', - kMAX_LEVEL : 'treestyletab-max-tree-level', - kPRINT_PREVIEW : 'treestyletab-print-preview', - kANIMATION_ENABLED : 'treestyletab-animation-enabled', - kINVERT_SCROLLBAR : 'treestyletab-invert-scrollbar', - kNARROW_SCROLLBAR : 'treestyletab-narrow-scrollbar', - kFAVICONIZED : 'treestyletab-faviconized', - kBG_NOTIFY_PHASE : 'treestyletab-notifybgtab-phase', - kIGNORE_POPUP_STATE : 'treestyletab-ignore-state', - kDOM_FULLSCREEN_ACTIVATED : 'treestyletab-dom-fullscreen-activated', - - kTAB_INVERTED : 'treestyletab-tab-inverted', - kTAB_CONTENTS_INVERTED : 'treestyletab-tab-contents-inverted', - kCLOSEBOX_INVERTED : 'treestyletab-closebox-inverted', - - kTWISTY_HOVER : 'treestyletab-twisty-hover', - kTWISTY_STYLE : 'treestyletab-twisty-style', - - kDROP_POSITION : 'treestyletab-drop-position', - kDRAG_TYPE_TABBAR : 'application/x-moz-treestyletab-tabbrowser-tabbar', - kDROP_POSITION_UNKNOWN : 'unknown', - kTABBAR_MOVE_FORCE : 'force', - kTABBAR_MOVE_NORMAL : 'normal', - - kTAB_STRIP_ELEMENT : 'treestyletab-tabstrip-element', - -/* classes */ - kTWISTY : 'treestyletab-twisty', - kCOUNTER : 'treestyletab-counter', - kCOUNTER_CONTAINER : 'treestyletab-counter-container', - kCOUNTER_PAREN : 'treestyletab-counter-paren', - kSPLITTER : 'treestyletab-splitter', - kTABBAR_TOGGLER : 'treestyletab-tabbar-toggler', - kTABBAR_PLACEHOLDER : 'treestyletab-tabbar-placeholder', - kTABBAR_TOOLBAR : 'treestyletab-tabbar-toolbar', - kTABBAR_TOOLBAR_READY : 'treestyletab-tabbar-toolbar-ready', - kTABBAR_TOOLBAR_READY_POPUP : 'treestyletab-tabbar-toolbar-ready-popup', - -/* event types, topics */ - kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN : 'nsDOMTreeStyleTabFocusSwitchingKeyDown', - kEVENT_TYPE_TAB_FOCUS_SWITCHING_START : 'nsDOMTreeStyleTabFocusSwitchingStart', - kEVENT_TYPE_TAB_FOCUS_SWITCHING_END : 'nsDOMTreeStyleTabFocusSwitchingEnd', - kTAB_FOCUS_SWITCHING_SCROLL_DOWN : (1 << 0), - kTAB_FOCUS_SWITCHING_SCROLL_UP : (1 << 1), - kTAB_FOCUS_SWITCHING_STAND_BY : (1 << 2), - kTAB_FOCUS_SWITCHING_ONLY_SHIFT_KEY : (1 << 3), - kEVENT_TYPE_SUBTREE_CLOSING : 'nsDOMTreeStyleTabSubtreeClosing', - kEVENT_TYPE_SUBTREE_CLOSED : 'nsDOMTreeStyleTabSubtreeClosed', - kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED : 'nsDOMTreeStyleTabCollapsedStateChange', - kEVENT_TYPE_TABBAR_INITIALIZED : 'nsDOMTreeStyleTabTabbarInitialized', - kEVENT_TYPE_TABBAR_POSITION_CHANGING : 'nsDOMTreeStyleTabTabbarPositionChanging', - kEVENT_TYPE_TABBAR_POSITION_CHANGED : 'nsDOMTreeStyleTabTabbarPositionChanged', - kEVENT_TYPE_TABBAR_STATE_CHANGING : 'nsDOMTreeStyleTabTabbarStateChanging', - kEVENT_TYPE_TABBAR_STATE_CHANGED : 'nsDOMTreeStyleTabTabbarStateChanged', - kEVENT_TYPE_FOCUS_NEXT_TAB : 'nsDOMTreeStyleTabFocusNextTab', - kEVENT_TYPE_ATTACHED : 'nsDOMTreeStyleTabAttached', - kEVENT_TYPE_DETACHED : 'nsDOMTreeStyleTabParted', - - kEVENT_TYPE_PRINT_PREVIEW_ENTERED : 'nsDOMTreeStyleTabPrintPreviewEntered', - kEVENT_TYPE_PRINT_PREVIEW_EXITED : 'nsDOMTreeStyleTabPrintPreviewExited', - kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING : 'nsDOMTreeStyleTabAutoHideStateChanging', - kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE : 'nsDOMTreeStyleTabAutoHideStateChange', - - kTOPIC_INDENT_MODIFIED : 'TreeStyleTab:indentModified', - kTOPIC_COLLAPSE_EXPAND_ALL : 'TreeStyleTab:collapseExpandAllSubtree', - kTOPIC_CHANGE_TREEVIEW_AVAILABILITY : 'TreeStyleTab:changeTreeViewAvailability', - -/* other constant values */ - kFOCUS_ALL : 0, - kFOCUS_VISIBLE : 1, - - kDROP_BEFORE : -1, - kDROP_ON : 0, - kDROP_AFTER : 1, - - kACTION_MOVE : 1 << 0, - kACTION_STAY : 1 << 1, - kACTION_DUPLICATE : 1 << 2, - kACTION_IMPORT : 1 << 3, - kACTION_NEWTAB : 1 << 4, - kACTION_ATTACH : 1 << 10, - kACTION_PART : 1 << 11, - kACTIONS_FOR_SOURCE : (1 << 0) | (1 << 1), - kACTIONS_FOR_DESTINATION : (1 << 2) | (1 << 3), - - kTABBAR_TOP : 1 << 0, - kTABBAR_BOTTOM : 1 << 1, - kTABBAR_LEFT : 1 << 2, - kTABBAR_RIGHT : 1 << 3, - - kTABBAR_HORIZONTAL : (1 << 0) | (1 << 1), - kTABBAR_VERTICAL : (1 << 2) | (1 << 3), - kTABBAR_REGULAR : (1 << 0) | (1 << 2), - kTABBAR_INVERTED : (1 << 3) | (1 << 4), - - kINSERT_FISRT : 0, - kINSERT_LAST : 1, - - kTABBAR_UPDATE_BY_UNKNOWN_REASON : (1 << 0), - kTABBAR_UPDATE_BY_RESET : (1 << 1), - kTABBAR_UPDATE_BY_PREF_CHANGE : (1 << 2), - kTABBAR_UPDATE_BY_APPEARANCE_CHANGE : (1 << 3), - kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR : (1 << 4), - kTABBAR_UPDATE_BY_TABBAR_RESIZE : (1 << 5), - kTABBAR_UPDATE_BY_WINDOW_RESIZE : (1 << 6), - kTABBAR_UPDATE_BY_FULLSCREEN : (1 << 7), - kTABBAR_UPDATE_BY_AUTOHIDE : (1 << 9), - kTABBAR_UPDATE_BY_INITIALIZE : (1 << 10), - kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR : (1 << 11), - kTABBAR_UPDATE_NOW : (1 << 5) | (1 << 6) | (1 << 9) | (1 << 10), - kTABBAR_UPDATE_SYNC_TO_TABBAR : (1 << 0) | (1 << 1) | (1 << 2) | (1 << 5) | (1 << 9), - kTABBAR_UPDATE_SYNC_TO_PLACEHOLDER : (1 << 3) | (1 << 4) | (1 << 6) | (1 << 7) | (1 << 10) | (1 << 11), - - kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD : 3, - kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN : 0, - kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN : 1, - kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN : 4, - kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN : 2, // onTabRemoved only - - kRESTORE_TREE_LEVEL_NONE : 0, - kRESTORE_TREE_ONLY_VISIBLE : 1, - kRESTORE_TREE_ALL : 2, - - kCOUNTER_ROLE_ALL_TABS : 1, - kCOUNTER_ROLE_CONTAINED_TABS : 2, - - MAX_TABBAR_SIZE_RATIO : 0.8, - DEFAULT_SHRUNKEN_WIDTH_RATIO : 0.67, - MIN_TABBAR_WIDTH : 24, - MIN_TABBAR_HEIGHT : 24 -}); +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TreeStyleTabConstants']; + +const TreeStyleTabConstants = Object.freeze({ +/* attributes */ + kID : 'treestyletab-id', + kCHILDREN : 'treestyletab-children', + kPARENT : 'treestyletab-parent', + kANCESTOR : 'treestyletab-ancestors', + kNEST : 'treestyletab-nest', + kINSERT_BEFORE : 'treestyletab-insert-before', + kINSERT_AFTER : 'treestyletab-insert-after', + kCLOSED_SET_ID : 'treestyletab-closed-set-id', + + kID_NEW : 'treestyletab-id-new', + kID_RESTORING : 'treestyletab-id-restoring', + kCHILDREN_RESTORING : 'treestyletab-children-restoring', + + kSUBTREE_COLLAPSED : 'treestyletab-subtree-collapsed', + kSUBTREE_EXPANDED_MANUALLY : 'treestyletab-subtree-expanded-manually', + kCOLLAPSED : 'treestyletab-collapsed', + kCOLLAPSED_DONE : 'treestyletab-collapsed-done', + kCOLLAPSING_PHASE : 'treestyletab-collapsing-phase', + kCOLLAPSING_PHASE_TO_BE_COLLAPSED : 'collapse', + kCOLLAPSING_PHASE_TO_BE_EXPANDED : 'expand', + kALLOW_COLLAPSE : 'treestyletab-allow-subtree-collapse', + kALLOW_STACK : 'treestyletab-stack-collapsed-tabs', + kREMOVED : 'treestyletab-removed', + + kX_OFFSET : 'treestyletab-x-offset', + kY_OFFSET : 'treestyletab-y-offset', + + kTABBAR_POSITION : 'treestyletab-tabbar-position', + kMODE : 'treestyletab-mode', + + kHIDE_NEWTAB : 'treestyletab-hide-newtab-button', + kSTYLE : 'treestyletab-style', + kFIRSTTAB_BORDER : 'treestyletab-firsttab-border', + kFIXED : 'treestyletab-tabbar-fixed', + kRESIZING : 'treestyletab-tabbar-resizing', + kINDENTED : 'treestyletab-tabs-indented', + kMAX_LEVEL : 'treestyletab-max-tree-level', + kPRINT_PREVIEW : 'treestyletab-print-preview', + kANIMATION_ENABLED : 'treestyletab-animation-enabled', + kINVERT_SCROLLBAR : 'treestyletab-invert-scrollbar', + kNARROW_SCROLLBAR : 'treestyletab-narrow-scrollbar', + kFAVICONIZED : 'treestyletab-faviconized', + kBG_NOTIFY_PHASE : 'treestyletab-notifybgtab-phase', + kIGNORE_POPUP_STATE : 'treestyletab-ignore-state', + kDOM_FULLSCREEN_ACTIVATED : 'treestyletab-dom-fullscreen-activated', + + kTAB_INVERTED : 'treestyletab-tab-inverted', + kTAB_CONTENTS_INVERTED : 'treestyletab-tab-contents-inverted', + kCLOSEBOX_INVERTED : 'treestyletab-closebox-inverted', + + kTWISTY_HOVER : 'treestyletab-twisty-hover', + kTWISTY_STYLE : 'treestyletab-twisty-style', + + kDROP_POSITION : 'treestyletab-drop-position', + kDRAG_TYPE_TABBAR : 'application/x-moz-treestyletab-tabbrowser-tabbar', + kDROP_POSITION_UNKNOWN : 'unknown', + kTABBAR_MOVE_FORCE : 'force', + kTABBAR_MOVE_NORMAL : 'normal', + + kTAB_STRIP_ELEMENT : 'treestyletab-tabstrip-element', + +/* classes */ + kTWISTY : 'treestyletab-twisty', + kCOUNTER : 'treestyletab-counter', + kCOUNTER_CONTAINER : 'treestyletab-counter-container', + kCOUNTER_PAREN : 'treestyletab-counter-paren', + kSPLITTER : 'treestyletab-splitter', + kTABBAR_TOGGLER : 'treestyletab-tabbar-toggler', + kTABBAR_PLACEHOLDER : 'treestyletab-tabbar-placeholder', + kTABBAR_TOOLBAR : 'treestyletab-tabbar-toolbar', + kTABBAR_TOOLBAR_READY : 'treestyletab-tabbar-toolbar-ready', + kTABBAR_TOOLBAR_READY_POPUP : 'treestyletab-tabbar-toolbar-ready-popup', + +/* event types, topics */ + kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN : 'nsDOMTreeStyleTabFocusSwitchingKeyDown', + kEVENT_TYPE_TAB_FOCUS_SWITCHING_START : 'nsDOMTreeStyleTabFocusSwitchingStart', + kEVENT_TYPE_TAB_FOCUS_SWITCHING_END : 'nsDOMTreeStyleTabFocusSwitchingEnd', + kTAB_FOCUS_SWITCHING_SCROLL_DOWN : (1 << 0), + kTAB_FOCUS_SWITCHING_SCROLL_UP : (1 << 1), + kTAB_FOCUS_SWITCHING_STAND_BY : (1 << 2), + kTAB_FOCUS_SWITCHING_ONLY_SHIFT_KEY : (1 << 3), + kEVENT_TYPE_SUBTREE_CLOSING : 'nsDOMTreeStyleTabSubtreeClosing', + kEVENT_TYPE_SUBTREE_CLOSED : 'nsDOMTreeStyleTabSubtreeClosed', + kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED : 'nsDOMTreeStyleTabCollapsedStateChange', + kEVENT_TYPE_TABBAR_INITIALIZED : 'nsDOMTreeStyleTabTabbarInitialized', + kEVENT_TYPE_TABBAR_POSITION_CHANGING : 'nsDOMTreeStyleTabTabbarPositionChanging', + kEVENT_TYPE_TABBAR_POSITION_CHANGED : 'nsDOMTreeStyleTabTabbarPositionChanged', + kEVENT_TYPE_TABBAR_STATE_CHANGING : 'nsDOMTreeStyleTabTabbarStateChanging', + kEVENT_TYPE_TABBAR_STATE_CHANGED : 'nsDOMTreeStyleTabTabbarStateChanged', + kEVENT_TYPE_FOCUS_NEXT_TAB : 'nsDOMTreeStyleTabFocusNextTab', + kEVENT_TYPE_ATTACHED : 'nsDOMTreeStyleTabAttached', + kEVENT_TYPE_DETACHED : 'nsDOMTreeStyleTabParted', + + kEVENT_TYPE_PRINT_PREVIEW_ENTERED : 'nsDOMTreeStyleTabPrintPreviewEntered', + kEVENT_TYPE_PRINT_PREVIEW_EXITED : 'nsDOMTreeStyleTabPrintPreviewExited', + kEVENT_TYPE_AUTO_HIDE_STATE_CHANGING : 'nsDOMTreeStyleTabAutoHideStateChanging', + kEVENT_TYPE_AUTO_HIDE_STATE_CHANGE : 'nsDOMTreeStyleTabAutoHideStateChange', + + kTOPIC_INDENT_MODIFIED : 'TreeStyleTab:indentModified', + kTOPIC_COLLAPSE_EXPAND_ALL : 'TreeStyleTab:collapseExpandAllSubtree', + kTOPIC_CHANGE_TREEVIEW_AVAILABILITY : 'TreeStyleTab:changeTreeViewAvailability', + +/* other constant values */ + kFOCUS_ALL : 0, + kFOCUS_VISIBLE : 1, + + kDROP_BEFORE : -1, + kDROP_ON : 0, + kDROP_AFTER : 1, + + kACTION_MOVE : 1 << 0, + kACTION_STAY : 1 << 1, + kACTION_DUPLICATE : 1 << 2, + kACTION_IMPORT : 1 << 3, + kACTION_NEWTAB : 1 << 4, + kACTION_ATTACH : 1 << 10, + kACTION_PART : 1 << 11, + kACTIONS_FOR_SOURCE : (1 << 0) | (1 << 1), + kACTIONS_FOR_DESTINATION : (1 << 2) | (1 << 3), + + kTABBAR_TOP : 1 << 0, + kTABBAR_BOTTOM : 1 << 1, + kTABBAR_LEFT : 1 << 2, + kTABBAR_RIGHT : 1 << 3, + + kTABBAR_HORIZONTAL : (1 << 0) | (1 << 1), + kTABBAR_VERTICAL : (1 << 2) | (1 << 3), + kTABBAR_REGULAR : (1 << 0) | (1 << 2), + kTABBAR_INVERTED : (1 << 3) | (1 << 4), + + kINSERT_FISRT : 0, + kINSERT_LAST : 1, + + kTABBAR_UPDATE_BY_UNKNOWN_REASON : (1 << 0), + kTABBAR_UPDATE_BY_RESET : (1 << 1), + kTABBAR_UPDATE_BY_PREF_CHANGE : (1 << 2), + kTABBAR_UPDATE_BY_APPEARANCE_CHANGE : (1 << 3), + kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR : (1 << 4), + kTABBAR_UPDATE_BY_TABBAR_RESIZE : (1 << 5), + kTABBAR_UPDATE_BY_WINDOW_RESIZE : (1 << 6), + kTABBAR_UPDATE_BY_FULLSCREEN : (1 << 7), + kTABBAR_UPDATE_BY_AUTOHIDE : (1 << 9), + kTABBAR_UPDATE_BY_INITIALIZE : (1 << 10), + kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR : (1 << 11), + kTABBAR_UPDATE_NOW : (1 << 5) | (1 << 6) | (1 << 9) | (1 << 10), + kTABBAR_UPDATE_SYNC_TO_TABBAR : (1 << 0) | (1 << 1) | (1 << 2) | (1 << 5) | (1 << 9), + kTABBAR_UPDATE_SYNC_TO_PLACEHOLDER : (1 << 3) | (1 << 4) | (1 << 6) | (1 << 7) | (1 << 10) | (1 << 11), + + kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD : 3, + kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN : 0, + kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN : 1, + kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN : 4, + kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN : 2, // onTabRemoved only + + kRESTORE_TREE_LEVEL_NONE : 0, + kRESTORE_TREE_ONLY_VISIBLE : 1, + kRESTORE_TREE_ALL : 2, + + kCOUNTER_ROLE_ALL_TABS : 1, + kCOUNTER_ROLE_CONTAINED_TABS : 2, + + MAX_TABBAR_SIZE_RATIO : 0.8, + DEFAULT_SHRUNKEN_WIDTH_RATIO : 0.67, + MIN_TABBAR_WIDTH : 24, + MIN_TABBAR_HEIGHT : 24 +}); diff --git a/modules/fullTooltip.js b/modules/fullTooltip.js index 1dbdd9c7..0707b754 100644 --- a/modules/fullTooltip.js +++ b/modules/fullTooltip.js @@ -1,480 +1,480 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2011-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['FullTooltipManager']; - -const Cc = Components.classes; -const Ci = Components.interfaces; - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); - -Components.utils.import('resource://treestyletab-modules/lib/inherit.jsm'); -Components.utils.import('resource://treestyletab-modules/base.js'); -Components.utils.import('resource://treestyletab-modules/pseudoTreeBuilder.js'); - -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); - -XPCOMUtils.defineLazyServiceGetter(this, 'ScreenManager', - '@mozilla.org/gfx/screenmanager;1', 'nsIScreenManager'); - -function FullTooltipManager(aOwner) -{ - this.init(aOwner); -} -FullTooltipManager.prototype = inherit(TreeStyleTabBase, { - - kTOOLTIP_MODE_DEFAULT : 0, - kTOOLTIP_MODE_COLLAPSED : 1, - kTOOLTIP_MODE_ALWAYS : 2, - - get window() - { - return this.owner.window; - }, - - get document() - { - return this.owner.document; - }, - - get tabTooltip() - { - return this.document.getElementById('tabbrowser-tab-tooltip'); - }, - - get tabFullTooltip() - { - return this.document.getElementById('treestyletab-full-tree-tooltip'); - }, - - - init : function FTM_init(aOwner) - { - this.owner = aOwner; - - this.tabTooltip.addEventListener('popupshowing', this, true); - this.tabTooltip.addEventListener('popuphiding', this, true); - - this.tabFullTooltip.addEventListener('click', this, false); - this.tabFullTooltip.addEventListener(PseudoTreeBuilder.kTAB_LINK_CLICK, this, true); - this.tabFullTooltip.addEventListener('popupshown', this, true); - this.tabFullTooltip.addEventListener('popuphidden', this, true); - }, - - destroy : function FTM_destroy() - { - this.cancel(); - this.stopListenTooltipEvents(); - - this.tabTooltip.removeEventListener('popupshowing', this, true); - this.tabTooltip.removeEventListener('popuphiding', this, true); - - this.tabFullTooltip.removeEventListener('click', this, false); - this.tabFullTooltip.removeEventListener(PseudoTreeBuilder.kTAB_LINK_CLICK, this, true); - this.tabFullTooltip.removeEventListener('popupshown', this, true); - this.tabFullTooltip.removeEventListener('popuphidden', this, true); - - delete this.owner; - }, - - handleEvent : function FTM_handleEvent(aEvent) - { - switch (aEvent.type) - { - case 'click': - return this.onClick(aEvent); - - case PseudoTreeBuilder.kTAB_LINK_CLICK: - return this.onItemClick(aEvent); - - case 'popupshowing': - return this.onDefaultTooltipShowing(aEvent); - - case 'popuphiding': - return this.onDefaultTooltipHiding(aEvent); - - case 'popupshown': - return this.onShown(aEvent); - - case 'popuphidden': - return this.onHidden(aEvent); - - case 'mousemove': - return this.onTooltipMouseMove(aEvent); - - case 'mouseover': - return this.cancelDelayedHide(); - - case 'mouseout': - return this.hideWithDelay(); - - default: - return this.onTooltipEvent(aEvent); - } - }, - - getFullTooltipFromEvent : function FTM_getFullTooltipFromEvent(aEvent) - { - return this.evaluateXPath( - 'ancestor-or-self::xul:tooltip[@id="'+this.tabFullTooltip.id+'"]', - aEvent.target, - Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE - ).singleNodeValue; - }, - - onClick : function FTM_onClick(aEvent) - { - this.tabFullTooltip.hidePopup(); - }, - - onItemClick : function FTM_onItemClick(aEvent) - { - var id = aEvent.detail.id; - if (id) { - let tab = this.getTabById(id, this.owner.browser); - if (tab) { - let event = aEvent.detail.sourceEvent; - if (event.button == 1 || - (event.button == 0 && this.isAccelKeyPressed(event))) - this.owner.browser.removeTab(tab); - else if (event.button != 2) - this.owner.browser.selectedTab = tab; - } - } - this.tabFullTooltip.hidePopup(); - }, - - onDefaultTooltipShowing : function FTM_onDefaultTooltipShowing(aEvent) - { - this.cancel(); - this.handleDefaultTooltip(aEvent); - }, - - onDefaultTooltipHiding : function FTM_onDefaultTooltipHiding(aEvent) - { - this.cancel(); - }, - - onShown : function FTM_onShown(aEvent) - { - this.startListenTooltipEvents(); - - var tooltip = this.tabFullTooltip; - tooltip.setAttribute('popup-shown', true); - - var w = {}, - h = {}; - var box = tooltip.boxObject; - var scrollBoxObject = tooltip.firstChild.scrollBoxObject; - scrollBoxObject.getScrolledSize(w, h); - var currentW = box.width - scrollBoxObject.width + w.value; - var currentH = box.height - scrollBoxObject.height + h.value; - var currentX = box.screenX; - var currentY = box.screenY; - - var currentScreen = Cc['@mozilla.org/gfx/screenmanager;1'] - .getService(Ci.nsIScreenManager) - .screenForRect(box.screenX, box.screenY, box.width, box.height); - var screenLeft = {}, - screenTop = {}, - screenWidth = {}, - screenHeight = {}; - currentScreen.GetRect(screenLeft, screenTop, screenWidth, screenHeight); - - var style = tooltip.style; - style.maxWidth = screenWidth.value+'px'; - style.maxHeight = screenHeight.value+'px'; - style.minWidth = 0; - style.minHeight = 0; - if (currentX + currentW + screenLeft.value >= screenWidth.value) - style.marginLeft = (Math.max(screenLeft.value, screenWidth.value - currentW) - this.window.screenX)+'px'; - if (currentY + currentH + screenTop.value >= screenHeight.value) - style.marginTop = (Math.max(screenTop.value, screenHeight.value - currentH) - this.window.screenY)+'px'; - }, - - onHidden : function FTM_onHidden(aEvent) - { - this.tabFullTooltip.removeAttribute('popup-shown'); - this.stopListenTooltipEvents(); - this.clear(); - }, - - onTooltipMouseMove : function FTM_onTooltipMouseMove(aEvent) - { - if (this.getFullTooltipFromEvent(aEvent)) - this.cancelDelayedHide(); - else - this.hideWithDelay(); - }, - - onTooltipEvent : function FTM_onTooltipEvent(aEvent) - { - if (!this.getFullTooltipFromEvent(aEvent)) - this.hide(); - }, - - startListenTooltipEvents : function FTM_startListenTooltipEvents() - { - if (this.listening) - return; - this.window.addEventListener('DOMMouseScroll', this, true); - this.window.addEventListener('keydown', this, true); - this.window.addEventListener('mousedown', this, true); - this.window.addEventListener('mouseup', this, true); - this.window.addEventListener('dragstart', this, true); - this.window.addEventListener('mousemove', this, true); - this.tabFullTooltip.addEventListener('mouseover', this, false); - this.tabFullTooltip.addEventListener('mouseout', this, false); - this.listening = true; - }, - - stopListenTooltipEvents : function FTM_stopListenTooltipEvents() - { - if (!this.listening) - return; - this.window.removeEventListener('DOMMouseScroll', this, true); - this.window.removeEventListener('keydown', this, true); - this.window.removeEventListener('mousedown', this, true); - this.window.removeEventListener('mouseup', this, true); - this.window.removeEventListener('dragstart', this, true); - this.window.removeEventListener('mousemove', this, true); - this.tabFullTooltip.removeEventListener('mouseover', this, false); - this.tabFullTooltip.removeEventListener('mouseout', this, false); - this.listening = false; - }, - - - handleDefaultTooltip : function FTM_handleDefaultTooltip(aEvent) - { - var tab = this.getTabFromChild(this.document.tooltipNode); - if (!tab || tab.localName != 'tab') - return; - - var label; - var collapsed = this.isSubtreeCollapsed(tab); - var mode = utils.getTreePref('tooltip.mode'); - - var base = parseInt(tab.getAttribute(this.kNEST) || 0); - var descendant = this.getDescendantTabs(tab); - var indentPart = ' '; - var tree = null; - var fullTooltipExtraLabel = ''; - if (mode > this.kTOOLTIP_MODE_DEFAULT && - descendant.length) { - let tabs = [tab].concat(descendant); - let tabsToBeListed = tabs.slice(0, Math.max(1, utils.getTreePref('tooltip.maxCount'))); - tree = tabsToBeListed - .map(function(aTab) { - let label = aTab.getAttribute('label'); - let indent = ''; - let nest = parseInt(aTab.getAttribute(this.kNEST) || 0) - base; - for (let i = 0; i < nest; i++) - { - indent += indentPart; - } - return utils.treeBundle.getFormattedString('tooltip.item.label', [label, indent]); - }, this) - .join('\n'); - if (tabs.length != tabsToBeListed.length) { - tree += '\n'+indentPart+utils.treeBundle.getFormattedString('tooltip.more', [tabs.length-tabsToBeListed.length]); - } - } - - var shouldShowTree = mode != this.kTOOLTIP_MODE_DEFAULT && (collapsed || mode == this.kTOOLTIP_MODE_ALWAYS); - if ('mOverCloseButton' in tab && tab.mOverCloseButton) { - if (descendant.length && - (collapsed || utils.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN)) { - label = tree || tab.getAttribute('label'); - label = label && shouldShowTree ? - utils.treeBundle.getFormattedString('tooltip.closeTree.labeled', [label]) : - utils.treeBundle.getString('tooltip.closeTree') ; - fullTooltipExtraLabel = utils.treeBundle.getFormattedString('tooltip.closeTree.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); - } - } - else if (tab.getAttribute(this.kTWISTY_HOVER) == 'true') { - let key = collapsed ? - 'tooltip.expandSubtree' : - 'tooltip.collapseSubtree' ; - label = tree || tab.getAttribute('label'); - label = label && shouldShowTree ? - utils.treeBundle.getFormattedString(key+'.labeled', [label]) : - utils.treeBundle.getString(key) ; - fullTooltipExtraLabel = utils.treeBundle.getFormattedString(key+'.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); - } - else if (shouldShowTree) { - label = tree; - } - - if (!label) - return; - - aEvent.target.setAttribute('label', label); - aEvent.stopPropagation(); - - if (shouldShowTree) - this.setup(aEvent.target, tab, fullTooltipExtraLabel); - }, - - - /** - * If the window is maximized, screenX and screenY can be out of - * visible screen rect. On the other hand, - * nsIPopupBoxObject#openPopupAtScreen() automatically reposition - * the popup if it is going to be shown out of the visible screen - * rect. As the result, the popup will be repositioned unexpectedly - * if I use the raw screenX and screenY. - * https://github.com/piroor/treestyletab/issues/302 - * To prevent such a result, I have to calculate valid base position - * for the popup. - */ - get windowBasePosition() { - var screen = ScreenManager.screenForRect( - this.window.screenX, - this.window.screenY, - this.window.outerWidth, - this.window.outerHeight - ); - var screenMinX = {}, - screenMinY = {}, - screenMaxX = {}, - screenMaxY = {}; - screen.GetAvailRect(screenMinX, screenMinY, screenMaxX, screenMaxY); - - return { - x: Math.max(this.window.screenX, screenMinX.value), - y: Math.max(this.window.screenY, screenMinY.value) - }; - }, - - setup : function FTM_setup(aBaseTooltip, aTab, aExtraLabels) - { - this.cancel(); - - var delay = utils.getTreePref('tooltip.fullTooltipDelay'); - if (delay < 0) - return; - - this._fullTooltipTimer = this.window.setTimeout(function(aSelf) { - var basePosition = aSelf.windowBasePosition; - var box = aBaseTooltip.boxObject; - var x = box.screenX - basePosition.x; - var y = box.screenY - basePosition.y; - var w = box.width; - var h = box.height; - aBaseTooltip.hidePopup(); - - aSelf.fill(aTab, aExtraLabels); - - var tooltip = aSelf.tabFullTooltip; - let (style = tooltip.style) { - style.marginLeft = x+'px'; - style.marginTop = y+'px'; - style.maxWidth = style.minWidth = w+'px'; - style.maxHeight = style.minHeight = h+'px'; - } - tooltip.openPopupAtScreen(basePosition.x, basePosition.y, false); - }, Math.max(delay, 0), this); - }, - - cancel : function FTM_destroyFullTooltip() - { - if (this._fullTooltipTimer) { - this.window.clearTimeout(this._fullTooltipTimer); - this._fullTooltipTimer = null; - } - this.hide(); - }, - - hide : function FTM_hide() - { - this.cancelDelayedHide(); - this.tabFullTooltip.hidePopup(); - }, - - - hideWithDelay : function FTM_hideWithDelay() - { - this.cancelDelayedHide(); - this._delayedHideTimer = this.window.setTimeout(function(aSelf) { - aSelf.hide(); - }, 500, this); - }, - - cancelDelayedHide : function FTM_cancelDelayedHide() - { - if (this._delayedHideTimer) { - this.window.clearTimeout(this._delayedHideTimer); - this._delayedHideTimer = null; - } - }, - - - fill : function FTM_fill(aTab, aExtraLabels) - { - this.clear(); - - var tree = PseudoTreeBuilder.build(aTab); - var root = this.document.createElement('arrowscrollbox'); - root.setAttribute('orient', 'vertical'); - root.setAttribute('flex', 1); - - if (aExtraLabels) { - if (typeof aExtraLabels == 'string') - aExtraLabels = [aExtraLabels]; - for (let i = 0, maxi = aExtraLabels.length; i < maxi; i++) - { - let label = aExtraLabels[i]; - label = label.trim(); - if (!label) - continue; - root.appendChild(this.document.createElement('description')) - .appendChild(this.document.createTextNode(label)); - } - } - - root.insertBefore(tree, root.firstChild && root.firstChild.nextSibling); - - this.tabFullTooltip.appendChild(root); - }, - - clear : function FTM_clear() - { - var range = this.document.createRange(); - range.selectNodeContents(this.tabFullTooltip); - range.deleteContents(); - range.detach(); - } -}); +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2011-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['FullTooltipManager']; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); + +Components.utils.import('resource://treestyletab-modules/lib/inherit.jsm'); +Components.utils.import('resource://treestyletab-modules/base.js'); +Components.utils.import('resource://treestyletab-modules/pseudoTreeBuilder.js'); + +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + +XPCOMUtils.defineLazyServiceGetter(this, 'ScreenManager', + '@mozilla.org/gfx/screenmanager;1', 'nsIScreenManager'); + +function FullTooltipManager(aOwner) +{ + this.init(aOwner); +} +FullTooltipManager.prototype = inherit(TreeStyleTabBase, { + + kTOOLTIP_MODE_DEFAULT : 0, + kTOOLTIP_MODE_COLLAPSED : 1, + kTOOLTIP_MODE_ALWAYS : 2, + + get window() + { + return this.owner.window; + }, + + get document() + { + return this.owner.document; + }, + + get tabTooltip() + { + return this.document.getElementById('tabbrowser-tab-tooltip'); + }, + + get tabFullTooltip() + { + return this.document.getElementById('treestyletab-full-tree-tooltip'); + }, + + + init : function FTM_init(aOwner) + { + this.owner = aOwner; + + this.tabTooltip.addEventListener('popupshowing', this, true); + this.tabTooltip.addEventListener('popuphiding', this, true); + + this.tabFullTooltip.addEventListener('click', this, false); + this.tabFullTooltip.addEventListener(PseudoTreeBuilder.kTAB_LINK_CLICK, this, true); + this.tabFullTooltip.addEventListener('popupshown', this, true); + this.tabFullTooltip.addEventListener('popuphidden', this, true); + }, + + destroy : function FTM_destroy() + { + this.cancel(); + this.stopListenTooltipEvents(); + + this.tabTooltip.removeEventListener('popupshowing', this, true); + this.tabTooltip.removeEventListener('popuphiding', this, true); + + this.tabFullTooltip.removeEventListener('click', this, false); + this.tabFullTooltip.removeEventListener(PseudoTreeBuilder.kTAB_LINK_CLICK, this, true); + this.tabFullTooltip.removeEventListener('popupshown', this, true); + this.tabFullTooltip.removeEventListener('popuphidden', this, true); + + delete this.owner; + }, + + handleEvent : function FTM_handleEvent(aEvent) + { + switch (aEvent.type) + { + case 'click': + return this.onClick(aEvent); + + case PseudoTreeBuilder.kTAB_LINK_CLICK: + return this.onItemClick(aEvent); + + case 'popupshowing': + return this.onDefaultTooltipShowing(aEvent); + + case 'popuphiding': + return this.onDefaultTooltipHiding(aEvent); + + case 'popupshown': + return this.onShown(aEvent); + + case 'popuphidden': + return this.onHidden(aEvent); + + case 'mousemove': + return this.onTooltipMouseMove(aEvent); + + case 'mouseover': + return this.cancelDelayedHide(); + + case 'mouseout': + return this.hideWithDelay(); + + default: + return this.onTooltipEvent(aEvent); + } + }, + + getFullTooltipFromEvent : function FTM_getFullTooltipFromEvent(aEvent) + { + return this.evaluateXPath( + 'ancestor-or-self::xul:tooltip[@id="'+this.tabFullTooltip.id+'"]', + aEvent.target, + Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue; + }, + + onClick : function FTM_onClick(aEvent) + { + this.tabFullTooltip.hidePopup(); + }, + + onItemClick : function FTM_onItemClick(aEvent) + { + var id = aEvent.detail.id; + if (id) { + let tab = this.getTabById(id, this.owner.browser); + if (tab) { + let event = aEvent.detail.sourceEvent; + if (event.button == 1 || + (event.button == 0 && this.isAccelKeyPressed(event))) + this.owner.browser.removeTab(tab); + else if (event.button != 2) + this.owner.browser.selectedTab = tab; + } + } + this.tabFullTooltip.hidePopup(); + }, + + onDefaultTooltipShowing : function FTM_onDefaultTooltipShowing(aEvent) + { + this.cancel(); + this.handleDefaultTooltip(aEvent); + }, + + onDefaultTooltipHiding : function FTM_onDefaultTooltipHiding(aEvent) + { + this.cancel(); + }, + + onShown : function FTM_onShown(aEvent) + { + this.startListenTooltipEvents(); + + var tooltip = this.tabFullTooltip; + tooltip.setAttribute('popup-shown', true); + + var w = {}, + h = {}; + var box = tooltip.boxObject; + var scrollBoxObject = tooltip.firstChild.scrollBoxObject; + scrollBoxObject.getScrolledSize(w, h); + var currentW = box.width - scrollBoxObject.width + w.value; + var currentH = box.height - scrollBoxObject.height + h.value; + var currentX = box.screenX; + var currentY = box.screenY; + + var currentScreen = Cc['@mozilla.org/gfx/screenmanager;1'] + .getService(Ci.nsIScreenManager) + .screenForRect(box.screenX, box.screenY, box.width, box.height); + var screenLeft = {}, + screenTop = {}, + screenWidth = {}, + screenHeight = {}; + currentScreen.GetRect(screenLeft, screenTop, screenWidth, screenHeight); + + var style = tooltip.style; + style.maxWidth = screenWidth.value+'px'; + style.maxHeight = screenHeight.value+'px'; + style.minWidth = 0; + style.minHeight = 0; + if (currentX + currentW + screenLeft.value >= screenWidth.value) + style.marginLeft = (Math.max(screenLeft.value, screenWidth.value - currentW) - this.window.screenX)+'px'; + if (currentY + currentH + screenTop.value >= screenHeight.value) + style.marginTop = (Math.max(screenTop.value, screenHeight.value - currentH) - this.window.screenY)+'px'; + }, + + onHidden : function FTM_onHidden(aEvent) + { + this.tabFullTooltip.removeAttribute('popup-shown'); + this.stopListenTooltipEvents(); + this.clear(); + }, + + onTooltipMouseMove : function FTM_onTooltipMouseMove(aEvent) + { + if (this.getFullTooltipFromEvent(aEvent)) + this.cancelDelayedHide(); + else + this.hideWithDelay(); + }, + + onTooltipEvent : function FTM_onTooltipEvent(aEvent) + { + if (!this.getFullTooltipFromEvent(aEvent)) + this.hide(); + }, + + startListenTooltipEvents : function FTM_startListenTooltipEvents() + { + if (this.listening) + return; + this.window.addEventListener('DOMMouseScroll', this, true); + this.window.addEventListener('keydown', this, true); + this.window.addEventListener('mousedown', this, true); + this.window.addEventListener('mouseup', this, true); + this.window.addEventListener('dragstart', this, true); + this.window.addEventListener('mousemove', this, true); + this.tabFullTooltip.addEventListener('mouseover', this, false); + this.tabFullTooltip.addEventListener('mouseout', this, false); + this.listening = true; + }, + + stopListenTooltipEvents : function FTM_stopListenTooltipEvents() + { + if (!this.listening) + return; + this.window.removeEventListener('DOMMouseScroll', this, true); + this.window.removeEventListener('keydown', this, true); + this.window.removeEventListener('mousedown', this, true); + this.window.removeEventListener('mouseup', this, true); + this.window.removeEventListener('dragstart', this, true); + this.window.removeEventListener('mousemove', this, true); + this.tabFullTooltip.removeEventListener('mouseover', this, false); + this.tabFullTooltip.removeEventListener('mouseout', this, false); + this.listening = false; + }, + + + handleDefaultTooltip : function FTM_handleDefaultTooltip(aEvent) + { + var tab = this.getTabFromChild(this.document.tooltipNode); + if (!tab || tab.localName != 'tab') + return; + + var label; + var collapsed = this.isSubtreeCollapsed(tab); + var mode = utils.getTreePref('tooltip.mode'); + + var base = parseInt(tab.getAttribute(this.kNEST) || 0); + var descendant = this.getDescendantTabs(tab); + var indentPart = ' '; + var tree = null; + var fullTooltipExtraLabel = ''; + if (mode > this.kTOOLTIP_MODE_DEFAULT && + descendant.length) { + let tabs = [tab].concat(descendant); + let tabsToBeListed = tabs.slice(0, Math.max(1, utils.getTreePref('tooltip.maxCount'))); + tree = tabsToBeListed + .map(function(aTab) { + let label = aTab.getAttribute('label'); + let indent = ''; + let nest = parseInt(aTab.getAttribute(this.kNEST) || 0) - base; + for (let i = 0; i < nest; i++) + { + indent += indentPart; + } + return utils.treeBundle.getFormattedString('tooltip.item.label', [label, indent]); + }, this) + .join('\n'); + if (tabs.length != tabsToBeListed.length) { + tree += '\n'+indentPart+utils.treeBundle.getFormattedString('tooltip.more', [tabs.length-tabsToBeListed.length]); + } + } + + var shouldShowTree = mode != this.kTOOLTIP_MODE_DEFAULT && (collapsed || mode == this.kTOOLTIP_MODE_ALWAYS); + if ('mOverCloseButton' in tab && tab.mOverCloseButton) { + if (descendant.length && + (collapsed || utils.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN)) { + label = tree || tab.getAttribute('label'); + label = label && shouldShowTree ? + utils.treeBundle.getFormattedString('tooltip.closeTree.labeled', [label]) : + utils.treeBundle.getString('tooltip.closeTree') ; + fullTooltipExtraLabel = utils.treeBundle.getFormattedString('tooltip.closeTree.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); + } + } + else if (tab.getAttribute(this.kTWISTY_HOVER) == 'true') { + let key = collapsed ? + 'tooltip.expandSubtree' : + 'tooltip.collapseSubtree' ; + label = tree || tab.getAttribute('label'); + label = label && shouldShowTree ? + utils.treeBundle.getFormattedString(key+'.labeled', [label]) : + utils.treeBundle.getString(key) ; + fullTooltipExtraLabel = utils.treeBundle.getFormattedString(key+'.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); + } + else if (shouldShowTree) { + label = tree; + } + + if (!label) + return; + + aEvent.target.setAttribute('label', label); + aEvent.stopPropagation(); + + if (shouldShowTree) + this.setup(aEvent.target, tab, fullTooltipExtraLabel); + }, + + + /** + * If the window is maximized, screenX and screenY can be out of + * visible screen rect. On the other hand, + * nsIPopupBoxObject#openPopupAtScreen() automatically reposition + * the popup if it is going to be shown out of the visible screen + * rect. As the result, the popup will be repositioned unexpectedly + * if I use the raw screenX and screenY. + * https://github.com/piroor/treestyletab/issues/302 + * To prevent such a result, I have to calculate valid base position + * for the popup. + */ + get windowBasePosition() { + var screen = ScreenManager.screenForRect( + this.window.screenX, + this.window.screenY, + this.window.outerWidth, + this.window.outerHeight + ); + var screenMinX = {}, + screenMinY = {}, + screenMaxX = {}, + screenMaxY = {}; + screen.GetAvailRect(screenMinX, screenMinY, screenMaxX, screenMaxY); + + return { + x: Math.max(this.window.screenX, screenMinX.value), + y: Math.max(this.window.screenY, screenMinY.value) + }; + }, + + setup : function FTM_setup(aBaseTooltip, aTab, aExtraLabels) + { + this.cancel(); + + var delay = utils.getTreePref('tooltip.fullTooltipDelay'); + if (delay < 0) + return; + + this._fullTooltipTimer = this.window.setTimeout(function(aSelf) { + var basePosition = aSelf.windowBasePosition; + var box = aBaseTooltip.boxObject; + var x = box.screenX - basePosition.x; + var y = box.screenY - basePosition.y; + var w = box.width; + var h = box.height; + aBaseTooltip.hidePopup(); + + aSelf.fill(aTab, aExtraLabels); + + var tooltip = aSelf.tabFullTooltip; + let (style = tooltip.style) { + style.marginLeft = x+'px'; + style.marginTop = y+'px'; + style.maxWidth = style.minWidth = w+'px'; + style.maxHeight = style.minHeight = h+'px'; + } + tooltip.openPopupAtScreen(basePosition.x, basePosition.y, false); + }, Math.max(delay, 0), this); + }, + + cancel : function FTM_destroyFullTooltip() + { + if (this._fullTooltipTimer) { + this.window.clearTimeout(this._fullTooltipTimer); + this._fullTooltipTimer = null; + } + this.hide(); + }, + + hide : function FTM_hide() + { + this.cancelDelayedHide(); + this.tabFullTooltip.hidePopup(); + }, + + + hideWithDelay : function FTM_hideWithDelay() + { + this.cancelDelayedHide(); + this._delayedHideTimer = this.window.setTimeout(function(aSelf) { + aSelf.hide(); + }, 500, this); + }, + + cancelDelayedHide : function FTM_cancelDelayedHide() + { + if (this._delayedHideTimer) { + this.window.clearTimeout(this._delayedHideTimer); + this._delayedHideTimer = null; + } + }, + + + fill : function FTM_fill(aTab, aExtraLabels) + { + this.clear(); + + var tree = PseudoTreeBuilder.build(aTab); + var root = this.document.createElement('arrowscrollbox'); + root.setAttribute('orient', 'vertical'); + root.setAttribute('flex', 1); + + if (aExtraLabels) { + if (typeof aExtraLabels == 'string') + aExtraLabels = [aExtraLabels]; + for (let i = 0, maxi = aExtraLabels.length; i < maxi; i++) + { + let label = aExtraLabels[i]; + label = label.trim(); + if (!label) + continue; + root.appendChild(this.document.createElement('description')) + .appendChild(this.document.createTextNode(label)); + } + } + + root.insertBefore(tree, root.firstChild && root.firstChild.nextSibling); + + this.tabFullTooltip.appendChild(root); + }, + + clear : function FTM_clear() + { + var range = this.document.createRange(); + range.selectNodeContents(this.tabFullTooltip); + range.deleteContents(); + range.detach(); + } +}); diff --git a/modules/fullscreenObserver.js b/modules/fullscreenObserver.js index f49a032b..368eb87d 100644 --- a/modules/fullscreenObserver.js +++ b/modules/fullscreenObserver.js @@ -1,109 +1,109 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2013 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['FullscreenObserver']; - -Components.utils.import('resource://treestyletab-modules/utils.js'); - -function FullscreenObserver(aWindow) { - this.window = aWindow; - this.init(); -} -FullscreenObserver.prototype = { - get MutationObserver() - { - var w = this.window; - return w.MutationObserver || w.MozMutationObserver; - }, - - init : function FullscreenObserver_onInit() - { - if (!this.MutationObserver) - return; - this.observer = new this.MutationObserver((function(aMutations, aObserver) { - this.onMutation(aMutations, aObserver); - }).bind(this)); - this.observer.observe(this.window.document.documentElement, { - attributes : true, - attributeFilter : ['sizemode'] - }); - - this.onSizeModeChange(); - }, - - destroy : function FullscreenObserver_destroy() - { - if (this.observer) { - this.observer.disconnect(); - delete this.observer; - } - delete this.window; - }, - - onMutation : function FullscreenObserver_onMutation(aMutations, aObserver) - { - this.window.setTimeout((function() { - this.onSizeModeChange(); - }).bind(this), 10); - }, - - onSizeModeChange : function FullscreenObserver_onSizeModeChange() - { - var w = this.window; - var d = w.document; - if (d.documentElement.getAttribute('sizemode') != 'fullscreen') - return; - - if ( - !w.FullScreen.useLionFullScreen && // see https://github.com/piroor/treestyletab/issues/645 - TreeStyleTabUtils.prefs.getPref('browser.fullscreen.autohide') // see https://github.com/piroor/treestyletab/issues/717 - ) { - let toolbox = w.gNavToolbox; - toolbox.style.marginTop = -toolbox.getBoundingClientRect().height + 'px'; - } - - var windowControls = d.getElementById('window-controls'); - var navigationToolbar = d.getElementById('nav-bar'); - if (!windowControls || - !navigationToolbar || - windowControls.parentNode == navigationToolbar || - (w.gBrowser.treeStyleTab.position == 'top' && w.gBrowser.treeStyleTab.fixed)) - return; - - // the location bar is flex=1, so we should not apply it. - // windowControls.setAttribute('flex', '1'); - navigationToolbar.appendChild(windowControls); - } -}; +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2013 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['FullscreenObserver']; + +Components.utils.import('resource://treestyletab-modules/utils.js'); + +function FullscreenObserver(aWindow) { + this.window = aWindow; + this.init(); +} +FullscreenObserver.prototype = { + get MutationObserver() + { + var w = this.window; + return w.MutationObserver || w.MozMutationObserver; + }, + + init : function FullscreenObserver_onInit() + { + if (!this.MutationObserver) + return; + this.observer = new this.MutationObserver((function(aMutations, aObserver) { + this.onMutation(aMutations, aObserver); + }).bind(this)); + this.observer.observe(this.window.document.documentElement, { + attributes : true, + attributeFilter : ['sizemode'] + }); + + this.onSizeModeChange(); + }, + + destroy : function FullscreenObserver_destroy() + { + if (this.observer) { + this.observer.disconnect(); + delete this.observer; + } + delete this.window; + }, + + onMutation : function FullscreenObserver_onMutation(aMutations, aObserver) + { + this.window.setTimeout((function() { + this.onSizeModeChange(); + }).bind(this), 10); + }, + + onSizeModeChange : function FullscreenObserver_onSizeModeChange() + { + var w = this.window; + var d = w.document; + if (d.documentElement.getAttribute('sizemode') != 'fullscreen') + return; + + if ( + !w.FullScreen.useLionFullScreen && // see https://github.com/piroor/treestyletab/issues/645 + TreeStyleTabUtils.prefs.getPref('browser.fullscreen.autohide') // see https://github.com/piroor/treestyletab/issues/717 + ) { + let toolbox = w.gNavToolbox; + toolbox.style.marginTop = -toolbox.getBoundingClientRect().height + 'px'; + } + + var windowControls = d.getElementById('window-controls'); + var navigationToolbar = d.getElementById('nav-bar'); + if (!windowControls || + !navigationToolbar || + windowControls.parentNode == navigationToolbar || + (w.gBrowser.treeStyleTab.position == 'top' && w.gBrowser.treeStyleTab.fixed)) + return; + + // the location bar is flex=1, so we should not apply it. + // windowControls.setAttribute('flex', '1'); + navigationToolbar.appendChild(windowControls); + } +}; diff --git a/modules/pseudoTreeBuilder.js b/modules/pseudoTreeBuilder.js index 89571c18..4e3b4e3f 100644 --- a/modules/pseudoTreeBuilder.js +++ b/modules/pseudoTreeBuilder.js @@ -1,142 +1,142 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2011-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['PseudoTreeBuilder']; - -const Cc = Components.classes; -const Ci = Components.interfaces; - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabBase', 'resource://treestyletab-modules/base.js'); - -var PseudoTreeBuilder = { - - kFAVICON : 'treestyletab-pseudo-tree-favicon', - kROOTITEM : 'treestyletab-pseudo-tree-root-item', - kTREEITEM : 'treestyletab-pseudo-tree-item', - kTREEROW : 'treestyletab-pseudo-tree-row', - kTREECHILDREN : 'treestyletab-pseudo-tree-children', - - kTAB_LINK_CLICK : 'nsDOMTSTPseudoTreeItemClick', - - build : function TB_build(aTab) - { - if (!aTab) - return null; - - var tree = this.createTabItem(aTab); - - var row = tree.querySelector("."+this.kTREEROW); - if (!row) - return; - - row.className += " "+this.kROOTITEM; - - tree.setAttribute('onclick', - ('var doc = event.target.ownerDocument;\n' + - 'var label = doc.evaluate(\n' + - ' "ancestor-or-self::*[local-name()=\'label\' and contains(@class, \'text-link\')][1]",\n' + - ' event.target,\n' + - ' null,\n' + - ' Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,\n' + - ' null\n' + - ' ).singleNodeValue;\n' + - 'if (label) {\n' + - ' var customEvent = new doc.defaultView.CustomEvent(%EVENT_TYPE%, {\n' + - ' bubbles : true,\n' + - ' cancelable : true,\n' + - ' detail : {\n' + - ' id : label.getAttribute("tab-id"),\n' + - ' sourceEvent : event\n' + - ' }\n' + - ' });\n' + - ' event.target.dispatchEvent(customEvent);\n' + - '}').replace('%EVENT_TYPE%', this.kTAB_LINK_CLICK.quote())); - - return tree; - }, - - createTabItem : function TB_createTabItem(aTab) - { - var doc = aTab.ownerDocument; - var w = doc.defaultView; - - var item = doc.createElement('hbox'); - item.setAttribute('class', this.kTREEROW); - - var favicon = item.appendChild(doc.createElement('image')); - favicon.setAttribute('src', aTab.getAttribute('image') || 'chrome://mozapps/skin/places/defaultFavicon.png'); - favicon.setAttribute('class', this.kFAVICON); - - var label = item.appendChild(doc.createElement('label')); - label.setAttribute('value', aTab.label); - var tooltip = aTab.label; - var uri = aTab.linkedBrowser.currentURI.spec; - if (w.isBlankPageURL ? !w.isBlankPageURL(uri) : (uri != 'about:blank')) tooltip += '\n' + uri; - label.setAttribute('tooltiptext', tooltip); - label.setAttribute('class', 'text-link '+this.kTREEITEM); - label.setAttribute('tab-id', TreeStyleTabBase.getTabValue(aTab, TreeStyleTabBase.kID)); - - var children = this.createTabChildren(aTab); - if (children) { - let container = doc.createElement('vbox'); - container.appendChild(item); - container.appendChild(children); - return container; - } - else { - return item; - } - }, - - createTabChildren : function TB_createTabChildren(aTab) - { - var doc = aTab.ownerDocument; - - var children = TreeStyleTabBase.getChildTabs(aTab); - if (!children.length) - return null; - - var container = doc.createElement('vbox'); - for (let i = 0, maxi = children.length; i < maxi; i++) - { - container.appendChild(this.createTabItem(children[i])); - } - container.setAttribute('class', this.kTREECHILDREN); - return container; - } -}; +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2011-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['PseudoTreeBuilder']; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabBase', 'resource://treestyletab-modules/base.js'); + +var PseudoTreeBuilder = { + + kFAVICON : 'treestyletab-pseudo-tree-favicon', + kROOTITEM : 'treestyletab-pseudo-tree-root-item', + kTREEITEM : 'treestyletab-pseudo-tree-item', + kTREEROW : 'treestyletab-pseudo-tree-row', + kTREECHILDREN : 'treestyletab-pseudo-tree-children', + + kTAB_LINK_CLICK : 'nsDOMTSTPseudoTreeItemClick', + + build : function TB_build(aTab) + { + if (!aTab) + return null; + + var tree = this.createTabItem(aTab); + + var row = tree.querySelector("."+this.kTREEROW); + if (!row) + return; + + row.className += " "+this.kROOTITEM; + + tree.setAttribute('onclick', + ('var doc = event.target.ownerDocument;\n' + + 'var label = doc.evaluate(\n' + + ' "ancestor-or-self::*[local-name()=\'label\' and contains(@class, \'text-link\')][1]",\n' + + ' event.target,\n' + + ' null,\n' + + ' Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,\n' + + ' null\n' + + ' ).singleNodeValue;\n' + + 'if (label) {\n' + + ' var customEvent = new doc.defaultView.CustomEvent(%EVENT_TYPE%, {\n' + + ' bubbles : true,\n' + + ' cancelable : true,\n' + + ' detail : {\n' + + ' id : label.getAttribute("tab-id"),\n' + + ' sourceEvent : event\n' + + ' }\n' + + ' });\n' + + ' event.target.dispatchEvent(customEvent);\n' + + '}').replace('%EVENT_TYPE%', this.kTAB_LINK_CLICK.quote())); + + return tree; + }, + + createTabItem : function TB_createTabItem(aTab) + { + var doc = aTab.ownerDocument; + var w = doc.defaultView; + + var item = doc.createElement('hbox'); + item.setAttribute('class', this.kTREEROW); + + var favicon = item.appendChild(doc.createElement('image')); + favicon.setAttribute('src', aTab.getAttribute('image') || 'chrome://mozapps/skin/places/defaultFavicon.png'); + favicon.setAttribute('class', this.kFAVICON); + + var label = item.appendChild(doc.createElement('label')); + label.setAttribute('value', aTab.label); + var tooltip = aTab.label; + var uri = aTab.linkedBrowser.currentURI.spec; + if (w.isBlankPageURL ? !w.isBlankPageURL(uri) : (uri != 'about:blank')) tooltip += '\n' + uri; + label.setAttribute('tooltiptext', tooltip); + label.setAttribute('class', 'text-link '+this.kTREEITEM); + label.setAttribute('tab-id', TreeStyleTabBase.getTabValue(aTab, TreeStyleTabBase.kID)); + + var children = this.createTabChildren(aTab); + if (children) { + let container = doc.createElement('vbox'); + container.appendChild(item); + container.appendChild(children); + return container; + } + else { + return item; + } + }, + + createTabChildren : function TB_createTabChildren(aTab) + { + var doc = aTab.ownerDocument; + + var children = TreeStyleTabBase.getChildTabs(aTab); + if (!children.length) + return null; + + var container = doc.createElement('vbox'); + for (let i = 0, maxi = children.length; i < maxi; i++) + { + container.appendChild(this.createTabItem(children[i])); + } + container.setAttribute('class', this.kTREECHILDREN); + return container; + } +}; diff --git a/modules/tabbarDNDObserver.js b/modules/tabbarDNDObserver.js index 178c294b..d6f6f61f 100644 --- a/modules/tabbarDNDObserver.js +++ b/modules/tabbarDNDObserver.js @@ -1,1324 +1,1324 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Infocatcher - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TabbarDNDObserver']; - -const DEBUG = false; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://gre/modules/Services.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); - -const TAB_DROP_TYPE = 'application/x-moz-tabbrowser-tab'; - -const SSS = Cc['@mozilla.org/content/style-sheet-service;1'] - .getService(Ci.nsIStyleSheetService); -const SecMan = Cc['@mozilla.org/scriptsecuritymanager;1'] - .getService(Ci.nsIScriptSecurityManager); - -function TabbarDNDObserver(aTabBrowser) -{ - this.init(aTabBrowser); -} - -TabbarDNDObserver.prototype = { - - readyToStartTabbarDrag : function TabbarDND_readyToStartTabbarDrag() - { - var sheet = this.treeStyleTab.makeURIFromSpec('chrome://treestyletab/content/hide-embed.css'); - if (!SSS.sheetRegistered(sheet, SSS.AGENT_SHEET)) - SSS.loadAndRegisterSheet(sheet, SSS.AGENT_SHEET); - }, - - readyToEndTabbarDrag : function TabbarDND_readyToEndTabbarDrag() - { - var sheet = this.treeStyleTab.makeURIFromSpec('chrome://treestyletab/content/hide-embed.css'); - if (SSS.sheetRegistered(sheet, SSS.AGENT_SHEET)) - SSS.unregisterSheet(sheet, SSS.AGENT_SHEET); - }, - - canDragTabbar : function TabbarDND_canDragTabbar(aEvent) - { - var sv = this.treeStyleTab; - - if ( - sv.evaluateXPath( - 'ancestor-or-self::*[' + - 'contains(" scrollbar popup menupopup panel tooltip ", concat(" ", local-name(), " ")) or' + - '(local-name()="toolbarbutton" and @type="menu")' + - ']', - aEvent.originalTarget, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue || - sv.isToolbarCustomizing - ) - return false; - - var tab = sv.getTabFromEvent(aEvent); - var tabbar = sv.getTabbarFromEvent(aEvent); - var canDrag = ( - (tab ? aEvent.shiftKey : tabbar ) && - ( - aEvent.shiftKey || - sv.browser.getAttribute(sv.kFIXED) != 'true' - ) - ); - - if (canDrag && !aEvent.shiftKey) { - let insensitiveArea = utils.getTreePref('tabbar.fixed.insensitiveArea'); - let box = tabbar.boxObject; - switch (sv.position) - { - case 'right': - if (aEvent.screenX < box.screenX + insensitiveArea) - canDrag = false; - break; - - case 'left': - if (aEvent.screenX > box.screenX + box.width - insensitiveArea) - canDrag = false; - break; - - default: - case 'top': - if (aEvent.screenY > box.screenY + box.height - insensitiveArea) - canDrag = false; - break; - - case 'bottom': - if (aEvent.screenY < box.screenY + insensitiveArea) - canDrag = false; - break; - } - } - - return canDrag; - }, - - canDrop : function TabbarDND_canDrop(aEvent) - { - var sv = this.treeStyleTab; - var tooltip = sv.tabStrip.firstChild; - if (tooltip && - tooltip.localName == 'tooltip' && - tooltip.popupBoxObject.popupState != 'closed') - tooltip.hidePopup(); - - var dropAction = this.getDropAction(aEvent); - if ('dataTransfer' in aEvent) { - var dt = aEvent.dataTransfer; - if (dropAction.action & sv.kACTION_NEWTAB) { - dt.effectAllowed = dt.dropEffect = ( - !dropAction.source ? 'link' : - sv.isCopyAction(aEvent) ? 'copy' : - 'move' - ); - } - } - return dropAction.canDrop; - }, - - canDropTab : function TabbarDND_canDropTab(aEvent) - { -try{ - var sv = this.treeStyleTab; - var b = this.browser; - - var session = sv.currentDragSession; - var node = session.sourceNode; - var tab = sv.getTabFromChild(node); - if (!node || - !tab || - tab.parentNode != b.mTabContainer) - return true; - - tab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent); - if (sv.isCollapsed(tab)) - return false; - - var info = this.getDropAction(aEvent, session); - return info.canDrop; -} -catch(e) { - dump('TabbarDND::canDrop\n'+e+'\n'); - return false; -} - }, - - getDropAction : function TabbarDND_getDropAction(aEvent, aDragSession) - { - var sv = this.treeStyleTab; - var b = this.browser; - - if (!aDragSession) - aDragSession = sv.currentDragSession; - - var tab = aDragSession ? sv.getTabFromChild(aDragSession.sourceNode) : null ; - sv.ensureTabInitialized(tab); - - var info = this.getDropActionInternal(aEvent, tab); - info.canDrop = true; - info.source = tab; - if (tab) { - var isCopy = sv.isCopyAction(aEvent); - if (isCopy && 'duplicateTab' in b) { - info.action |= sv.kACTION_DUPLICATE; - } - if ( - !isCopy && - sv.getTabBrowserFromChild(tab) != b && - ( - ('duplicateTab' in b) - ) - ) { - info.action |= sv.kACTION_IMPORT; - } - - if (info.action & sv.kACTIONS_FOR_DESTINATION) { - if (info.action & sv.kACTION_MOVE) info.action ^= sv.kACTION_MOVE; - if (info.action & sv.kACTION_STAY) info.action ^= sv.kACTION_STAY; - } - - if (info.action & sv.kACTION_ATTACH) { - if (info.parent == tab) { - info.canDrop = false; - } - else { - var orig = tab; - tab = info.target; - while (tab = sv.getParentTab(tab)) - { - if (tab != orig) continue; - info.canDrop = false; - break; - } - } - } - } - - var isInverted = sv.isVertical ? false : b.ownerDocument.defaultView.getComputedStyle(b.parentNode, null).direction == 'rtl'; - if ( - info.target && - ( - info.target.hidden || - ( - sv.isCollapsed(info.target) && - info.position != (isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ) - ) - ) - ) - info.canDrop = false; - - return info; - }, - - getDropActionInternal : function TabbarDND_getDropActionInternal(aEvent, aSourceTab) - { - if (DEBUG) dump('getDropActionInternal: start\n'); - var sv = this.treeStyleTab; - var b = this.browser; - var d = this.document; - - var tab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent) || aEvent.target; - var tabs = sv.getTabs(b); - var firstTab = sv.getFirstNormalTab(b) || tabs[0]; - var lastTabIndex = tabs.length - 1; - var isInverted = sv.isVertical ? false : b.ownerDocument.defaultView.getComputedStyle(b.parentNode, null).direction == 'rtl'; - var info = { - target : null, - position : null, - action : null, - parent : null, - insertBefore : null, - event : aEvent - }; - - let draggedTab = aEvent.dataTransfer && aEvent.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0); - if (draggedTab && draggedTab._dragData) { - // "draggedTab._dragData.animDropIndex" means the actual "_tPos" - // of the drop target, so we have to use the array of all - // (including hidden) tabs here. - let tabs = sv.getAllTabs(b); - let sameTypeUndraggedTabs = tabs.filter(function(aTab) { - return !aTab._dragData && aTab.pinned == draggedTab.pinned; - }); - tab = draggedTab.pinned ? - // pinned tabs cannot be dropped to another pinned tab, so - // we can use the drop position calculated by "_animateTabMove()". - tabs[draggedTab._dragData.animDropIndex] : - // otherwise, we have to find "drop target" tab from screen coordinates. - sv.getTabFromCoordinates(aEvent, sameTypeUndraggedTabs) || - tabs[draggedTab._dragData.animDropIndex] ; - } - - var isTabMoveFromOtherWindow = aSourceTab && aSourceTab.ownerDocument != d; - var isNewTabAction = !aSourceTab || aSourceTab.ownerDocument != d; - - if (!tab || tab.localName != 'tab') { - if (DEBUG) dump(' not on a tab\n'); - let action = isTabMoveFromOtherWindow ? sv.kACTION_STAY : (sv.kACTION_MOVE | sv.kACTION_PART) ; - if (isNewTabAction) action |= sv.kACTION_NEWTAB; - if (aEvent[sv.screenPositionProp] < sv.getTabActualScreenPosition(firstTab)) { - if (DEBUG) dump(' above the first tab\n'); - info.target = info.parent = info.insertBefore = firstTab; - info.position = isInverted ? sv.kDROP_AFTER : sv.kDROP_BEFORE ; - info.action = action; - return info; - } - else if (aEvent[sv.screenPositionProp] > sv.getTabActualScreenPosition(tabs[lastTabIndex]) + tabs[lastTabIndex].boxObject[sv.sizeProp]) { - if (DEBUG) dump(' below the last tab\n'); - info.target = info.parent = tabs[lastTabIndex]; - info.position = isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ; - info.action = action; - return info; - } - else { - let index = b.getNewIndex ? - b.getNewIndex(aEvent) : - b.tabContainer._getDropIndex(aEvent) ; - if (DEBUG) dump(' on the tab '+index+'\n'); - index = Math.min(index, lastTabIndex); - info.target = tab = tabs[index]; - if (index == tabs[lastTabIndex]._tPos) { - if (index > 0) - info.target = tab = tabs[index - 1]; - info.position = sv.kDROP_AFTER; - if (DEBUG) dump(' => after the last tab\n'); - } else if (index == firstTab._tPos) { - if (index < lastTabIndex - 1) - info.target = tab = tabs[index + 1]; - info.position = sv.kDROP_BEFORE; - if (DEBUG) dump(' => before the first tab\n'); - } - if (DEBUG) dump(' info.target = ' + info.target._tPos + '\n'); - } - } - else { - if (DEBUG) dump(' on the tab '+tab._tPos+'\n'); - sv.ensureTabInitialized(tab); - info.target = tab; - } - - /** - * Basically, tabs should have three areas for dropping of items: - * [start][center][end], but, pinned tabs couldn't have its tree. - * So, if a tab is dragged and the target tab is pinned, then, we - * have to ignore the [center] area. - */ - var pinned = tab.getAttribute('pinned') == 'true'; - var dropAreasCount = (aSourceTab && pinned) ? 2 : 3 ; - var screenPositionProp = sv.isVertical && pinned ? sv.invertedScreenPositionProp : sv.screenPositionProp ; - var sizeProp = sv.isVertical && pinned ? sv.invertedSizeProp : sv.sizeProp ; - var orient = pinned ? 'horizontal' : null ; - var boxPos = sv.getTabActualScreenPosition(tab, orient); - var boxUnit = Math.round(tab.boxObject[sizeProp] / dropAreasCount); - var eventPosition = aEvent[screenPositionProp]; -// if (this.window['piro.sakura.ne.jp'].tabsDragUtils -// .canAnimateDraggedTabs(aEvent)) { -// eventPosition = Math.round(sv.getTabActualScreenPosition(draggedTab) + (tab.boxObject[sizeProp] / 2)) -// } - if (eventPosition < boxPos + boxUnit) { - info.position = isInverted ? sv.kDROP_AFTER : sv.kDROP_BEFORE ; - } - else if (dropAreasCount == 2 || eventPosition > boxPos + boxUnit + boxUnit) { - info.position = isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ; - } - else { - info.position = sv.kDROP_ON; - } - - switch (info.position) - { - case sv.kDROP_ON: - if (DEBUG) dump(' position = on the tab\n'); - var visible = sv.getNextVisibleTab(tab); - info.action = sv.kACTION_STAY | sv.kACTION_ATTACH; - info.parent = tab; - info.insertBefore = utils.getTreePref('insertNewChildAt') == sv.kINSERT_FISRT ? - (sv.getFirstChildTab(tab) || visible) : - (sv.getNextSiblingTab(tab) || sv.getNextTab(sv.getLastDescendantTab(tab) || tab)); - if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); - break; - - case sv.kDROP_BEFORE: - if (DEBUG) dump(' position = before the tab\n'); -/* - <= detach from parent, and move - [TARGET ] - - [ ] - <= attach to the parent of the target, and move - [TARGET ] - - [ ] - <= attach to the parent of the target, and move - [TARGET ] - - [ ] - <= attach to the parent of the target (previous tab), and move - [TARGET] -*/ - var prevTab = sv.getPreviousVisibleTab(tab); - if (!prevTab) { - // allow to drop pinned tab to beside of another pinned tab - if (aSourceTab && aSourceTab.getAttribute('pinned') == 'true') { - info.action = sv.kACTION_MOVE; - info.insertBefore = tab; - } - else { - info.action = sv.kACTION_MOVE | sv.kACTION_PART; - info.insertBefore = firstTab; - } - } - else { - var prevLevel = Number(prevTab.getAttribute(sv.kNEST)); - var targetNest = Number(tab.getAttribute(sv.kNEST)); - info.parent = (prevLevel < targetNest) ? prevTab : sv.getParentTab(tab) ; - info.action = sv.kACTION_MOVE | (info.parent ? sv.kACTION_ATTACH : sv.kACTION_PART ); - info.insertBefore = tab; - } - if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); - break; - - case sv.kDROP_AFTER: - if (DEBUG) dump(' position = after the tab\n'); -/* - [TARGET ] - <= if the target has a parent, attach to it and and move - - [TARGET] - <= attach to the parent of the target, and move - [ ] - - [TARGET ] - <= attach to the parent of the target, and move - [ ] - - [TARGET ] - <= attach to the target, and move - [ ] -*/ - var nextTab = sv.getNextVisibleTab(tab); - if (!nextTab) { - info.action = sv.kACTION_MOVE | sv.kACTION_ATTACH; - info.parent = sv.getParentTab(tab); - } - else { - var targetNest = Number(tab.getAttribute(sv.kNEST)); - var nextLevel = Number(nextTab.getAttribute(sv.kNEST)); - info.parent = (targetNest < nextLevel) ? tab : sv.getParentTab(tab) ; - info.action = sv.kACTION_MOVE | (info.parent ? sv.kACTION_ATTACH : sv.kACTION_PART ); - info.insertBefore = nextTab; -/* - [TARGET ] - <= attach dragged tab to the parent of the target as its next sibling - [DRAGGED] -*/ - if (aSourceTab == nextTab) { - info.action = sv.kACTION_MOVE | sv.kACTION_ATTACH; - info.parent = sv.getParentTab(tab); - info.insertBefore = sv.getNextSiblingTab(tab); - let ancestor = info.parent; - while (ancestor && !info.insertBefore) { - info.insertBefore = sv.getNextSiblingTab(ancestor); - ancestor = sv.getParentTab(ancestor); - } - } - } - if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); - break; - } - - if (isNewTabAction) info.action |= sv.kACTION_NEWTAB; - - return info; - }, - - performDrop : function TabbarDND_performDrop(aInfo, aDraggedTab) - { - if (DEBUG) dump('performDrop: start\n'); - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - var tabsInfo = this.getDraggedTabsInfoFromOneTab(aDraggedTab, aInfo); - if (!tabsInfo.draggedTab) { - if (DEBUG) dump(' => no dragged tab\n'); - return false; - } - - var sourceWindow = aDraggedTab.ownerDocument.defaultView; - var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aDraggedTab); - var sourceService = sourceBrowser.treeStyleTab; - - aDraggedTab = tabsInfo.draggedTab; - var draggedTabs = tabsInfo.draggedTabs; - var draggedRoots = sourceService.collectRootTabs(tabsInfo.draggedTabs); - - - var targetBrowser = b; - var tabs = sv.getTabs(targetBrowser); - - var draggedWholeTree = [].concat(draggedRoots); - for (let i = 0, maxi = draggedRoots.length; i < maxi; i++) - { - let root = draggedRoots[i]; - let tabs = sourceService.getDescendantTabs(root); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - if (draggedWholeTree.indexOf(tab) < 0) - draggedWholeTree.push(tab); - } - } - - var selectedTabs = draggedTabs.filter(function(aTab) { - return aTab.getAttribute('multiselected') == 'true'; - }); - if (draggedWholeTree.length != selectedTabs.length && - selectedTabs.length) { - draggedTabs = draggedRoots = selectedTabs; - if (aInfo.action & sv.kACTIONS_FOR_SOURCE) - sourceService.detachTabs(selectedTabs); - } - - while (aInfo.insertBefore && draggedWholeTree.indexOf(aInfo.insertBefore) > -1) - { - aInfo.insertBefore = sv.getNextTab(aInfo.insertBefore); - } - - if (aInfo.action & sv.kACTIONS_FOR_SOURCE) { - if (aInfo.action & sv.kACTION_PART) { - this.detachTabsOnDrop(draggedRoots); - } - else if (aInfo.action & sv.kACTION_ATTACH) { - this.attachTabsOnDrop(draggedRoots, aInfo.parent); - } - // otherwise, just moved. - - if ( // if this move will cause no change... - sourceBrowser == targetBrowser && - sourceService.getNextVisibleTab(draggedTabs[draggedTabs.length-1]) == aInfo.insertBefore - ) { - if (DEBUG) dump(' => no change\n'); - // then, do nothing - return true; - } - } - - var treeStructure = sourceService.getTreeStructureFromTabs(draggedTabs); - - var newTabs = sv.moveTabsInternal(draggedTabs, { - duplicate : aInfo.action & sv.kACTION_DUPLICATE, - insertBefore : aInfo.insertBefore - }); - - if (newTabs.length && aInfo.action & sv.kACTION_ATTACH) - this.attachTabsOnDrop( - newTabs.filter(function(aTab, aIndex) { - return treeStructure[aIndex] == -1; - }), - aInfo.parent - ); - - return true; - }, - - getDraggedTabsInfoFromOneTab : function TabbarDND_getDraggedTabsInfoFromOneTab(aTab, aInfo) - { - aInfo = aInfo || {}; - if (aInfo.draggedTabsInfo) - return aInfo.draggedTabsInfo; - - var sv = this.treeStyleTab; - var sourceWindow = aTab.ownerDocument.defaultView; - var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aTab); - var sourceService = sourceBrowser.treeStyleTab; - - aTab = sourceService.getTabFromChild(aTab); - if (!aTab || !aTab.parentNode) // ignore removed tabs! - return { - draggedTab : null, - draggedTabs : [], - isMultipleMove : false - }; - - var draggedTabs = sourceWindow['piro.sakura.ne.jp'].tabsDragUtils.getSelectedTabs(aTab || sourceBrowser || aInfo.event); - var isMultipleMove = false; - - if (draggedTabs.length > 1) { - isMultipleMove = true; - } - else if (aInfo.action & sv.kACTIONS_FOR_DESTINATION) { - draggedTabs = [aTab].concat(sourceService.getDescendantTabs(aTab)); - } - - return { - draggedTab : aTab, - draggedTabs : draggedTabs, - isMultipleMove : isMultipleMove - }; - }, - - attachTabsOnDrop : function TabbarDND_attachTabsOnDrop(aTabs, aParent) - { - var b = aTabs[0].ownerDocument.defaultView.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); - var sv = b.treeStyleTab; - - b.movingSelectedTabs = true; // Multiple Tab Handler - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (!tab.parentNode) continue; // ignore removed tabs - if (aParent) - sv.attachTabTo(tab, aParent); - else - sv.detachTab(tab); - sv.collapseExpandTab(tab, false); - } - b.movingSelectedTabs = false; // Multiple Tab Handler - }, - - detachTabsOnDrop : function TabbarDND_detachTabsOnDrop(aTabs) - { - var b = aTabs[0].ownerDocument.defaultView.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); - var sv = b.treeStyleTab; - - b.movingSelectedTabs = true; // Multiple Tab Handler - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - if (!tab.parentNode) continue; // ignore removed tabs - sv.detachTab(tab); - sv.collapseExpandTab(tab, false); - } - b.movingSelectedTabs = false; // Multiple Tab Handler - }, - - clearDropPosition : function TabbarDND_clearDropPosition(aOnFinish) - { - var sv = this.treeStyleTab; - var b = this.browser; - - b.visibleTabs.forEach(function(aTab) { - if (aTab.hasAttribute(sv.kDROP_POSITION)) - aTab.removeAttribute(sv.kDROP_POSITION) - - if (aOnFinish) { - aTab.style.transform = ''; - if ('__treestyletab__opacityBeforeDragged' in aTab) { - aTab.style.opacity = aTab.__treestyletab__opacityBeforeDragged; - delete aTab.__treestyletab__opacityBeforeDragged; - } - } - }); - - if (aOnFinish) - this.browser.mTabContainer.removeAttribute('movingtab') - }, - - isDraggingAllTabs : function TabbarDND_isDraggingAllTabs(aTab, aTabs) - { - var sv = this.treeStyleTab; - var b = this.browser; - - var actionInfo = { - action : sv.kACTIONS_FOR_DESTINATION | sv.kACTION_IMPORT - }; - var tabsInfo = this.getDraggedTabsInfoFromOneTab(aTab, actionInfo); - return tabsInfo.draggedTabs.length == (aTabs || sv.getAllTabs(b)).length; - }, - - isDraggingAllCurrentTabs : function TabbarDND_isDraggingAllCurrentTabs(aTab) - { - return this.isDraggingAllTabs(aTab, this.treeStyleTab.getTabs(this.treeStyleTab.browser)); - }, - - handleEvent : function TabbarDND_handleEvent(aEvent) - { - // ignore drag and drop while toolbar customization - if (this.treeStyleTab.isToolbarCustomizing) - return; - - switch (aEvent.type) - { - case 'dragstart': return this.onDragStart(aEvent); - case 'dragenter': return this.onDragEnter(aEvent); - case 'dragleave': return this.onDragLeave(aEvent); - case 'dragend': return this.onDragEnd(aEvent); - case 'dragover': return this.onDragOver(aEvent); - case 'drop': return this.onDrop(aEvent); - } - }, - - onDragStart : function TabbarDND_onDragStart(aEvent) - { - if (this.canDragTabbar(aEvent)) - return this.onTabbarDragStart(aEvent); - - var tab = this.treeStyleTab.getTabFromEvent(aEvent); - if (tab) - return this.onTabDragStart(aEvent, tab); - }, - - onTabDragStart : function TabbarDND_onTabDragStart(aEvent, aTab) - { - var sv = this.treeStyleTab; - var w = this.window; - var actionInfo = { - action : sv.kACTIONS_FOR_DESTINATION | sv.kACTION_MOVE, - event : aEvent - }; - var tabsInfo = this.getDraggedTabsInfoFromOneTab(aTab, actionInfo); - if ( - tabsInfo.draggedTabs.length <= 1 || - Array.some(tabsInfo.draggedTabs, function(aTab) { - return aTab.getAttribute('multiselected') == 'true'; // if multiselected, it should be handled by other addons (like Multiple Tab Handler) - }) - ) - return; - - w['piro.sakura.ne.jp'].tabsDragUtils.startTabsDrag(aEvent, tabsInfo.draggedTabs); - }, - - onTabbarDragStart : function TabbarDND_onTabbarDragStart(aEvent) - { - var sv = this.treeStyleTab; - var dt = aEvent.dataTransfer; - dt.mozSetDataAt( - sv.kDRAG_TYPE_TABBAR, - aEvent.shiftKey ? - sv.kTABBAR_MOVE_FORCE : - sv.kTABBAR_MOVE_NORMAL, - 0 - ); - dt.mozCursor = 'move'; -// var tabbar = sv.browser.mTabContainer; -// var box = tabbar.boxObject; -// dt.setDragImage( -// tabbar, -// aEvent.screenX - box.screenX, -// aEvent.screenY - box.screenY -// ); - // no feedback image, because it's annoying... - dt.setDragImage(new this.window.Image(), 0, 0); - aEvent.stopPropagation(); - this.readyToStartTabbarDrag(); - }, - - onDragEnter : function TabbarDND_onDragEnter(aEvent) - { - var sv = this.treeStyleTab; - var w = this.window; - - var dt = aEvent.dataTransfer; - if (!this.canDrop(aEvent)) { - dt.effectAllowed = dt.dropEffect = 'none'; - return; - } - - var tab = aEvent.target; - if (tab.localName != 'tab' || - !utils.getTreePref('autoExpand.enabled')) - return; - - w.clearTimeout(this.mAutoExpandTimer); - w.clearTimeout(this.mAutoExpandTimerNext); - - var sourceNode = dt.getData(sv.kDRAG_TYPE_TABBAR+'-node'); - if (aEvent.target == sourceNode) - return; - - var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); - this.mAutoExpandTimerNext = w.setTimeout(function(aSelf, aTarget, aDragged) { - aSelf.mAutoExpandTimerNext = null; - aSelf.mAutoExpandTimer = w.setTimeout( - function() { - let tab = sv.getTabById(aTarget); - if (tab && - sv.shouldTabAutoExpanded(tab) && - tab.getAttribute(sv.kDROP_POSITION) == 'self') { - let draggedTab = aDragged && sv.getTabById(aDragged); - if (utils.getTreePref('autoExpand.intelligently')) { - sv.collapseExpandTreesIntelligentlyFor(tab); - if (draggedTab) - aSelf.updateDragData(draggedTab); - } - else { - if (aSelf.mAutoExpandedTabs.indexOf(aTarget) < 0) - aSelf.mAutoExpandedTabs.push(aTarget); - sv.collapseExpandSubtree(tab, false); - if (draggedTab) - aSelf.updateDragData(draggedTab); - } - } - }, - utils.getTreePref('autoExpand.delay') - ); - }, 0, this, tab.getAttribute(sv.kID), draggedTab && draggedTab.getAttribute(sv.kID)); - - tab = null; - }, - updateDragData : function TabbarDND_updateDragData(aTab) - { - if (!aTab || !aTab._dragData) return; - var sv = this.treeStyleTab; - var data = aTab._dragData; - var offsetX = sv.getXOffsetOfTab(aTab); - var offsetY = sv.getYOffsetOfTab(aTab); - if ('offsetX' in data) data.offsetX += offsetX; - if ('screenX' in data) data.screenX += offsetX; - if ('offsetY' in data) data.offsetY += offsetY; - if ('screenY' in data) data.screenY += offsetY; - }, - - onDragLeave : function TabbarDND_onDragLeave(aEvent) - { - this.clearDropPosition(); - - this.window.clearTimeout(this.mAutoExpandTimer); - this.mAutoExpandTimer = null; - }, - - onDragEnd : function TabbarDND_onDragEnd(aEvent) - { - var sv = this.treeStyleTab; - var dt = aEvent.dataTransfer; - if (dt.getData(sv.kDRAG_TYPE_TABBAR)) - this.onTabbarDragEnd(aEvent); - else - this.onTabDragEnd(aEvent); - }, - - onTabDragEnd : function TabbarDND_onTabDragEnd(aEvent) - { - var sv = this.treeStyleTab; - var b = this.browser; - var d = this.document; - var w = this.window; - - var tabbar = b.mTabContainer; - var strip = sv.tabStrip; - var dt = aEvent.dataTransfer; - - this.clearDropPosition(true); - this.collapseAutoExpandedTabs(); - - if (dt.mozUserCancelled || dt.dropEffect != 'none') - return; - - // prevent handling of this event by the default handler - aEvent.stopPropagation(); - aEvent.preventDefault(); - - var eX = aEvent.screenX; - var eY = aEvent.screenY; - var x, y, w, h; - - // ignore drop on the toolbox - x = w.screenX; - y = w.screenY; - w = w.outerWidth; - h = d.getElementById('navigator-toolbox').boxObject.height; - if (eX > x && eX < x + w && eY > y && eY < y + h) - return; - - // ignore drop near the tab bar - var box = strip.boxObject; - var ignoreArea = Math.max(16, parseInt(sv.getFirstNormalTab(b).boxObject.height / 2)); - x = box.screenX - (sv.isVertical ? ignoreArea : 0 ); - y = box.screenY - ignoreArea; - w = box.width + (sv.isVertical ? ignoreArea + ignoreArea : 0 ); - h = box.height + ignoreArea + ignoreArea; - if (eX > x && eX < x + w && eY > y && eY < y + h) - return; - - var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); - if (this.isDraggingAllCurrentTabs(draggedTab)) - return; - - // Respect the behaviour of "Disable detach and tear off tab" - // https://addons.mozilla.org/firefox/addon/bug489729-disable-detach-and-t/ - if ('bug489729' in this.window && - prefs.getPref('extensions.bug489729.disable_detach_tab')) - return; - - if (aEvent.ctrlKey || aEvent.metaKey) - draggedTab.__treestyletab__toBeDuplicated = true; - - b.replaceTabWithWindow(draggedTab); - }, - - onTabbarDragEnd : function TabbarDND_onTabbarDragEnd(aEvent) - { - var w = this.window; - w.setTimeout(function(aSelf) { - aSelf.readyToEndTabbarDrag(); - aSelf.treeStyleTab.removeTabbrowserAttribute(aSelf.treeStyleTab.kDROP_POSITION); - }, 10, this); - aEvent.stopPropagation(); - aEvent.preventDefault(); - }, - - onDragOver : function TabbarDND_onDragOver(aEvent) - { - if (this.onTabDragOver(aEvent)) { - aEvent.stopPropagation(); - aEvent.preventDefault(); // this is required to override default dragover actions! - } - }, - - onTabDragOver : function TabbarDND_onTabDragOver(aEvent) - { -try{ - var sv = this.treeStyleTab; - var b = this.browser; - var tabbar = b.mTabContainer; - - var session = sv.currentDragSession; - if (sv.isToolbarCustomizing) - return false; - - sv.autoScroll.processAutoScroll(aEvent); - - let draggedTab = aEvent.dataTransfer && aEvent.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0); - let dragOverTab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent) || aEvent.target; - b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils - .processTabsDragging(aEvent, { - canDropOnSelf : !dragOverTab || !dragOverTab.pinned, - isVertical : ( - b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils.isVertical(b.tabContainer) && - ( - (draggedTab && !draggedTab.pinned) || - !utils.getTreePref('pinnedTab.faviconized') - ) - ) - }); - - /** - * We must calculate drop action after tabsDragUtils.processTabsDragging(), - * because the drop position depends on tabs' actual - * positions (which can be changed by animation effects.) - */ - var info = this.getDropAction(aEvent, session); - - var observer = b; - if (b.tabContainer && b.tabContainer._setEffectAllowedForDataTransfer) - observer = b.tabContainer; - - // auto-switch for staying on tabs - if ( - info.position == sv.kDROP_ON && - info.target && - !info.target.selected && - '_dragTime' in observer && '_dragOverDelay' in observer - ) { - let time = observer.mDragTime || observer._dragTime || 0; - let delay = observer.mDragOverDelay || observer._dragOverDelay || 0; - let effects = observer._setEffectAllowedForDataTransfer(aEvent); - if (effects == 'link') { - let now = Date.now(); - if (!time) { - time = now; - if ('mDragTime' in observer) - observer.mDragTime = time; - else - observer._dragTime = time; - } - if (now >= time + delay) - b.selectedTab = info.target; - } - } - - if ( - !info.canDrop || - observer._setEffectAllowedForDataTransfer(aEvent) == 'none' - ) { - aEvent.dataTransfer.effectAllowed = "none"; - this.clearDropPosition(); - return true; - } - - let indicatorTab = info.target; - if (sv.isCollapsed(info.target)) { - let tab = indicatorTab; - while ((tab = sv.getPreviousTab(tab)) && sv.isCollapsed(tab)) {} - if (tab) indicatorTab = tab; - } - - let dropPosition = info.position == sv.kDROP_BEFORE ? 'before' : - info.position == sv.kDROP_AFTER ? 'after' : - 'self'; - if (indicatorTab != draggedTab && - indicatorTab.getAttribute(sv.kDROP_POSITION) != dropPosition) { - this.clearDropPosition(); - indicatorTab.setAttribute(sv.kDROP_POSITION, dropPosition); - if (b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils - .canAnimateDraggedTabs(aEvent)) { - let newOpacity = dropPosition == 'self' ? 0.35 : 0.75 ; // to prevent the dragged tab hides the drop target itself - this.window['piro.sakura.ne.jp'].tabsDragUtils.getDraggedTabs(aEvent).forEach(function(aTab) { - if (!('__treestyletab__opacityBeforeDragged' in aTab)) - aTab.__treestyletab__opacityBeforeDragged = aTab.style.opacity || ''; - aTab.style.opacity = newOpacity; - }); - } - } - - - var indicator = b.mTabDropIndicatorBar || b.tabContainer._tabDropIndicator; - indicator.setAttribute('dragging', (info.position == sv.kDROP_ON || sv.isVertical) ? 'false' : 'true' ); - if (sv.isVertical) - indicator.collapsed = true; - - return (info.position == sv.kDROP_ON || sv.position != 'top') -} -catch(e) { - dump('TabbarDND::onDragOver\n'+e+'\n'); -} - }, - - onDrop : function TabbarDND_onDrop(aEvent) - { - this.onTabDrop(aEvent); - this.collapseAutoExpandedTabs(); - }, - collapseAutoExpandedTabs : function TabbarDND_collapseAutoExpandedTabs() - { - var sv = this.treeStyleTab; - if (this.mAutoExpandedTabs.length) { - if (utils.getTreePref('autoExpand.collapseFinally')) { - for (let i = 0, maxi = this.mAutoExpandedTabs.length; i < maxi; i++) - { - sv.collapseExpandSubtree(sv.getTabById(this.mAutoExpandedTabs[i]), true, true); - } - } - this.mAutoExpandedTabs = []; - } - }, - - onTabDrop : function TSTService_onTabDrop(aEvent) - { - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - - var tabbar = b.mTabContainer; - var dt = aEvent.dataTransfer; - - /** - * We must calculate drop action before clearing "dragging" - * state, because the drop position depends on tabs' actual - * positions (they are applied only while tab dragging.) - */ - var session = sv.currentDragSession; - var dropActionInfo = this.getDropAction(aEvent, session); - - this.clearDropPosition(true); - if (tabbar._tabDropIndicator) - tabbar._tabDropIndicator.collapsed = true; - - var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); - if (dt.dropEffect != 'link' && !draggedTab) { - aEvent.stopPropagation(); - return; - } - - var sourceBrowser = sv.getTabBrowserFromChild(draggedTab); - if (draggedTab && sourceBrowser != b) - sourceBrowser.treeStyleTab.tabbarDNDObserver.clearDropPosition(true); - - if (draggedTab && this.performDrop(dropActionInfo, draggedTab)) { - aEvent.stopPropagation(); - return; - } - - // duplicating of tabs - if ( - draggedTab && - ( - dt.dropEffect == 'copy' || - sourceBrowser != b - ) && - dropActionInfo.position == sv.kDROP_ON - ) { - var beforeTabs = Array.slice(b.mTabContainer.childNodes); - w.setTimeout(function() { - var newTabs = Array.slice(b.mTabContainer.childNodes).filter(function(aTab) { - return beforeTabs.indexOf(aTab) < 0; - }); - if (newTabs.length) - sv.attachTabTo(newTabs[0], dropActionInfo.target); - }, 0); - return; - } - - if (!draggedTab) - this.handleLinksOrBookmarks(aEvent, dropActionInfo); - }, - handleLinksOrBookmarks : function TabbarDND_handleLinksOrBookmarks(aEvent, aDropActionInfo) - { - aEvent.stopPropagation(); - - var uris = this.retrieveURLsFromDataTransfer(aEvent.dataTransfer); - uris.forEach(function(aURI) { - if (aURI.indexOf(this.BOOKMARK_FOLDER) != 0) - this.securityCheck(aURI, aEvent); - }, this); - - var sv = this.treeStyleTab; - var b = this.browser; - var w = this.window; - var self = this; - - let bgLoad = prefs.getPref('browser.tabs.loadInBackground'); - if (aEvent.shiftKey) bgLoad = !bgLoad; - - let tab = sv.getTabFromEvent(aEvent); - if ( - !tab || - aEvent.dataTransfer.dropEffect == 'copy' || - uris.length > 1 || - uris[0].indexOf(this.BOOKMARK_FOLDER) == 0 - ) { - uris.reverse().forEach(function(aURI) { - if (aURI.indexOf(this.BOOKMARK_FOLDER) == 0) { - let newTabs = sv.getNewTabsWithOperation(function() { - var data = aURI.replace(self.BOOKMARK_FOLDER, ''); - data = JSON.parse(data); - w.PlacesUIUtils._openTabset(data.children, { type : 'drop' }, w, data.title); - }, b); - aDropActionInfo.draggedTabsInfo = { - draggedTabs : newTabs, - draggedTab : newTabs[0], - isMultipleMove : newTabs.length > 1 - }; - this.performDrop(aDropActionInfo, newTabs[0]); - } - else { - aURI = utils.getShortcutOrURI(w, aURI); - this.performDrop(aDropActionInfo, b.loadOneTab(aURI, { inBackground: bgLoad })); - } - }, this); - } - else { - let locked = ( - tab.getAttribute('locked') == 'true' || // Tab Mix Plus and others - tab.getAttribute('isPageLocked') == 'true' // Super Tab Mode - ); - let loadDroppedLinkToNewChildTab = aDropActionInfo.position != sv.kDROP_ON || locked; - if (!loadDroppedLinkToNewChildTab && - aDropActionInfo.position == sv.kDROP_ON) - loadDroppedLinkToNewChildTab = sv.dropLinksOnTabBehavior() == sv.kDROPLINK_NEWTAB; - - try { - let uri = utils.getShortcutOrURI(w, uris[0]); - if (loadDroppedLinkToNewChildTab || locked) { - this.performDrop(aDropActionInfo, b.loadOneTab(uri, { inBackground: bgLoad })); - } - else { - tab.linkedBrowser.loadURI(uri); - if (!bgLoad) - b.selectedTab = tab; - } - } - catch(e) { - } - } - }, - securityCheck : function TabbarDND_securityCheck(aURI, aEvent) - { - // See dragDropSecurityCheck() in chrome://global/content/nsDragAndDrop.js - let session = this.treeStyleTab.currentDragSession; - if (!session) { //TODO: use some fake nodePrincipal? - aEvent.stopPropagation(); - throw 'Drop of ' + aURI + ' denied: no drag session.'; - } - let normalizedURI; - try { - normalizedURI = this.treeStyleTab.makeURIFromSpec(aURI); - } - catch(e) { - } - if (!normalizedURI) - return; - let sourceDoc = session.sourceDocument; - let sourceURI = sourceDoc ? sourceDoc.documentURI : 'file:///' ; - let principal = sourceDoc ? - sourceDoc.nodePrincipal : - SecMan.getSimpleCodebasePrincipal(Services.io.newURI(sourceURI, null, null)) ; - try { - if (principal) - SecMan.checkLoadURIStrWithPrincipal(principal, normalizedURI.spec, Ci.nsIScriptSecurityManager.STANDARD); - else - SecMan.checkLoadURIStr(sourceURI, normalizedURI.spec, Ci.nsIScriptSecurityManager.STANDARD); - } - catch(e) { - aEvent.stopPropagation(); - throw 'Drop of ' + aURI + ' denied.'; - } - }, - - retrieveURLsFromDataTransfer : function TSTService_retrieveURLsFromDataTransfer(aDataTransfer) - { - var urls = []; - var types = [ - 'text/x-moz-place', - 'text/uri-list', - 'text/x-moz-text-internal', - 'text/x-moz-url', - 'text/plain', - 'application/x-moz-file' - ]; - for (let i = 0; i < types.length; i++) { - let dataType = types[i]; - for (let i = 0, maxi = aDataTransfer.mozItemCount; i < maxi; i++) - { - let urlData = aDataTransfer.mozGetDataAt(dataType, i); - if (urlData) { - urls = urls.concat(this.retrieveURLsFromData(urlData, dataType)); - } - } - if (urls.length) - break; - } - return urls.filter(function(aURI) { - return aURI && - aURI.length && - aURI.indexOf(this.BOOKMARK_FOLDER) == 0 || - ( - aURI.indexOf(' ', 0) == -1 && - !/^\s*(javascript|data):/.test(aURI) - ); - }, this); - }, - BOOKMARK_FOLDER: 'x-moz-place:', - retrieveURLsFromData : function TSTService_retrieveURLsFromData(aData, aType) - { - switch (aType) - { - case 'text/x-moz-place': - let (uri = JSON.parse(aData).uri) { - if (uri) - return uri; - else - return this.BOOKMARK_FOLDER+aData; - } - - case 'text/uri-list': - return aData.replace(/\r/g, '\n') - .replace(/^\#.+$/gim, '') - .replace(/\n\n+/g, '\n') - .split('\n'); - - case 'text/unicode': - case 'text/plain': - case 'text/x-moz-text-internal': - return [aData.trim()]; - - case 'text/x-moz-url': - return [((aData instanceof Ci.nsISupportsString) ? aData.toString() : aData) - .split('\n')[0]]; - - case 'application/x-moz-file': - let fileHandler = Services.io.getProtocolHandler('file') - .QueryInterface(Ci.nsIFileProtocolHandler); - return [fileHandler.getURLSpecFromFile(aData)]; - } - return []; - }, - - init : function TabbarDND_init(aTabBrowser) - { - this.browser = aTabBrowser; - this.document = aTabBrowser.ownerDocument; - this.window = this.document.defaultView; - this.treeStyleTab = aTabBrowser.treeStyleTab; - - this.mAutoExpandTimer = null; - this.mAutoExpandTimerNext = null; - this.mAutoExpandedTabs = []; - this.startListenEvents(); - }, - - startListenEvents : function TabbarDND_startListenEvents() - { - var target = this.treeStyleTab.ownerToolbar || this.treeStyleTab.tabStrip; - target.addEventListener('dragstart', this, true); - target.addEventListener('dragover', this, true); - target.addEventListener('dragenter', this, false); - target.addEventListener('dragleave', this, false); - target.addEventListener('dragend', this, true); - target.addEventListener('drop', this, true); - }, - - destroy : function TabbarDND_destroy() - { - this.endListenEvents(); - - delete this.treeStyleTab; - delete this.browser; - delete this.document; - delete this.window; - }, - - endListenEvents : function TabbarDND_endListenEvents() - { - var target = this.treeStyleTab.ownerToolbar || this.treeStyleTab.tabStrip; - target.removeEventListener('dragstart', this, true); - target.removeEventListener('dragover', this, true); - target.removeEventListener('dragenter', this, false); - target.removeEventListener('dragleave', this, false); - target.removeEventListener('dragend', this, true); - target.removeEventListener('drop', this, true); - } - -}; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Infocatcher + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TabbarDNDObserver']; + +const DEBUG = false; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); + +const TAB_DROP_TYPE = 'application/x-moz-tabbrowser-tab'; + +const SSS = Cc['@mozilla.org/content/style-sheet-service;1'] + .getService(Ci.nsIStyleSheetService); +const SecMan = Cc['@mozilla.org/scriptsecuritymanager;1'] + .getService(Ci.nsIScriptSecurityManager); + +function TabbarDNDObserver(aTabBrowser) +{ + this.init(aTabBrowser); +} + +TabbarDNDObserver.prototype = { + + readyToStartTabbarDrag : function TabbarDND_readyToStartTabbarDrag() + { + var sheet = this.treeStyleTab.makeURIFromSpec('chrome://treestyletab/content/hide-embed.css'); + if (!SSS.sheetRegistered(sheet, SSS.AGENT_SHEET)) + SSS.loadAndRegisterSheet(sheet, SSS.AGENT_SHEET); + }, + + readyToEndTabbarDrag : function TabbarDND_readyToEndTabbarDrag() + { + var sheet = this.treeStyleTab.makeURIFromSpec('chrome://treestyletab/content/hide-embed.css'); + if (SSS.sheetRegistered(sheet, SSS.AGENT_SHEET)) + SSS.unregisterSheet(sheet, SSS.AGENT_SHEET); + }, + + canDragTabbar : function TabbarDND_canDragTabbar(aEvent) + { + var sv = this.treeStyleTab; + + if ( + sv.evaluateXPath( + 'ancestor-or-self::*[' + + 'contains(" scrollbar popup menupopup panel tooltip ", concat(" ", local-name(), " ")) or' + + '(local-name()="toolbarbutton" and @type="menu")' + + ']', + aEvent.originalTarget, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue || + sv.isToolbarCustomizing + ) + return false; + + var tab = sv.getTabFromEvent(aEvent); + var tabbar = sv.getTabbarFromEvent(aEvent); + var canDrag = ( + (tab ? aEvent.shiftKey : tabbar ) && + ( + aEvent.shiftKey || + sv.browser.getAttribute(sv.kFIXED) != 'true' + ) + ); + + if (canDrag && !aEvent.shiftKey) { + let insensitiveArea = utils.getTreePref('tabbar.fixed.insensitiveArea'); + let box = tabbar.boxObject; + switch (sv.position) + { + case 'right': + if (aEvent.screenX < box.screenX + insensitiveArea) + canDrag = false; + break; + + case 'left': + if (aEvent.screenX > box.screenX + box.width - insensitiveArea) + canDrag = false; + break; + + default: + case 'top': + if (aEvent.screenY > box.screenY + box.height - insensitiveArea) + canDrag = false; + break; + + case 'bottom': + if (aEvent.screenY < box.screenY + insensitiveArea) + canDrag = false; + break; + } + } + + return canDrag; + }, + + canDrop : function TabbarDND_canDrop(aEvent) + { + var sv = this.treeStyleTab; + var tooltip = sv.tabStrip.firstChild; + if (tooltip && + tooltip.localName == 'tooltip' && + tooltip.popupBoxObject.popupState != 'closed') + tooltip.hidePopup(); + + var dropAction = this.getDropAction(aEvent); + if ('dataTransfer' in aEvent) { + var dt = aEvent.dataTransfer; + if (dropAction.action & sv.kACTION_NEWTAB) { + dt.effectAllowed = dt.dropEffect = ( + !dropAction.source ? 'link' : + sv.isCopyAction(aEvent) ? 'copy' : + 'move' + ); + } + } + return dropAction.canDrop; + }, + + canDropTab : function TabbarDND_canDropTab(aEvent) + { +try{ + var sv = this.treeStyleTab; + var b = this.browser; + + var session = sv.currentDragSession; + var node = session.sourceNode; + var tab = sv.getTabFromChild(node); + if (!node || + !tab || + tab.parentNode != b.mTabContainer) + return true; + + tab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent); + if (sv.isCollapsed(tab)) + return false; + + var info = this.getDropAction(aEvent, session); + return info.canDrop; +} +catch(e) { + dump('TabbarDND::canDrop\n'+e+'\n'); + return false; +} + }, + + getDropAction : function TabbarDND_getDropAction(aEvent, aDragSession) + { + var sv = this.treeStyleTab; + var b = this.browser; + + if (!aDragSession) + aDragSession = sv.currentDragSession; + + var tab = aDragSession ? sv.getTabFromChild(aDragSession.sourceNode) : null ; + sv.ensureTabInitialized(tab); + + var info = this.getDropActionInternal(aEvent, tab); + info.canDrop = true; + info.source = tab; + if (tab) { + var isCopy = sv.isCopyAction(aEvent); + if (isCopy && 'duplicateTab' in b) { + info.action |= sv.kACTION_DUPLICATE; + } + if ( + !isCopy && + sv.getTabBrowserFromChild(tab) != b && + ( + ('duplicateTab' in b) + ) + ) { + info.action |= sv.kACTION_IMPORT; + } + + if (info.action & sv.kACTIONS_FOR_DESTINATION) { + if (info.action & sv.kACTION_MOVE) info.action ^= sv.kACTION_MOVE; + if (info.action & sv.kACTION_STAY) info.action ^= sv.kACTION_STAY; + } + + if (info.action & sv.kACTION_ATTACH) { + if (info.parent == tab) { + info.canDrop = false; + } + else { + var orig = tab; + tab = info.target; + while (tab = sv.getParentTab(tab)) + { + if (tab != orig) continue; + info.canDrop = false; + break; + } + } + } + } + + var isInverted = sv.isVertical ? false : b.ownerDocument.defaultView.getComputedStyle(b.parentNode, null).direction == 'rtl'; + if ( + info.target && + ( + info.target.hidden || + ( + sv.isCollapsed(info.target) && + info.position != (isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ) + ) + ) + ) + info.canDrop = false; + + return info; + }, + + getDropActionInternal : function TabbarDND_getDropActionInternal(aEvent, aSourceTab) + { + if (DEBUG) dump('getDropActionInternal: start\n'); + var sv = this.treeStyleTab; + var b = this.browser; + var d = this.document; + + var tab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent) || aEvent.target; + var tabs = sv.getTabs(b); + var firstTab = sv.getFirstNormalTab(b) || tabs[0]; + var lastTabIndex = tabs.length - 1; + var isInverted = sv.isVertical ? false : b.ownerDocument.defaultView.getComputedStyle(b.parentNode, null).direction == 'rtl'; + var info = { + target : null, + position : null, + action : null, + parent : null, + insertBefore : null, + event : aEvent + }; + + let draggedTab = aEvent.dataTransfer && aEvent.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0); + if (draggedTab && draggedTab._dragData) { + // "draggedTab._dragData.animDropIndex" means the actual "_tPos" + // of the drop target, so we have to use the array of all + // (including hidden) tabs here. + let tabs = sv.getAllTabs(b); + let sameTypeUndraggedTabs = tabs.filter(function(aTab) { + return !aTab._dragData && aTab.pinned == draggedTab.pinned; + }); + tab = draggedTab.pinned ? + // pinned tabs cannot be dropped to another pinned tab, so + // we can use the drop position calculated by "_animateTabMove()". + tabs[draggedTab._dragData.animDropIndex] : + // otherwise, we have to find "drop target" tab from screen coordinates. + sv.getTabFromCoordinates(aEvent, sameTypeUndraggedTabs) || + tabs[draggedTab._dragData.animDropIndex] ; + } + + var isTabMoveFromOtherWindow = aSourceTab && aSourceTab.ownerDocument != d; + var isNewTabAction = !aSourceTab || aSourceTab.ownerDocument != d; + + if (!tab || tab.localName != 'tab') { + if (DEBUG) dump(' not on a tab\n'); + let action = isTabMoveFromOtherWindow ? sv.kACTION_STAY : (sv.kACTION_MOVE | sv.kACTION_PART) ; + if (isNewTabAction) action |= sv.kACTION_NEWTAB; + if (aEvent[sv.screenPositionProp] < sv.getTabActualScreenPosition(firstTab)) { + if (DEBUG) dump(' above the first tab\n'); + info.target = info.parent = info.insertBefore = firstTab; + info.position = isInverted ? sv.kDROP_AFTER : sv.kDROP_BEFORE ; + info.action = action; + return info; + } + else if (aEvent[sv.screenPositionProp] > sv.getTabActualScreenPosition(tabs[lastTabIndex]) + tabs[lastTabIndex].boxObject[sv.sizeProp]) { + if (DEBUG) dump(' below the last tab\n'); + info.target = info.parent = tabs[lastTabIndex]; + info.position = isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ; + info.action = action; + return info; + } + else { + let index = b.getNewIndex ? + b.getNewIndex(aEvent) : + b.tabContainer._getDropIndex(aEvent) ; + if (DEBUG) dump(' on the tab '+index+'\n'); + index = Math.min(index, lastTabIndex); + info.target = tab = tabs[index]; + if (index == tabs[lastTabIndex]._tPos) { + if (index > 0) + info.target = tab = tabs[index - 1]; + info.position = sv.kDROP_AFTER; + if (DEBUG) dump(' => after the last tab\n'); + } else if (index == firstTab._tPos) { + if (index < lastTabIndex - 1) + info.target = tab = tabs[index + 1]; + info.position = sv.kDROP_BEFORE; + if (DEBUG) dump(' => before the first tab\n'); + } + if (DEBUG) dump(' info.target = ' + info.target._tPos + '\n'); + } + } + else { + if (DEBUG) dump(' on the tab '+tab._tPos+'\n'); + sv.ensureTabInitialized(tab); + info.target = tab; + } + + /** + * Basically, tabs should have three areas for dropping of items: + * [start][center][end], but, pinned tabs couldn't have its tree. + * So, if a tab is dragged and the target tab is pinned, then, we + * have to ignore the [center] area. + */ + var pinned = tab.getAttribute('pinned') == 'true'; + var dropAreasCount = (aSourceTab && pinned) ? 2 : 3 ; + var screenPositionProp = sv.isVertical && pinned ? sv.invertedScreenPositionProp : sv.screenPositionProp ; + var sizeProp = sv.isVertical && pinned ? sv.invertedSizeProp : sv.sizeProp ; + var orient = pinned ? 'horizontal' : null ; + var boxPos = sv.getTabActualScreenPosition(tab, orient); + var boxUnit = Math.round(tab.boxObject[sizeProp] / dropAreasCount); + var eventPosition = aEvent[screenPositionProp]; +// if (this.window['piro.sakura.ne.jp'].tabsDragUtils +// .canAnimateDraggedTabs(aEvent)) { +// eventPosition = Math.round(sv.getTabActualScreenPosition(draggedTab) + (tab.boxObject[sizeProp] / 2)) +// } + if (eventPosition < boxPos + boxUnit) { + info.position = isInverted ? sv.kDROP_AFTER : sv.kDROP_BEFORE ; + } + else if (dropAreasCount == 2 || eventPosition > boxPos + boxUnit + boxUnit) { + info.position = isInverted ? sv.kDROP_BEFORE : sv.kDROP_AFTER ; + } + else { + info.position = sv.kDROP_ON; + } + + switch (info.position) + { + case sv.kDROP_ON: + if (DEBUG) dump(' position = on the tab\n'); + var visible = sv.getNextVisibleTab(tab); + info.action = sv.kACTION_STAY | sv.kACTION_ATTACH; + info.parent = tab; + info.insertBefore = utils.getTreePref('insertNewChildAt') == sv.kINSERT_FISRT ? + (sv.getFirstChildTab(tab) || visible) : + (sv.getNextSiblingTab(tab) || sv.getNextTab(sv.getLastDescendantTab(tab) || tab)); + if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); + break; + + case sv.kDROP_BEFORE: + if (DEBUG) dump(' position = before the tab\n'); +/* + <= detach from parent, and move + [TARGET ] + + [ ] + <= attach to the parent of the target, and move + [TARGET ] + + [ ] + <= attach to the parent of the target, and move + [TARGET ] + + [ ] + <= attach to the parent of the target (previous tab), and move + [TARGET] +*/ + var prevTab = sv.getPreviousVisibleTab(tab); + if (!prevTab) { + // allow to drop pinned tab to beside of another pinned tab + if (aSourceTab && aSourceTab.getAttribute('pinned') == 'true') { + info.action = sv.kACTION_MOVE; + info.insertBefore = tab; + } + else { + info.action = sv.kACTION_MOVE | sv.kACTION_PART; + info.insertBefore = firstTab; + } + } + else { + var prevLevel = Number(prevTab.getAttribute(sv.kNEST)); + var targetNest = Number(tab.getAttribute(sv.kNEST)); + info.parent = (prevLevel < targetNest) ? prevTab : sv.getParentTab(tab) ; + info.action = sv.kACTION_MOVE | (info.parent ? sv.kACTION_ATTACH : sv.kACTION_PART ); + info.insertBefore = tab; + } + if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); + break; + + case sv.kDROP_AFTER: + if (DEBUG) dump(' position = after the tab\n'); +/* + [TARGET ] + <= if the target has a parent, attach to it and and move + + [TARGET] + <= attach to the parent of the target, and move + [ ] + + [TARGET ] + <= attach to the parent of the target, and move + [ ] + + [TARGET ] + <= attach to the target, and move + [ ] +*/ + var nextTab = sv.getNextVisibleTab(tab); + if (!nextTab) { + info.action = sv.kACTION_MOVE | sv.kACTION_ATTACH; + info.parent = sv.getParentTab(tab); + } + else { + var targetNest = Number(tab.getAttribute(sv.kNEST)); + var nextLevel = Number(nextTab.getAttribute(sv.kNEST)); + info.parent = (targetNest < nextLevel) ? tab : sv.getParentTab(tab) ; + info.action = sv.kACTION_MOVE | (info.parent ? sv.kACTION_ATTACH : sv.kACTION_PART ); + info.insertBefore = nextTab; +/* + [TARGET ] + <= attach dragged tab to the parent of the target as its next sibling + [DRAGGED] +*/ + if (aSourceTab == nextTab) { + info.action = sv.kACTION_MOVE | sv.kACTION_ATTACH; + info.parent = sv.getParentTab(tab); + info.insertBefore = sv.getNextSiblingTab(tab); + let ancestor = info.parent; + while (ancestor && !info.insertBefore) { + info.insertBefore = sv.getNextSiblingTab(ancestor); + ancestor = sv.getParentTab(ancestor); + } + } + } + if (DEBUG && info.insertBefore) dump(' insertBefore = '+info.insertBefore._tPos+'\n'); + break; + } + + if (isNewTabAction) info.action |= sv.kACTION_NEWTAB; + + return info; + }, + + performDrop : function TabbarDND_performDrop(aInfo, aDraggedTab) + { + if (DEBUG) dump('performDrop: start\n'); + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + var tabsInfo = this.getDraggedTabsInfoFromOneTab(aDraggedTab, aInfo); + if (!tabsInfo.draggedTab) { + if (DEBUG) dump(' => no dragged tab\n'); + return false; + } + + var sourceWindow = aDraggedTab.ownerDocument.defaultView; + var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aDraggedTab); + var sourceService = sourceBrowser.treeStyleTab; + + aDraggedTab = tabsInfo.draggedTab; + var draggedTabs = tabsInfo.draggedTabs; + var draggedRoots = sourceService.collectRootTabs(tabsInfo.draggedTabs); + + + var targetBrowser = b; + var tabs = sv.getTabs(targetBrowser); + + var draggedWholeTree = [].concat(draggedRoots); + for (let i = 0, maxi = draggedRoots.length; i < maxi; i++) + { + let root = draggedRoots[i]; + let tabs = sourceService.getDescendantTabs(root); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + if (draggedWholeTree.indexOf(tab) < 0) + draggedWholeTree.push(tab); + } + } + + var selectedTabs = draggedTabs.filter(function(aTab) { + return aTab.getAttribute('multiselected') == 'true'; + }); + if (draggedWholeTree.length != selectedTabs.length && + selectedTabs.length) { + draggedTabs = draggedRoots = selectedTabs; + if (aInfo.action & sv.kACTIONS_FOR_SOURCE) + sourceService.detachTabs(selectedTabs); + } + + while (aInfo.insertBefore && draggedWholeTree.indexOf(aInfo.insertBefore) > -1) + { + aInfo.insertBefore = sv.getNextTab(aInfo.insertBefore); + } + + if (aInfo.action & sv.kACTIONS_FOR_SOURCE) { + if (aInfo.action & sv.kACTION_PART) { + this.detachTabsOnDrop(draggedRoots); + } + else if (aInfo.action & sv.kACTION_ATTACH) { + this.attachTabsOnDrop(draggedRoots, aInfo.parent); + } + // otherwise, just moved. + + if ( // if this move will cause no change... + sourceBrowser == targetBrowser && + sourceService.getNextVisibleTab(draggedTabs[draggedTabs.length-1]) == aInfo.insertBefore + ) { + if (DEBUG) dump(' => no change\n'); + // then, do nothing + return true; + } + } + + var treeStructure = sourceService.getTreeStructureFromTabs(draggedTabs); + + var newTabs = sv.moveTabsInternal(draggedTabs, { + duplicate : aInfo.action & sv.kACTION_DUPLICATE, + insertBefore : aInfo.insertBefore + }); + + if (newTabs.length && aInfo.action & sv.kACTION_ATTACH) + this.attachTabsOnDrop( + newTabs.filter(function(aTab, aIndex) { + return treeStructure[aIndex] == -1; + }), + aInfo.parent + ); + + return true; + }, + + getDraggedTabsInfoFromOneTab : function TabbarDND_getDraggedTabsInfoFromOneTab(aTab, aInfo) + { + aInfo = aInfo || {}; + if (aInfo.draggedTabsInfo) + return aInfo.draggedTabsInfo; + + var sv = this.treeStyleTab; + var sourceWindow = aTab.ownerDocument.defaultView; + var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aTab); + var sourceService = sourceBrowser.treeStyleTab; + + aTab = sourceService.getTabFromChild(aTab); + if (!aTab || !aTab.parentNode) // ignore removed tabs! + return { + draggedTab : null, + draggedTabs : [], + isMultipleMove : false + }; + + var draggedTabs = sourceWindow['piro.sakura.ne.jp'].tabsDragUtils.getSelectedTabs(aTab || sourceBrowser || aInfo.event); + var isMultipleMove = false; + + if (draggedTabs.length > 1) { + isMultipleMove = true; + } + else if (aInfo.action & sv.kACTIONS_FOR_DESTINATION) { + draggedTabs = [aTab].concat(sourceService.getDescendantTabs(aTab)); + } + + return { + draggedTab : aTab, + draggedTabs : draggedTabs, + isMultipleMove : isMultipleMove + }; + }, + + attachTabsOnDrop : function TabbarDND_attachTabsOnDrop(aTabs, aParent) + { + var b = aTabs[0].ownerDocument.defaultView.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); + var sv = b.treeStyleTab; + + b.movingSelectedTabs = true; // Multiple Tab Handler + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (!tab.parentNode) continue; // ignore removed tabs + if (aParent) + sv.attachTabTo(tab, aParent); + else + sv.detachTab(tab); + sv.collapseExpandTab(tab, false); + } + b.movingSelectedTabs = false; // Multiple Tab Handler + }, + + detachTabsOnDrop : function TabbarDND_detachTabsOnDrop(aTabs) + { + var b = aTabs[0].ownerDocument.defaultView.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); + var sv = b.treeStyleTab; + + b.movingSelectedTabs = true; // Multiple Tab Handler + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + if (!tab.parentNode) continue; // ignore removed tabs + sv.detachTab(tab); + sv.collapseExpandTab(tab, false); + } + b.movingSelectedTabs = false; // Multiple Tab Handler + }, + + clearDropPosition : function TabbarDND_clearDropPosition(aOnFinish) + { + var sv = this.treeStyleTab; + var b = this.browser; + + b.visibleTabs.forEach(function(aTab) { + if (aTab.hasAttribute(sv.kDROP_POSITION)) + aTab.removeAttribute(sv.kDROP_POSITION) + + if (aOnFinish) { + aTab.style.transform = ''; + if ('__treestyletab__opacityBeforeDragged' in aTab) { + aTab.style.opacity = aTab.__treestyletab__opacityBeforeDragged; + delete aTab.__treestyletab__opacityBeforeDragged; + } + } + }); + + if (aOnFinish) + this.browser.mTabContainer.removeAttribute('movingtab') + }, + + isDraggingAllTabs : function TabbarDND_isDraggingAllTabs(aTab, aTabs) + { + var sv = this.treeStyleTab; + var b = this.browser; + + var actionInfo = { + action : sv.kACTIONS_FOR_DESTINATION | sv.kACTION_IMPORT + }; + var tabsInfo = this.getDraggedTabsInfoFromOneTab(aTab, actionInfo); + return tabsInfo.draggedTabs.length == (aTabs || sv.getAllTabs(b)).length; + }, + + isDraggingAllCurrentTabs : function TabbarDND_isDraggingAllCurrentTabs(aTab) + { + return this.isDraggingAllTabs(aTab, this.treeStyleTab.getTabs(this.treeStyleTab.browser)); + }, + + handleEvent : function TabbarDND_handleEvent(aEvent) + { + // ignore drag and drop while toolbar customization + if (this.treeStyleTab.isToolbarCustomizing) + return; + + switch (aEvent.type) + { + case 'dragstart': return this.onDragStart(aEvent); + case 'dragenter': return this.onDragEnter(aEvent); + case 'dragleave': return this.onDragLeave(aEvent); + case 'dragend': return this.onDragEnd(aEvent); + case 'dragover': return this.onDragOver(aEvent); + case 'drop': return this.onDrop(aEvent); + } + }, + + onDragStart : function TabbarDND_onDragStart(aEvent) + { + if (this.canDragTabbar(aEvent)) + return this.onTabbarDragStart(aEvent); + + var tab = this.treeStyleTab.getTabFromEvent(aEvent); + if (tab) + return this.onTabDragStart(aEvent, tab); + }, + + onTabDragStart : function TabbarDND_onTabDragStart(aEvent, aTab) + { + var sv = this.treeStyleTab; + var w = this.window; + var actionInfo = { + action : sv.kACTIONS_FOR_DESTINATION | sv.kACTION_MOVE, + event : aEvent + }; + var tabsInfo = this.getDraggedTabsInfoFromOneTab(aTab, actionInfo); + if ( + tabsInfo.draggedTabs.length <= 1 || + Array.some(tabsInfo.draggedTabs, function(aTab) { + return aTab.getAttribute('multiselected') == 'true'; // if multiselected, it should be handled by other addons (like Multiple Tab Handler) + }) + ) + return; + + w['piro.sakura.ne.jp'].tabsDragUtils.startTabsDrag(aEvent, tabsInfo.draggedTabs); + }, + + onTabbarDragStart : function TabbarDND_onTabbarDragStart(aEvent) + { + var sv = this.treeStyleTab; + var dt = aEvent.dataTransfer; + dt.mozSetDataAt( + sv.kDRAG_TYPE_TABBAR, + aEvent.shiftKey ? + sv.kTABBAR_MOVE_FORCE : + sv.kTABBAR_MOVE_NORMAL, + 0 + ); + dt.mozCursor = 'move'; +// var tabbar = sv.browser.mTabContainer; +// var box = tabbar.boxObject; +// dt.setDragImage( +// tabbar, +// aEvent.screenX - box.screenX, +// aEvent.screenY - box.screenY +// ); + // no feedback image, because it's annoying... + dt.setDragImage(new this.window.Image(), 0, 0); + aEvent.stopPropagation(); + this.readyToStartTabbarDrag(); + }, + + onDragEnter : function TabbarDND_onDragEnter(aEvent) + { + var sv = this.treeStyleTab; + var w = this.window; + + var dt = aEvent.dataTransfer; + if (!this.canDrop(aEvent)) { + dt.effectAllowed = dt.dropEffect = 'none'; + return; + } + + var tab = aEvent.target; + if (tab.localName != 'tab' || + !utils.getTreePref('autoExpand.enabled')) + return; + + w.clearTimeout(this.mAutoExpandTimer); + w.clearTimeout(this.mAutoExpandTimerNext); + + var sourceNode = dt.getData(sv.kDRAG_TYPE_TABBAR+'-node'); + if (aEvent.target == sourceNode) + return; + + var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + this.mAutoExpandTimerNext = w.setTimeout(function(aSelf, aTarget, aDragged) { + aSelf.mAutoExpandTimerNext = null; + aSelf.mAutoExpandTimer = w.setTimeout( + function() { + let tab = sv.getTabById(aTarget); + if (tab && + sv.shouldTabAutoExpanded(tab) && + tab.getAttribute(sv.kDROP_POSITION) == 'self') { + let draggedTab = aDragged && sv.getTabById(aDragged); + if (utils.getTreePref('autoExpand.intelligently')) { + sv.collapseExpandTreesIntelligentlyFor(tab); + if (draggedTab) + aSelf.updateDragData(draggedTab); + } + else { + if (aSelf.mAutoExpandedTabs.indexOf(aTarget) < 0) + aSelf.mAutoExpandedTabs.push(aTarget); + sv.collapseExpandSubtree(tab, false); + if (draggedTab) + aSelf.updateDragData(draggedTab); + } + } + }, + utils.getTreePref('autoExpand.delay') + ); + }, 0, this, tab.getAttribute(sv.kID), draggedTab && draggedTab.getAttribute(sv.kID)); + + tab = null; + }, + updateDragData : function TabbarDND_updateDragData(aTab) + { + if (!aTab || !aTab._dragData) return; + var sv = this.treeStyleTab; + var data = aTab._dragData; + var offsetX = sv.getXOffsetOfTab(aTab); + var offsetY = sv.getYOffsetOfTab(aTab); + if ('offsetX' in data) data.offsetX += offsetX; + if ('screenX' in data) data.screenX += offsetX; + if ('offsetY' in data) data.offsetY += offsetY; + if ('screenY' in data) data.screenY += offsetY; + }, + + onDragLeave : function TabbarDND_onDragLeave(aEvent) + { + this.clearDropPosition(); + + this.window.clearTimeout(this.mAutoExpandTimer); + this.mAutoExpandTimer = null; + }, + + onDragEnd : function TabbarDND_onDragEnd(aEvent) + { + var sv = this.treeStyleTab; + var dt = aEvent.dataTransfer; + if (dt.getData(sv.kDRAG_TYPE_TABBAR)) + this.onTabbarDragEnd(aEvent); + else + this.onTabDragEnd(aEvent); + }, + + onTabDragEnd : function TabbarDND_onTabDragEnd(aEvent) + { + var sv = this.treeStyleTab; + var b = this.browser; + var d = this.document; + var w = this.window; + + var tabbar = b.mTabContainer; + var strip = sv.tabStrip; + var dt = aEvent.dataTransfer; + + this.clearDropPosition(true); + this.collapseAutoExpandedTabs(); + + if (dt.mozUserCancelled || dt.dropEffect != 'none') + return; + + // prevent handling of this event by the default handler + aEvent.stopPropagation(); + aEvent.preventDefault(); + + var eX = aEvent.screenX; + var eY = aEvent.screenY; + var x, y, w, h; + + // ignore drop on the toolbox + x = w.screenX; + y = w.screenY; + w = w.outerWidth; + h = d.getElementById('navigator-toolbox').boxObject.height; + if (eX > x && eX < x + w && eY > y && eY < y + h) + return; + + // ignore drop near the tab bar + var box = strip.boxObject; + var ignoreArea = Math.max(16, parseInt(sv.getFirstNormalTab(b).boxObject.height / 2)); + x = box.screenX - (sv.isVertical ? ignoreArea : 0 ); + y = box.screenY - ignoreArea; + w = box.width + (sv.isVertical ? ignoreArea + ignoreArea : 0 ); + h = box.height + ignoreArea + ignoreArea; + if (eX > x && eX < x + w && eY > y && eY < y + h) + return; + + var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + if (this.isDraggingAllCurrentTabs(draggedTab)) + return; + + // Respect the behaviour of "Disable detach and tear off tab" + // https://addons.mozilla.org/firefox/addon/bug489729-disable-detach-and-t/ + if ('bug489729' in this.window && + prefs.getPref('extensions.bug489729.disable_detach_tab')) + return; + + if (aEvent.ctrlKey || aEvent.metaKey) + draggedTab.__treestyletab__toBeDuplicated = true; + + b.replaceTabWithWindow(draggedTab); + }, + + onTabbarDragEnd : function TabbarDND_onTabbarDragEnd(aEvent) + { + var w = this.window; + w.setTimeout(function(aSelf) { + aSelf.readyToEndTabbarDrag(); + aSelf.treeStyleTab.removeTabbrowserAttribute(aSelf.treeStyleTab.kDROP_POSITION); + }, 10, this); + aEvent.stopPropagation(); + aEvent.preventDefault(); + }, + + onDragOver : function TabbarDND_onDragOver(aEvent) + { + if (this.onTabDragOver(aEvent)) { + aEvent.stopPropagation(); + aEvent.preventDefault(); // this is required to override default dragover actions! + } + }, + + onTabDragOver : function TabbarDND_onTabDragOver(aEvent) + { +try{ + var sv = this.treeStyleTab; + var b = this.browser; + var tabbar = b.mTabContainer; + + var session = sv.currentDragSession; + if (sv.isToolbarCustomizing) + return false; + + sv.autoScroll.processAutoScroll(aEvent); + + let draggedTab = aEvent.dataTransfer && aEvent.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0); + let dragOverTab = sv.getTabFromEvent(aEvent) || sv.getTabFromTabbarEvent(aEvent) || aEvent.target; + b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils + .processTabsDragging(aEvent, { + canDropOnSelf : !dragOverTab || !dragOverTab.pinned, + isVertical : ( + b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils.isVertical(b.tabContainer) && + ( + (draggedTab && !draggedTab.pinned) || + !utils.getTreePref('pinnedTab.faviconized') + ) + ) + }); + + /** + * We must calculate drop action after tabsDragUtils.processTabsDragging(), + * because the drop position depends on tabs' actual + * positions (which can be changed by animation effects.) + */ + var info = this.getDropAction(aEvent, session); + + var observer = b; + if (b.tabContainer && b.tabContainer._setEffectAllowedForDataTransfer) + observer = b.tabContainer; + + // auto-switch for staying on tabs + if ( + info.position == sv.kDROP_ON && + info.target && + !info.target.selected && + '_dragTime' in observer && '_dragOverDelay' in observer + ) { + let time = observer.mDragTime || observer._dragTime || 0; + let delay = observer.mDragOverDelay || observer._dragOverDelay || 0; + let effects = observer._setEffectAllowedForDataTransfer(aEvent); + if (effects == 'link') { + let now = Date.now(); + if (!time) { + time = now; + if ('mDragTime' in observer) + observer.mDragTime = time; + else + observer._dragTime = time; + } + if (now >= time + delay) + b.selectedTab = info.target; + } + } + + if ( + !info.canDrop || + observer._setEffectAllowedForDataTransfer(aEvent) == 'none' + ) { + aEvent.dataTransfer.effectAllowed = "none"; + this.clearDropPosition(); + return true; + } + + let indicatorTab = info.target; + if (sv.isCollapsed(info.target)) { + let tab = indicatorTab; + while ((tab = sv.getPreviousTab(tab)) && sv.isCollapsed(tab)) {} + if (tab) indicatorTab = tab; + } + + let dropPosition = info.position == sv.kDROP_BEFORE ? 'before' : + info.position == sv.kDROP_AFTER ? 'after' : + 'self'; + if (indicatorTab != draggedTab && + indicatorTab.getAttribute(sv.kDROP_POSITION) != dropPosition) { + this.clearDropPosition(); + indicatorTab.setAttribute(sv.kDROP_POSITION, dropPosition); + if (b.ownerDocument.defaultView['piro.sakura.ne.jp'].tabsDragUtils + .canAnimateDraggedTabs(aEvent)) { + let newOpacity = dropPosition == 'self' ? 0.35 : 0.75 ; // to prevent the dragged tab hides the drop target itself + this.window['piro.sakura.ne.jp'].tabsDragUtils.getDraggedTabs(aEvent).forEach(function(aTab) { + if (!('__treestyletab__opacityBeforeDragged' in aTab)) + aTab.__treestyletab__opacityBeforeDragged = aTab.style.opacity || ''; + aTab.style.opacity = newOpacity; + }); + } + } + + + var indicator = b.mTabDropIndicatorBar || b.tabContainer._tabDropIndicator; + indicator.setAttribute('dragging', (info.position == sv.kDROP_ON || sv.isVertical) ? 'false' : 'true' ); + if (sv.isVertical) + indicator.collapsed = true; + + return (info.position == sv.kDROP_ON || sv.position != 'top') +} +catch(e) { + dump('TabbarDND::onDragOver\n'+e+'\n'); +} + }, + + onDrop : function TabbarDND_onDrop(aEvent) + { + this.onTabDrop(aEvent); + this.collapseAutoExpandedTabs(); + }, + collapseAutoExpandedTabs : function TabbarDND_collapseAutoExpandedTabs() + { + var sv = this.treeStyleTab; + if (this.mAutoExpandedTabs.length) { + if (utils.getTreePref('autoExpand.collapseFinally')) { + for (let i = 0, maxi = this.mAutoExpandedTabs.length; i < maxi; i++) + { + sv.collapseExpandSubtree(sv.getTabById(this.mAutoExpandedTabs[i]), true, true); + } + } + this.mAutoExpandedTabs = []; + } + }, + + onTabDrop : function TSTService_onTabDrop(aEvent) + { + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + + var tabbar = b.mTabContainer; + var dt = aEvent.dataTransfer; + + /** + * We must calculate drop action before clearing "dragging" + * state, because the drop position depends on tabs' actual + * positions (they are applied only while tab dragging.) + */ + var session = sv.currentDragSession; + var dropActionInfo = this.getDropAction(aEvent, session); + + this.clearDropPosition(true); + if (tabbar._tabDropIndicator) + tabbar._tabDropIndicator.collapsed = true; + + var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + if (dt.dropEffect != 'link' && !draggedTab) { + aEvent.stopPropagation(); + return; + } + + var sourceBrowser = sv.getTabBrowserFromChild(draggedTab); + if (draggedTab && sourceBrowser != b) + sourceBrowser.treeStyleTab.tabbarDNDObserver.clearDropPosition(true); + + if (draggedTab && this.performDrop(dropActionInfo, draggedTab)) { + aEvent.stopPropagation(); + return; + } + + // duplicating of tabs + if ( + draggedTab && + ( + dt.dropEffect == 'copy' || + sourceBrowser != b + ) && + dropActionInfo.position == sv.kDROP_ON + ) { + var beforeTabs = Array.slice(b.mTabContainer.childNodes); + w.setTimeout(function() { + var newTabs = Array.slice(b.mTabContainer.childNodes).filter(function(aTab) { + return beforeTabs.indexOf(aTab) < 0; + }); + if (newTabs.length) + sv.attachTabTo(newTabs[0], dropActionInfo.target); + }, 0); + return; + } + + if (!draggedTab) + this.handleLinksOrBookmarks(aEvent, dropActionInfo); + }, + handleLinksOrBookmarks : function TabbarDND_handleLinksOrBookmarks(aEvent, aDropActionInfo) + { + aEvent.stopPropagation(); + + var uris = this.retrieveURLsFromDataTransfer(aEvent.dataTransfer); + uris.forEach(function(aURI) { + if (aURI.indexOf(this.BOOKMARK_FOLDER) != 0) + this.securityCheck(aURI, aEvent); + }, this); + + var sv = this.treeStyleTab; + var b = this.browser; + var w = this.window; + var self = this; + + let bgLoad = prefs.getPref('browser.tabs.loadInBackground'); + if (aEvent.shiftKey) bgLoad = !bgLoad; + + let tab = sv.getTabFromEvent(aEvent); + if ( + !tab || + aEvent.dataTransfer.dropEffect == 'copy' || + uris.length > 1 || + uris[0].indexOf(this.BOOKMARK_FOLDER) == 0 + ) { + uris.reverse().forEach(function(aURI) { + if (aURI.indexOf(this.BOOKMARK_FOLDER) == 0) { + let newTabs = sv.getNewTabsWithOperation(function() { + var data = aURI.replace(self.BOOKMARK_FOLDER, ''); + data = JSON.parse(data); + w.PlacesUIUtils._openTabset(data.children, { type : 'drop' }, w, data.title); + }, b); + aDropActionInfo.draggedTabsInfo = { + draggedTabs : newTabs, + draggedTab : newTabs[0], + isMultipleMove : newTabs.length > 1 + }; + this.performDrop(aDropActionInfo, newTabs[0]); + } + else { + aURI = utils.getShortcutOrURI(w, aURI); + this.performDrop(aDropActionInfo, b.loadOneTab(aURI, { inBackground: bgLoad })); + } + }, this); + } + else { + let locked = ( + tab.getAttribute('locked') == 'true' || // Tab Mix Plus and others + tab.getAttribute('isPageLocked') == 'true' // Super Tab Mode + ); + let loadDroppedLinkToNewChildTab = aDropActionInfo.position != sv.kDROP_ON || locked; + if (!loadDroppedLinkToNewChildTab && + aDropActionInfo.position == sv.kDROP_ON) + loadDroppedLinkToNewChildTab = sv.dropLinksOnTabBehavior() == sv.kDROPLINK_NEWTAB; + + try { + let uri = utils.getShortcutOrURI(w, uris[0]); + if (loadDroppedLinkToNewChildTab || locked) { + this.performDrop(aDropActionInfo, b.loadOneTab(uri, { inBackground: bgLoad })); + } + else { + tab.linkedBrowser.loadURI(uri); + if (!bgLoad) + b.selectedTab = tab; + } + } + catch(e) { + } + } + }, + securityCheck : function TabbarDND_securityCheck(aURI, aEvent) + { + // See dragDropSecurityCheck() in chrome://global/content/nsDragAndDrop.js + let session = this.treeStyleTab.currentDragSession; + if (!session) { //TODO: use some fake nodePrincipal? + aEvent.stopPropagation(); + throw 'Drop of ' + aURI + ' denied: no drag session.'; + } + let normalizedURI; + try { + normalizedURI = this.treeStyleTab.makeURIFromSpec(aURI); + } + catch(e) { + } + if (!normalizedURI) + return; + let sourceDoc = session.sourceDocument; + let sourceURI = sourceDoc ? sourceDoc.documentURI : 'file:///' ; + let principal = sourceDoc ? + sourceDoc.nodePrincipal : + SecMan.getSimpleCodebasePrincipal(Services.io.newURI(sourceURI, null, null)) ; + try { + if (principal) + SecMan.checkLoadURIStrWithPrincipal(principal, normalizedURI.spec, Ci.nsIScriptSecurityManager.STANDARD); + else + SecMan.checkLoadURIStr(sourceURI, normalizedURI.spec, Ci.nsIScriptSecurityManager.STANDARD); + } + catch(e) { + aEvent.stopPropagation(); + throw 'Drop of ' + aURI + ' denied.'; + } + }, + + retrieveURLsFromDataTransfer : function TSTService_retrieveURLsFromDataTransfer(aDataTransfer) + { + var urls = []; + var types = [ + 'text/x-moz-place', + 'text/uri-list', + 'text/x-moz-text-internal', + 'text/x-moz-url', + 'text/plain', + 'application/x-moz-file' + ]; + for (let i = 0; i < types.length; i++) { + let dataType = types[i]; + for (let i = 0, maxi = aDataTransfer.mozItemCount; i < maxi; i++) + { + let urlData = aDataTransfer.mozGetDataAt(dataType, i); + if (urlData) { + urls = urls.concat(this.retrieveURLsFromData(urlData, dataType)); + } + } + if (urls.length) + break; + } + return urls.filter(function(aURI) { + return aURI && + aURI.length && + aURI.indexOf(this.BOOKMARK_FOLDER) == 0 || + ( + aURI.indexOf(' ', 0) == -1 && + !/^\s*(javascript|data):/.test(aURI) + ); + }, this); + }, + BOOKMARK_FOLDER: 'x-moz-place:', + retrieveURLsFromData : function TSTService_retrieveURLsFromData(aData, aType) + { + switch (aType) + { + case 'text/x-moz-place': + let (uri = JSON.parse(aData).uri) { + if (uri) + return uri; + else + return this.BOOKMARK_FOLDER+aData; + } + + case 'text/uri-list': + return aData.replace(/\r/g, '\n') + .replace(/^\#.+$/gim, '') + .replace(/\n\n+/g, '\n') + .split('\n'); + + case 'text/unicode': + case 'text/plain': + case 'text/x-moz-text-internal': + return [aData.trim()]; + + case 'text/x-moz-url': + return [((aData instanceof Ci.nsISupportsString) ? aData.toString() : aData) + .split('\n')[0]]; + + case 'application/x-moz-file': + let fileHandler = Services.io.getProtocolHandler('file') + .QueryInterface(Ci.nsIFileProtocolHandler); + return [fileHandler.getURLSpecFromFile(aData)]; + } + return []; + }, + + init : function TabbarDND_init(aTabBrowser) + { + this.browser = aTabBrowser; + this.document = aTabBrowser.ownerDocument; + this.window = this.document.defaultView; + this.treeStyleTab = aTabBrowser.treeStyleTab; + + this.mAutoExpandTimer = null; + this.mAutoExpandTimerNext = null; + this.mAutoExpandedTabs = []; + this.startListenEvents(); + }, + + startListenEvents : function TabbarDND_startListenEvents() + { + var target = this.treeStyleTab.ownerToolbar || this.treeStyleTab.tabStrip; + target.addEventListener('dragstart', this, true); + target.addEventListener('dragover', this, true); + target.addEventListener('dragenter', this, false); + target.addEventListener('dragleave', this, false); + target.addEventListener('dragend', this, true); + target.addEventListener('drop', this, true); + }, + + destroy : function TabbarDND_destroy() + { + this.endListenEvents(); + + delete this.treeStyleTab; + delete this.browser; + delete this.document; + delete this.window; + }, + + endListenEvents : function TabbarDND_endListenEvents() + { + var target = this.treeStyleTab.ownerToolbar || this.treeStyleTab.tabStrip; + target.removeEventListener('dragstart', this, true); + target.removeEventListener('dragover', this, true); + target.removeEventListener('dragenter', this, false); + target.removeEventListener('dragleave', this, false); + target.removeEventListener('dragend', this, true); + target.removeEventListener('drop', this, true); + } + +}; + diff --git a/modules/tabpanelDNDObserver.js b/modules/tabpanelDNDObserver.js index 4f19e606..fa7e273a 100644 --- a/modules/tabpanelDNDObserver.js +++ b/modules/tabpanelDNDObserver.js @@ -1,168 +1,168 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2013 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TabpanelDNDObserver']; - -const Cc = Components.classes; -const Ci = Components.interfaces; - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); - - -function TabpanelDNDObserver(aTabBrowser) -{ - this.init(aTabBrowser); -} - -TabpanelDNDObserver.prototype = { - - getDropPosition : function TabpanelDND_getDropPosition(aEvent) - { - var box = this.browser.boxObject; - var W = box.width; - var H = box.height; - var X = box.screenX; - var Y = box.screenY; - var x = aEvent.screenX - X; - var y = aEvent.screenY - Y; - - if (x > (W * 0.33) && - x < (W * 0.66) && - y > (H * 0.33) && - y < (H * 0.66)) - return 'center'; - - var isTL = x <= W - (y * W / H); - var isBL = x <= y * W / H; - return (isTL && isBL) ? 'left' : - (isTL && !isBL) ? 'top' : - (!isTL && isBL) ? 'bottom' : - 'right' ; - }, - - canDrop : function TabpanelDND_canDrop(aEvent) - { - var session = this.treeStyleTab.currentDragSession; - return !!( - session && - session.isDataFlavorSupported(this.treeStyleTab.kDRAG_TYPE_TABBAR) && - session.sourceNode && - session.sourceNode.ownerDocument == this.document - ); - }, - - handleEvent : function TabpanelDND_handleEvent(aEvent) - { - // ignore drag and drop while toolbar customization - if (this.treeStyleTab.isToolbarCustomizing) - return; - - switch (aEvent.type) - { - case 'dragleave': return this.onDragLeave(aEvent); - case 'dragover': return this.onDragOver(aEvent); - case 'drop': return this.onDrop(aEvent); - } - }, - - onDragLeave : function TabpanelDND_onDragLeave(aEvent) - { - if (!this.canDrop(aEvent)) return; - var sv = this.treeStyleTab; - if (this.browser.hasAttribute(sv.kDROP_POSITION)) - sv.setTabbrowserAttribute(sv.kDROP_POSITION, sv.kDROP_POSITION_UNKNOWN); - }, - - onDragOver : function TabpanelDND_onDragOver(aEvent) - { - if (!this.canDrop(aEvent)) return; - aEvent.preventDefault(); - var sv = this.treeStyleTab; - sv.setTabbrowserAttribute(sv.kDROP_POSITION, this.getDropPosition(aEvent)); - }, - - onDrop : function TabpanelDND_onDrop(aEvent) - { - if (!this.canDrop(aEvent)) return; - var sv = this.treeStyleTab; - var dt = aEvent.dataTransfer; - var position = this.getDropPosition(aEvent); - if (position != 'center' && - position != sv.position) { - if (utils.getTreePref('tabbar.fixed.autoCancelOnDrop') && - dt.getData(sv.kDRAG_TYPE_TABBAR) != sv.kTABBAR_MOVE_FORCE) { - let orient = (position == 'left' || position == 'right') ? 'vertical' : 'horizontal' ; - utils.setTreePref('tabbar.fixed.'+orient, false); - } - sv.setPrefForActiveWindow(function() { - sv.position = sv.base.position = position; - }); - } - - aEvent.preventDefault(); - aEvent.stopPropagation(); - }, - - init : function TabpanelDND_init(aTabBrowser) - { - this.browser = aTabBrowser; - this.document = aTabBrowser.ownerDocument; - this.window = this.document.defaultView; - this.treeStyleTab = aTabBrowser.treeStyleTab; - - var b = this.treeStyleTab.mTabBrowser; - b.mPanelContainer.addEventListener('dragover', this, true); - b.mPanelContainer.addEventListener('dragleave', this, true); - b.mPanelContainer.addEventListener('drop', this, true); - }, - - destroy : function TabpanelDND_destroy() - { - var b = this.treeStyleTab.mTabBrowser; - b.mPanelContainer.removeEventListener('dragover', this, true); - b.mPanelContainer.removeEventListener('dragleave', this, true); - b.mPanelContainer.removeEventListener('drop', this, true); - - delete this.treeStyleTab; - delete this.browser; - delete this.document; - delete this.window; - } - -}; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2013 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TabpanelDNDObserver']; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + + +function TabpanelDNDObserver(aTabBrowser) +{ + this.init(aTabBrowser); +} + +TabpanelDNDObserver.prototype = { + + getDropPosition : function TabpanelDND_getDropPosition(aEvent) + { + var box = this.browser.boxObject; + var W = box.width; + var H = box.height; + var X = box.screenX; + var Y = box.screenY; + var x = aEvent.screenX - X; + var y = aEvent.screenY - Y; + + if (x > (W * 0.33) && + x < (W * 0.66) && + y > (H * 0.33) && + y < (H * 0.66)) + return 'center'; + + var isTL = x <= W - (y * W / H); + var isBL = x <= y * W / H; + return (isTL && isBL) ? 'left' : + (isTL && !isBL) ? 'top' : + (!isTL && isBL) ? 'bottom' : + 'right' ; + }, + + canDrop : function TabpanelDND_canDrop(aEvent) + { + var session = this.treeStyleTab.currentDragSession; + return !!( + session && + session.isDataFlavorSupported(this.treeStyleTab.kDRAG_TYPE_TABBAR) && + session.sourceNode && + session.sourceNode.ownerDocument == this.document + ); + }, + + handleEvent : function TabpanelDND_handleEvent(aEvent) + { + // ignore drag and drop while toolbar customization + if (this.treeStyleTab.isToolbarCustomizing) + return; + + switch (aEvent.type) + { + case 'dragleave': return this.onDragLeave(aEvent); + case 'dragover': return this.onDragOver(aEvent); + case 'drop': return this.onDrop(aEvent); + } + }, + + onDragLeave : function TabpanelDND_onDragLeave(aEvent) + { + if (!this.canDrop(aEvent)) return; + var sv = this.treeStyleTab; + if (this.browser.hasAttribute(sv.kDROP_POSITION)) + sv.setTabbrowserAttribute(sv.kDROP_POSITION, sv.kDROP_POSITION_UNKNOWN); + }, + + onDragOver : function TabpanelDND_onDragOver(aEvent) + { + if (!this.canDrop(aEvent)) return; + aEvent.preventDefault(); + var sv = this.treeStyleTab; + sv.setTabbrowserAttribute(sv.kDROP_POSITION, this.getDropPosition(aEvent)); + }, + + onDrop : function TabpanelDND_onDrop(aEvent) + { + if (!this.canDrop(aEvent)) return; + var sv = this.treeStyleTab; + var dt = aEvent.dataTransfer; + var position = this.getDropPosition(aEvent); + if (position != 'center' && + position != sv.position) { + if (utils.getTreePref('tabbar.fixed.autoCancelOnDrop') && + dt.getData(sv.kDRAG_TYPE_TABBAR) != sv.kTABBAR_MOVE_FORCE) { + let orient = (position == 'left' || position == 'right') ? 'vertical' : 'horizontal' ; + utils.setTreePref('tabbar.fixed.'+orient, false); + } + sv.setPrefForActiveWindow(function() { + sv.position = sv.base.position = position; + }); + } + + aEvent.preventDefault(); + aEvent.stopPropagation(); + }, + + init : function TabpanelDND_init(aTabBrowser) + { + this.browser = aTabBrowser; + this.document = aTabBrowser.ownerDocument; + this.window = this.document.defaultView; + this.treeStyleTab = aTabBrowser.treeStyleTab; + + var b = this.treeStyleTab.mTabBrowser; + b.mPanelContainer.addEventListener('dragover', this, true); + b.mPanelContainer.addEventListener('dragleave', this, true); + b.mPanelContainer.addEventListener('drop', this, true); + }, + + destroy : function TabpanelDND_destroy() + { + var b = this.treeStyleTab.mTabBrowser; + b.mPanelContainer.removeEventListener('dragover', this, true); + b.mPanelContainer.removeEventListener('dragleave', this, true); + b.mPanelContainer.removeEventListener('drop', this, true); + + delete this.treeStyleTab; + delete this.browser; + delete this.document; + delete this.window; + } + +}; + diff --git a/modules/themeManager.js b/modules/themeManager.js index a2f0d614..1cc1105d 100644 --- a/modules/themeManager.js +++ b/modules/themeManager.js @@ -1,178 +1,178 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2011-2013 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TreeStyleTabThemeManager']; - -const BASE = 'chrome://treestyletab/skin/'; - -Components.utils.import('resource://treestyletab-modules/base.js'); - -function TreeStyleTabThemeManager(aWindow) -{ - this.window = aWindow; - this._preLoadImagesForStyleDone = []; - this._preLoadImagesForStyleDoneImages = []; -} -TreeStyleTabThemeManager.prototype = { - destroy : function() - { - delete this.window; - }, - - set : function(aStyle, aPosition) - { - if (this._lastStyles) { - for (let i = 0, maxi = this._lastStyles.length; i < maxi; i++) - { - let style = this._lastStyles[i]; - style.parentNode.removeChild(style); - } - } - this._lastStyles = null; - - var styles = []; - switch (aStyle) - { - default: - break; - - case 'plain': - case 'flat': - case 'mixed': - styles.push(BASE+'square/base.css'); - if (aStyle != 'plain') { - styles.push(BASE+'square/mixed.css'); - styles.push(BASE+'square/tab-surface.css'); - } - if (aStyle != 'flat') - styles.push(BASE+'square/dropshadow.css'); - styles.push(BASE+'platform-styled.css'); - styles.push(BASE+'square/platform.css'); - break; - - case 'vertigo': - styles.push(BASE+'square/base.css'); - styles.push(BASE+'square/vertigo.css'); - styles.push(BASE+'platform-styled.css'); - styles.push(BASE+'square/platform.css'); - break; - - case 'metal': - styles.push(BASE+'metal/base.css'); - styles.push(BASE+'metal/tab.css'); - styles.push(BASE+'metal/aero.css'); - styles.push(BASE+'platform-styled.css'); - styles.push(BASE+'metal/platform.css'); - break; - - case 'sidebar': - styles.push(BASE+'sidebar/sidebar.css'); - styles.push(BASE+'sidebar/aero.css'); - styles.push(BASE+'platform-styled.css'); - break; - } - - if (styles.length) { - this._lastStyles = styles.map(function(aStyle) { - var d = this.window.document; - var pi = d.createProcessingInstruction( - 'xml-stylesheet', - 'type="text/css" href="'+aStyle+'"' - ); - d.insertBefore(pi, d.documentElement); - return pi; - }, this); - this.preloadImages(aStyle, aPosition); - } - }, - - preloadImages : function(aStyle, aPosition) - { - var key = aStyle+'-'+aPosition; - if (!aStyle || - this._preLoadImagesForStyleDone.indexOf(key) > -1) - return; - this._preLoadImagesForStyleDone.push(key); - - var images = key in this._preLoadImages ? - this._preLoadImages[key] : - null ; - if (!images) return; - - for (let i = 0, maxi = images.length; i < maxi; i++) - { - let image = images[i]; - if (this._preLoadImagesForStyleDoneImages.indexOf(image) > -1) - continue; - - (new this.window.Image()).src = image; - this._preLoadImagesForStyleDoneImages.push(image); - } - }, - - _preLoadImages : { - 'metal-left' : [ - BASE+'metal/tab-active-l.png', - BASE+'metal/tab-inactive-l.png', - BASE+'metal/tab-active-selected-l.png', - BASE+'metal/tab-inactive-selected-l.png', - BASE+'metal/shadow-active-l.png', - BASE+'metal/shadow-inactive-l.png' - ].concat( - [ - BASE+'metal/tab-active-middle.png', - BASE+'metal/tab-active-middle-selected.png', - BASE+'metal/tab-inactive-middle.png', - BASE+'metal/tab-inactive-middle-selected.png' - ] - ), - 'metal-right' : [ - BASE+'metal/tab-active-r.png', - BASE+'metal/tab-inactive-r.png', - BASE+'metal/tab-active-selected-r.png', - BASE+'metal/tab-inactive-selected-r.png', - BASE+'metal/shadow-active-r.png', - BASE+'metal/shadow-inactive-r.png' - ].concat( - [ - BASE+'metal/tab-active-middle.png', - BASE+'metal/tab-active-middle-selected.png', - BASE+'metal/tab-inactive-middle.png', - BASE+'metal/tab-inactive-middle-selected.png' - ] - ) - } -}; +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2011-2013 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TreeStyleTabThemeManager']; + +const BASE = 'chrome://treestyletab/skin/'; + +Components.utils.import('resource://treestyletab-modules/base.js'); + +function TreeStyleTabThemeManager(aWindow) +{ + this.window = aWindow; + this._preLoadImagesForStyleDone = []; + this._preLoadImagesForStyleDoneImages = []; +} +TreeStyleTabThemeManager.prototype = { + destroy : function() + { + delete this.window; + }, + + set : function(aStyle, aPosition) + { + if (this._lastStyles) { + for (let i = 0, maxi = this._lastStyles.length; i < maxi; i++) + { + let style = this._lastStyles[i]; + style.parentNode.removeChild(style); + } + } + this._lastStyles = null; + + var styles = []; + switch (aStyle) + { + default: + break; + + case 'plain': + case 'flat': + case 'mixed': + styles.push(BASE+'square/base.css'); + if (aStyle != 'plain') { + styles.push(BASE+'square/mixed.css'); + styles.push(BASE+'square/tab-surface.css'); + } + if (aStyle != 'flat') + styles.push(BASE+'square/dropshadow.css'); + styles.push(BASE+'platform-styled.css'); + styles.push(BASE+'square/platform.css'); + break; + + case 'vertigo': + styles.push(BASE+'square/base.css'); + styles.push(BASE+'square/vertigo.css'); + styles.push(BASE+'platform-styled.css'); + styles.push(BASE+'square/platform.css'); + break; + + case 'metal': + styles.push(BASE+'metal/base.css'); + styles.push(BASE+'metal/tab.css'); + styles.push(BASE+'metal/aero.css'); + styles.push(BASE+'platform-styled.css'); + styles.push(BASE+'metal/platform.css'); + break; + + case 'sidebar': + styles.push(BASE+'sidebar/sidebar.css'); + styles.push(BASE+'sidebar/aero.css'); + styles.push(BASE+'platform-styled.css'); + break; + } + + if (styles.length) { + this._lastStyles = styles.map(function(aStyle) { + var d = this.window.document; + var pi = d.createProcessingInstruction( + 'xml-stylesheet', + 'type="text/css" href="'+aStyle+'"' + ); + d.insertBefore(pi, d.documentElement); + return pi; + }, this); + this.preloadImages(aStyle, aPosition); + } + }, + + preloadImages : function(aStyle, aPosition) + { + var key = aStyle+'-'+aPosition; + if (!aStyle || + this._preLoadImagesForStyleDone.indexOf(key) > -1) + return; + this._preLoadImagesForStyleDone.push(key); + + var images = key in this._preLoadImages ? + this._preLoadImages[key] : + null ; + if (!images) return; + + for (let i = 0, maxi = images.length; i < maxi; i++) + { + let image = images[i]; + if (this._preLoadImagesForStyleDoneImages.indexOf(image) > -1) + continue; + + (new this.window.Image()).src = image; + this._preLoadImagesForStyleDoneImages.push(image); + } + }, + + _preLoadImages : { + 'metal-left' : [ + BASE+'metal/tab-active-l.png', + BASE+'metal/tab-inactive-l.png', + BASE+'metal/tab-active-selected-l.png', + BASE+'metal/tab-inactive-selected-l.png', + BASE+'metal/shadow-active-l.png', + BASE+'metal/shadow-inactive-l.png' + ].concat( + [ + BASE+'metal/tab-active-middle.png', + BASE+'metal/tab-active-middle-selected.png', + BASE+'metal/tab-inactive-middle.png', + BASE+'metal/tab-inactive-middle-selected.png' + ] + ), + 'metal-right' : [ + BASE+'metal/tab-active-r.png', + BASE+'metal/tab-inactive-r.png', + BASE+'metal/tab-active-selected-r.png', + BASE+'metal/tab-inactive-selected-r.png', + BASE+'metal/shadow-active-r.png', + BASE+'metal/shadow-inactive-r.png' + ].concat( + [ + BASE+'metal/tab-active-middle.png', + BASE+'metal/tab-active-middle-selected.png', + BASE+'metal/tab-inactive-middle.png', + BASE+'metal/tab-inactive-middle-selected.png' + ] + ) + } +}; diff --git a/modules/utils.js b/modules/utils.js index 8cfb1d82..114ede43 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -1,306 +1,306 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2010-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -"use strict"; - -let EXPORTED_SYMBOLS = ['TreeStyleTabUtils']; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://treestyletab-modules/constants.js'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); -XPCOMUtils.defineLazyGetter(this, 'stringBundle', function() { - Cu.import('resource://treestyletab-modules/lib/stringBundle.js', {}); - return window['piro.sakura.ne.jp'].stringBundle; -}); - -XPCOMUtils.defineLazyModuleGetter(this, 'Task', - 'resource://gre/modules/Task.jsm'); -XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabConstants', - 'resource://treestyletab-modules/constants.js', 'TreeStyleTabConstants'); - -const TST_PREF_PREFIX = 'extensions.treestyletab.'; -const TST_PREF_VERSION = 10; - - -let TreeStyleTabUtils = { - - get prefs () { - return prefs; - }, - -/* Save/Load Prefs */ - - getTreePref : function TSTUtils_getTreePref(aPrefstring) - { - return prefs.getPref(TST_PREF_PREFIX + aPrefstring); - }, - - setTreePref : function TSTUtils_setTreePref(aPrefstring, aNewValue) - { - return prefs.setPref(TST_PREF_PREFIX + aPrefstring, aNewValue); - }, - - clearTreePref : function TSTUtils_clearTreePref(aPrefstring) - { - return prefs.clearPref(TST_PREF_PREFIX + aPrefstring); - }, - - migratePrefs : function utils_migratePrefs() - { - // migrate old prefs - var orientalPrefs = []; - switch (this.getTreePref('prefsVersion')) - { - case 0: - orientalPrefs = orientalPrefs.concat([ - 'extensions.treestyletab.tabbar.fixed', - 'extensions.treestyletab.enableSubtreeIndent', - 'extensions.treestyletab.allowSubtreeCollapseExpand' - ]); - case 1: - case 2: - if (this.getTreePref('urlbar.loadSameDomainToNewChildTab') !== null) { - let value = this.getTreePref('urlbar.loadSameDomainToNewChildTab'); - this.setTreePref('urlbar.loadSameDomainToNewTab', value); - this.setTreePref('urlbar.loadSameDomainToNewTab.asChild', value); - if (value) { - this.setTreePref('urlbar.loadDifferentDomainToNewTab', value); - } - this.clearTreePref('urlbar.loadSameDomainToNewChildTab'); - } - case 3: - if (this.getTreePref('loadDroppedLinkToNewChildTab') !== null) { - this.setTreePref('dropLinksOnTab.behavior', - this.getTreePref('loadDroppedLinkToNewChildTab.confirm') ? - TreeStyleTabConstants.kDROPLINK_ASK : - this.getTreePref('loadDroppedLinkToNewChildTab') ? - TreeStyleTabConstants.kDROPLINK_NEWTAB : - TreeStyleTabConstants.kDROPLINK_LOAD - ); - this.clearTreePref('loadDroppedLinkToNewChildTab.confirm'); - this.clearTreePref('loadDroppedLinkToNewChildTab'); - } - if (this.getTreePref('openGroupBookmarkAsTabSubTree') !== null) { - let behavior = 0; - if (this.getTreePref('openGroupBookmarkAsTabSubTree.underParent')) - behavior += TreeStyleTabConstants.kGROUP_BOOKMARK_USE_DUMMY; - if (!this.getTreePref('openGroupBookmarkBehavior.confirm')) { - behavior += ( - this.getTreePref('openGroupBookmarkAsTabSubTree') ? - TreeStyleTabConstants.kGROUP_BOOKMARK_SUBTREE : - TreeStyleTabConstants.kGROUP_BOOKMARK_SEPARATE - ); - } - this.setTreePref('openGroupBookmark.behavior', behavior); - this.clearTreePref('openGroupBookmarkBehavior.confirm'); - this.clearTreePref('openGroupBookmarkAsTabSubTree'); - this.clearTreePref('openGroupBookmarkAsTabSubTree.underParent'); - } - case 4: - let (subTreePrefs = [ - 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect', - 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect.onCurrentTabRemove', - 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect.whileFocusMovingByShortcut', - 'extensions.treestyletab.autoExpandSubTreeOnAppendChild', - 'extensions.treestyletab.autoExpandSubTreeOnCollapsedChildFocused', - 'extensions.treestyletab.collapseExpandSubTree.dblclick', - 'extensions.treestyletab.createSubTree.underParent', - 'extensions.treestyletab.show.context-item-reloadTabSubTree', - 'extensions.treestyletab.show.context-item-removeTabSubTree', - 'extensions.treestyletab.show.context-item-bookmarkTabSubTree', - 'extensions.multipletab.show.multipletab-selection-item-removeTabSubTree', - 'extensions.multipletab.show.multipletab-selection-item-createSubTree' - ]) { - for (let i = 0, maxi = subTreePrefs.length; i < maxi; i++) - { - let pref = subTreePrefs[i]; - let value = prefs.getPref(pref); - if (value === null) { - continue; - } - prefs.setPref(pref.replace('SubTree', 'Subtree'), value); - prefs.clearPref(pref); - } - } - case 5: - let (behavior = this.getTreePref('openGroupBookmark.behavior')) { - behavior = behavior | 2048; - this.setTreePref('openGroupBookmark.behavior', behavior); - } - case 6: - let ( - general = this.getTreePref('autoAttachNewTabsAsChildren'), - search = this.getTreePref('autoAttachSearchResultAsChildren') - ) { - if (general !== null) - this.setTreePref('autoAttach', general); - if (search !== null) - this.setTreePref('autoAttach.searchResult', search); - } - case 7: - let ( - enabled = this.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut'), - delay = this.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay') - ) { - if (enabled !== null) { - this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut', enabled); - this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.collapseOthers', enabled); - } - if (delay !== null) - this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay', delay); - } - case 8: - orientalPrefs = orientalPrefs.concat([ - 'extensions.treestyletab.indent', - 'extensions.treestyletab.indent.min' - ]); - case 9: - let (behavior = this.getTreePref('openGroupBookmark.behavior')) { - if (behavior & 4) { - behavior ^= 4; - behavior |= 1; - this.setTreePref('openGroupBookmark.behavior', behavior); - } - } - default: - for (let i = 0, maxi = orientalPrefs.length; i < maxi; i++) - { - let pref = orientalPrefs[i]; - let value = prefs.getPref(pref); - if (value === null) { - continue; - } - prefs.setPref(pref+'.horizontal', value); - prefs.setPref(pref+'.vertical', value); - prefs.clearPref(pref); - } - break; - } - this.setTreePref('prefsVersion', TST_PREF_VERSION); - }, - -/* string bundle */ - get treeBundle () { - return stringBundle.get('chrome://treestyletab/locale/treestyletab.properties'); - }, - get tabbrowserBundle () { - return stringBundle.get('chrome://browser/locale/tabbrowser.properties'); - }, - - evalInSandbox : function utils_evalInSandbox(aCode, aOwner) - { - try { - var sandbox = new Cu.Sandbox(aOwner || 'about:blank'); - return Cu.evalInSandbox(aCode, sandbox); - } - catch(e) { - } - return void(0); - }, - - - isTabNotRestoredYet : function utils_isTabNotRestoredYet(aTab) - { - var browser = aTab.linkedBrowser; - // Firefox 25 and later. See: https://bugzilla.mozilla.org/show_bug.cgi?id=867142 - if (this.TabRestoreStates && - this.TabRestoreStates.has(browser)) - return ( - this.TabRestoreStates.isNeedsRestore(browser) || - this.TabRestoreStates.isRestoring(browser) - ); - - return !!browser.__SS_restoreState; - }, - isTabNeedToBeRestored : function utils_isTabNeedToBeRestored(aTab) - { - var browser = aTab.linkedBrowser; - // Firefox 25 and later. See: https://bugzilla.mozilla.org/show_bug.cgi?id=867142 - if (this.TabRestoreStates && - this.TabRestoreStates.has(browser)) - return this.TabRestoreStates.isNeedsRestore(browser); - - return browser.__SS_restoreState == 1; - }, - get SessionStoreInternal() { - return this.SessionStoreNS.SessionStoreInternal; - }, - get TabRestoreStates() { - return this.SessionStoreNS.TabRestoreStates; - }, - get SessionStoreNS() { - if (!this._SessionStoreNS) { - try { - // resource://app/modules/sessionstore/SessionStore.jsm ? - this._SessionStoreNS = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm', {}); - } - catch(e) { - this._SessionStoreNS = {}; - } - } - return this._SessionStoreNS; - }, - - getShortcutOrURI : function utils_getShortcutOrURI(aBrowserWindow, aURI) - { - var done = false; - aBrowserWindow.getShortcutOrURIAndPostData(aURI, function(aData) { - aURI = aData.url; - done = true; - }); - - // this should be rewritten in asynchronous style... - var thread = Cc['@mozilla.org/thread-manager;1'].getService().mainThread; - while (!done) - { - thread.processNextEvent(true); - } - - return aURI; - } -}; +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2010-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +"use strict"; + +let EXPORTED_SYMBOLS = ['TreeStyleTabUtils']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://treestyletab-modules/constants.js'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); +XPCOMUtils.defineLazyGetter(this, 'stringBundle', function() { + Cu.import('resource://treestyletab-modules/lib/stringBundle.js', {}); + return window['piro.sakura.ne.jp'].stringBundle; +}); + +XPCOMUtils.defineLazyModuleGetter(this, 'Task', + 'resource://gre/modules/Task.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabConstants', + 'resource://treestyletab-modules/constants.js', 'TreeStyleTabConstants'); + +const TST_PREF_PREFIX = 'extensions.treestyletab.'; +const TST_PREF_VERSION = 10; + + +let TreeStyleTabUtils = { + + get prefs () { + return prefs; + }, + +/* Save/Load Prefs */ + + getTreePref : function TSTUtils_getTreePref(aPrefstring) + { + return prefs.getPref(TST_PREF_PREFIX + aPrefstring); + }, + + setTreePref : function TSTUtils_setTreePref(aPrefstring, aNewValue) + { + return prefs.setPref(TST_PREF_PREFIX + aPrefstring, aNewValue); + }, + + clearTreePref : function TSTUtils_clearTreePref(aPrefstring) + { + return prefs.clearPref(TST_PREF_PREFIX + aPrefstring); + }, + + migratePrefs : function utils_migratePrefs() + { + // migrate old prefs + var orientalPrefs = []; + switch (this.getTreePref('prefsVersion')) + { + case 0: + orientalPrefs = orientalPrefs.concat([ + 'extensions.treestyletab.tabbar.fixed', + 'extensions.treestyletab.enableSubtreeIndent', + 'extensions.treestyletab.allowSubtreeCollapseExpand' + ]); + case 1: + case 2: + if (this.getTreePref('urlbar.loadSameDomainToNewChildTab') !== null) { + let value = this.getTreePref('urlbar.loadSameDomainToNewChildTab'); + this.setTreePref('urlbar.loadSameDomainToNewTab', value); + this.setTreePref('urlbar.loadSameDomainToNewTab.asChild', value); + if (value) { + this.setTreePref('urlbar.loadDifferentDomainToNewTab', value); + } + this.clearTreePref('urlbar.loadSameDomainToNewChildTab'); + } + case 3: + if (this.getTreePref('loadDroppedLinkToNewChildTab') !== null) { + this.setTreePref('dropLinksOnTab.behavior', + this.getTreePref('loadDroppedLinkToNewChildTab.confirm') ? + TreeStyleTabConstants.kDROPLINK_ASK : + this.getTreePref('loadDroppedLinkToNewChildTab') ? + TreeStyleTabConstants.kDROPLINK_NEWTAB : + TreeStyleTabConstants.kDROPLINK_LOAD + ); + this.clearTreePref('loadDroppedLinkToNewChildTab.confirm'); + this.clearTreePref('loadDroppedLinkToNewChildTab'); + } + if (this.getTreePref('openGroupBookmarkAsTabSubTree') !== null) { + let behavior = 0; + if (this.getTreePref('openGroupBookmarkAsTabSubTree.underParent')) + behavior += TreeStyleTabConstants.kGROUP_BOOKMARK_USE_DUMMY; + if (!this.getTreePref('openGroupBookmarkBehavior.confirm')) { + behavior += ( + this.getTreePref('openGroupBookmarkAsTabSubTree') ? + TreeStyleTabConstants.kGROUP_BOOKMARK_SUBTREE : + TreeStyleTabConstants.kGROUP_BOOKMARK_SEPARATE + ); + } + this.setTreePref('openGroupBookmark.behavior', behavior); + this.clearTreePref('openGroupBookmarkBehavior.confirm'); + this.clearTreePref('openGroupBookmarkAsTabSubTree'); + this.clearTreePref('openGroupBookmarkAsTabSubTree.underParent'); + } + case 4: + let (subTreePrefs = [ + 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect', + 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect.onCurrentTabRemove', + 'extensions.treestyletab.autoCollapseExpandSubTreeOnSelect.whileFocusMovingByShortcut', + 'extensions.treestyletab.autoExpandSubTreeOnAppendChild', + 'extensions.treestyletab.autoExpandSubTreeOnCollapsedChildFocused', + 'extensions.treestyletab.collapseExpandSubTree.dblclick', + 'extensions.treestyletab.createSubTree.underParent', + 'extensions.treestyletab.show.context-item-reloadTabSubTree', + 'extensions.treestyletab.show.context-item-removeTabSubTree', + 'extensions.treestyletab.show.context-item-bookmarkTabSubTree', + 'extensions.multipletab.show.multipletab-selection-item-removeTabSubTree', + 'extensions.multipletab.show.multipletab-selection-item-createSubTree' + ]) { + for (let i = 0, maxi = subTreePrefs.length; i < maxi; i++) + { + let pref = subTreePrefs[i]; + let value = prefs.getPref(pref); + if (value === null) { + continue; + } + prefs.setPref(pref.replace('SubTree', 'Subtree'), value); + prefs.clearPref(pref); + } + } + case 5: + let (behavior = this.getTreePref('openGroupBookmark.behavior')) { + behavior = behavior | 2048; + this.setTreePref('openGroupBookmark.behavior', behavior); + } + case 6: + let ( + general = this.getTreePref('autoAttachNewTabsAsChildren'), + search = this.getTreePref('autoAttachSearchResultAsChildren') + ) { + if (general !== null) + this.setTreePref('autoAttach', general); + if (search !== null) + this.setTreePref('autoAttach.searchResult', search); + } + case 7: + let ( + enabled = this.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut'), + delay = this.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay') + ) { + if (enabled !== null) { + this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut', enabled); + this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.collapseOthers', enabled); + } + if (delay !== null) + this.setTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay', delay); + } + case 8: + orientalPrefs = orientalPrefs.concat([ + 'extensions.treestyletab.indent', + 'extensions.treestyletab.indent.min' + ]); + case 9: + let (behavior = this.getTreePref('openGroupBookmark.behavior')) { + if (behavior & 4) { + behavior ^= 4; + behavior |= 1; + this.setTreePref('openGroupBookmark.behavior', behavior); + } + } + default: + for (let i = 0, maxi = orientalPrefs.length; i < maxi; i++) + { + let pref = orientalPrefs[i]; + let value = prefs.getPref(pref); + if (value === null) { + continue; + } + prefs.setPref(pref+'.horizontal', value); + prefs.setPref(pref+'.vertical', value); + prefs.clearPref(pref); + } + break; + } + this.setTreePref('prefsVersion', TST_PREF_VERSION); + }, + +/* string bundle */ + get treeBundle () { + return stringBundle.get('chrome://treestyletab/locale/treestyletab.properties'); + }, + get tabbrowserBundle () { + return stringBundle.get('chrome://browser/locale/tabbrowser.properties'); + }, + + evalInSandbox : function utils_evalInSandbox(aCode, aOwner) + { + try { + var sandbox = new Cu.Sandbox(aOwner || 'about:blank'); + return Cu.evalInSandbox(aCode, sandbox); + } + catch(e) { + } + return void(0); + }, + + + isTabNotRestoredYet : function utils_isTabNotRestoredYet(aTab) + { + var browser = aTab.linkedBrowser; + // Firefox 25 and later. See: https://bugzilla.mozilla.org/show_bug.cgi?id=867142 + if (this.TabRestoreStates && + this.TabRestoreStates.has(browser)) + return ( + this.TabRestoreStates.isNeedsRestore(browser) || + this.TabRestoreStates.isRestoring(browser) + ); + + return !!browser.__SS_restoreState; + }, + isTabNeedToBeRestored : function utils_isTabNeedToBeRestored(aTab) + { + var browser = aTab.linkedBrowser; + // Firefox 25 and later. See: https://bugzilla.mozilla.org/show_bug.cgi?id=867142 + if (this.TabRestoreStates && + this.TabRestoreStates.has(browser)) + return this.TabRestoreStates.isNeedsRestore(browser); + + return browser.__SS_restoreState == 1; + }, + get SessionStoreInternal() { + return this.SessionStoreNS.SessionStoreInternal; + }, + get TabRestoreStates() { + return this.SessionStoreNS.TabRestoreStates; + }, + get SessionStoreNS() { + if (!this._SessionStoreNS) { + try { + // resource://app/modules/sessionstore/SessionStore.jsm ? + this._SessionStoreNS = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm', {}); + } + catch(e) { + this._SessionStoreNS = {}; + } + } + return this._SessionStoreNS; + }, + + getShortcutOrURI : function utils_getShortcutOrURI(aBrowserWindow, aURI) + { + var done = false; + aBrowserWindow.getShortcutOrURIAndPostData(aURI, function(aData) { + aURI = aData.url; + done = true; + }); + + // this should be rewritten in asynchronous style... + var thread = Cc['@mozilla.org/thread-manager;1'].getService().mainThread; + while (!done) + { + thread.processNextEvent(true); + } + + return aURI; + } +}; diff --git a/modules/window.js b/modules/window.js index be05fdbd..e9ec77c3 100644 --- a/modules/window.js +++ b/modules/window.js @@ -1,1772 +1,1772 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Tree Style Tab. - * - * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. - * Portions created by the Initial Developer are Copyright (C) 2012-2014 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): YUKI "Piro" Hiroshi - * Tetsuharu OHZEKI - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ******/ - -const EXPORTED_SYMBOLS = ['TreeStyleTabWindow']; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); - -XPCOMUtils.defineLazyGetter(this, 'window', function() { - Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); - return getNamespaceFor('piro.sakura.ne.jp'); -}); -XPCOMUtils.defineLazyGetter(this, 'prefs', function() { - Cu.import('resource://treestyletab-modules/lib/prefs.js'); - return window['piro.sakura.ne.jp'].prefs; -}); - -XPCOMUtils.defineLazyModuleGetter(this, 'UninstallationListener', - 'resource://treestyletab-modules/lib/UninstallationListener.js'); - -XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm'); - -Cu.import('resource://treestyletab-modules/base.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabBrowser', 'resource://treestyletab-modules/browser.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); -XPCOMUtils.defineLazyModuleGetter(this, 'AutoHideWindow', 'resource://treestyletab-modules/autoHide.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabThemeManager', 'resource://treestyletab-modules/themeManager.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'FullscreenObserver', 'resource://treestyletab-modules/fullscreenObserver.js'); -XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUIShowHideObserver', 'resource://treestyletab-modules/browserUIShowHideObserver.js'); - -function TreeStyleTabWindow(aWindow) -{ - this.window = aWindow; - this.document = aWindow.document; - - this._restoringTabs = []; - this._shownPopups = []; - this.restoringCount = 0; - - aWindow.addEventListener('DOMContentLoaded', this, true); - aWindow.addEventListener('load', this, false); - aWindow.TreeStyleTabService = this; - - XPCOMUtils.defineLazyModuleGetter(aWindow, 'TreeStyleTabBrowser', 'resource://treestyletab-modules/browser.js'); -} - -TreeStyleTabWindow.prototype = inherit(TreeStyleTabBase, { - - base : TreeStyleTabBase, - - window : null, - document : null, - -/* API */ - - changeTabbarPosition : function TSTWindow_changeTabbarPosition(aNewPosition) /* PUBLIC API (obsolete, for backward compatibility) */ - { - this.position = aNewPosition; - }, - - get position() /* PUBLIC API */ - { - return this.preInitialized && this.browser.treeStyleTab ? - this.browser.treeStyleTab.position : - this.base.position ; - }, - set position(aValue) - { - if ('UndoTabService' in this.window && this.window.UndoTabService.isUndoable()) { - var current = this.base.position; - var self = this; - this.window.UndoTabService.doOperation( - function() { - self.base.position = aValue; - }, - { - label : utils.treeBundle.getString('undo_changeTabbarPosition_label'), - name : 'treestyletab-changeTabbarPosition', - data : { - oldPosition : current, - newPosition : aValue - } - } - ); - } - else { - this.base.position = aValue; - } - return aValue; - }, - - undoChangeTabbarPosition : function TSTWindow_undoChangeTabbarPosition() /* PUBLIC API */ - { - return this.base.undoChangeTabbarPosition(); - }, - - redoChangeTabbarPosition : function TSTWindow_redoChangeTabbarPosition() /* PUBLIC API */ - { - return this.base.redoChangeTabbarPosition(); - }, - - get treeViewEnabled() /* PUBLIC API */ - { - return this.base.treeViewEnabled; - }, - set treeViewEnabled(aValue) - { - return this.base.treeViewEnabled = aValue; - }, - - get useTMPSessionAPI() /* PUBLIC API */ - { - return this.base.useTMPSessionAPI; - }, - set useTMPSessionAPI(aValue) - { - return this.base.useTMPSessionAPI = aValue; - }, - - get browser() - { - var w = this.window; - this.assertBeforeDestruction(w); - return 'SplitBrowser' in w ? w.SplitBrowser.activeBrowser : - w.gBrowser ; - }, - - get browserToolbox() - { - var w = this.window; - return w.gToolbox || w.gNavToolbox; - }, - - get browserBox() - { - return this.document.getElementById('browser'); - }, - - get browserBottomBox() - { - return this.document.getElementById('browser-bottombox'); - }, - - get isPopupWindow() - { - return ( - this.document && - this.document.documentElement.getAttribute('chromehidden') != '' && - !this.window.gBrowser.treeStyleTab.isVisible - ); - }, - -/* backward compatibility */ - getTempTreeStyleTab : function TSTWindow_getTempTreeStyleTab(aTabBrowser) - { - return aTabBrowser.treeStyleTab || new TreeStyleTabBrowser(this, aTabBrowser); - }, - - initTabAttributes : function TSTWindow_initTabAttributes(aTab, aTabBrowser) - { - var b = aTabBrowser || this.getTabBrowserFromChild(aTab); - this.getTempTreeStyleTab(b).initTabAttributes(aTab); - }, - - initTabContents : function TSTWindow_initTabContents(aTab, aTabBrowser) - { - var b = aTabBrowser || this.getTabBrowserFromChild(aTab); - this.getTempTreeStyleTab(b).initTabContents(aTab); - }, - - initTabContentsOrder : function TSTWindow_initTabContentsOrder(aTab, aTabBrowser) - { - var b = aTabBrowser || this.getTabBrowserFromChild(aTab); - this.getTempTreeStyleTab(b).initTabContentsOrder(aTab); - }, - -/* Utilities */ - - stopRendering : function TSTWindow_stopRendering() - { - this.window['piro.sakura.ne.jp'].stopRendering.stop(); - }, - startRendering : function TSTWindow_startRendering() - { - this.window['piro.sakura.ne.jp'].stopRendering.start(); - }, - - getPropertyPixelValue : function TSTWindow_getPropertyPixelValue(aElementOrStyle, aProp) - { - var style = aElementOrStyle instanceof Ci.nsIDOMCSSStyleDeclaration ? - aElementOrStyle : - this.window.getComputedStyle(aElementOrStyle, null) ; - return Number(style.getPropertyValue(aProp).replace(/px$/, '')); - }, - - get isToolbarCustomizing() - { - var toolbox = this.browserToolbox; - return toolbox && toolbox.customizing; - }, - - get maximized() - { - var sizemode = this.document.documentElement.getAttribute('sizemode'); - return ( - this.window.fullScreen || - this.window.windowState == this.window.STATE_MAXIMIZED || - sizemode == 'maximized' || - sizemode == 'fullscreen' - ); - }, - - maxTabbarWidth : function TSTWindow_maxTabbarWidth(aWidth, aTabBrowser) - { - aTabBrowser = aTabBrowser || this.browser; - var safePadding = 20; // for window border, etc. - var windowWidth = this.maximized ? this.window.screen.availWidth - safePadding : this.window.outerWidth ; - var rootWidth = parseInt(this.document.documentElement.getAttribute('width') || 0); - var max = Math.max(windowWidth, rootWidth); - return Math.max(0, Math.min(aWidth, max * this.MAX_TABBAR_SIZE_RATIO)); - }, - - maxTabbarHeight : function TSTWindow_maxTabbarHeight(aHeight, aTabBrowser) - { - aTabBrowser = aTabBrowser || this.browser; - var safePadding = 20; // for window border, etc. - var windowHeight = this.maximized ? this.window.screen.availHeight - safePadding : this.window.outerHeight ; - var rootHeight = parseInt(this.document.documentElement.getAttribute('height') || 0); - var max = Math.max(windowHeight, rootHeight); - return Math.max(0, Math.min(aHeight, max * this.MAX_TABBAR_SIZE_RATIO)); - }, - - shouldOpenSearchResultAsChild : function TSTWindow_shouldOpenSearchResultAsChild(aTerm) - { - aTerm = aTerm.trim(); - - var mode = utils.getTreePref('autoAttach.searchResult'); - if (mode == this.kSEARCH_RESULT_ATTACH_ALWAYS) { - return true; - } - else if (!aTerm || mode == this.kSEARCH_RESULT_DO_NOT_ATTACH) { - return false; - } - - var selection = this.window.getBrowserSelection(); - return selection.trim() == aTerm; - }, - kSEARCH_RESULT_DO_NOT_ATTACH : 0, - kSEARCH_RESULT_ATTACH_IF_SELECTED : 1, - kSEARCH_RESULT_ATTACH_ALWAYS : 2, - - get isAutoHide() - { - return this.window.fullScreen ? - ( - prefs.getPref('browser.fullscreen.autohide') && - utils.getTreePref('tabbar.autoHide.mode.fullscreen') - ) : - utils.getTreePref('tabbar.autoHide.mode'); - }, - - get autoHideWindow() - { - if (!this._autoHideWindow) { - this._autoHideWindow = new AutoHideWindow(this.window); - } - return this._autoHideWindow; - }, - - get themeManager() - { - if (!this._themeManager) { - this._themeManager = new TreeStyleTabThemeManager(this.window); - } - return this._themeManager; - }, - -/* Initializing */ - - preInit : function TSTWindow_preInit() - { - if (this.preInitialized) - return; - this.preInitialized = true; - - var w = this.window; - w.removeEventListener('DOMContentLoaded', this, true); - if (w.location.href.indexOf('chrome://browser/content/browser.xul') != 0) - return; - - w.addEventListener('SSTabRestoring', this, true); - - w.TreeStyleTabWindowHelper.preInit(); - - // initialize theme - this.onPrefChange('extensions.treestyletab.tabbar.style'); - }, - preInitialized : false, - - init : function TSTWindow_init() - { - var w = this.window; - w.removeEventListener('load', this, false); - - w.addEventListener('unload', this, false); - - if ( - w.location.href.indexOf('chrome://browser/content/browser.xul') != 0 || - !this.browser - ) - return; - - if (this.initialized) - return; - - if (!this.preInitialized) { - this.preInit(); - } - w.removeEventListener('SSTabRestoring', this, true); - - var d = this.document; - d.addEventListener('popupshowing', this, false); - d.addEventListener('popuphiding', this, true); - d.addEventListener(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); - d.addEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); - d.addEventListener(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this, false); - d.addEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); - w.addEventListener('beforecustomization', this, true); - w.addEventListener('aftercustomization', this, false); - - w.messageManager.addMessageListener('SessionStore:restoreTabContentStarted', this); - - this.fullscreenObserver = new FullscreenObserver(this.window); - this.initUIShowHideObserver(); - - var appcontent = d.getElementById('appcontent'); - appcontent.addEventListener('SubBrowserAdded', this, false); - appcontent.addEventListener('SubBrowserRemoveRequest', this, false); - - w.addEventListener('UIOperationHistoryUndo:TabbarOperations', this, false); - w.addEventListener('UIOperationHistoryRedo:TabbarOperations', this, false); - - prefs.addPrefListener(this); - - this.initUninstallationListener(); - - w.TreeStyleTabWindowHelper.onBeforeBrowserInit(); - this.initTabBrowser(this.browser); - w.TreeStyleTabWindowHelper.onAfterBrowserInit(); - - this.processRestoredTabs(); - this.updateTabsOnTop(); - - // Init autohide service only if it have to be activated. - if (this.isAutoHide) - this.onPrefChange('extensions.treestyletab.tabbar.autoHide.mode'); - - this.onPrefChange('extensions.treestyletab.autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut'); - - this.initialized = true; - }, - initialized : false, - - initUninstallationListener : function TSTWindow_initUninstallationListener() - { - var restorePrefs = function() { - if (prefs.getPref('extensions.treestyletab.tabsOnTop.originalState')) { - prefs.clearPref('extensions.treestyletab.tabsOnTop.originalState'); - try { - this.browser.treeStyleTab.position = 'top'; - } - catch(e) { - } - this.window.TabsOnTop.enabled = true; - } - }.bind(this); - new UninstallationListener({ - id : 'treestyletab@piro.sakura.ne.jp', - onuninstalled : restorePrefs, - ondisabled : restorePrefs - }); - }, - - initTabBrowser : function TSTWindow_initTabBrowser(aTabBrowser) - { - if (aTabBrowser.localName != 'tabbrowser') - return; - (new TreeStyleTabBrowser(this, aTabBrowser)).init(); - }, - - updateAllTabsButton : function TSTWindow_updateAllTabsButton(aTabBrowser) - { - var d = this.document; - aTabBrowser = aTabBrowser || this.browser; - var allTabsButton = d.getElementById('alltabs-button') || - ( // Tab Mix Plus - utils.getTreePref('compatibility.TMP') && - d.getAnonymousElementByAttribute(aTabBrowser.mTabContainer, 'anonid', 'alltabs-button') - ); - - if (allTabsButton && allTabsButton.hasChildNodes() && aTabBrowser.treeStyleTab) - allTabsButton.firstChild.setAttribute('position', aTabBrowser.treeStyleTab.isVertical ? 'before_start' : 'after_end' ); - }, - - updateAllTabsPopup : function TSTWindow_updateAllTabsPopup(aEvent) - { - if (!utils.getTreePref('enableSubtreeIndent.allTabsPopup')) - return; - - Array.forEach(aEvent.originalTarget.childNodes, function(aItem) { - if (aItem.classList.contains('alltabs-item') && 'tab' in aItem) - aItem.style.marginLeft = aItem.tab.getAttribute(this.kNEST) + 'em'; - }, this); - }, - - initUIShowHideObserver : function TSTWindow_initUIShowHideObserver() - { - this.rootElementObserver = new BrowserUIShowHideObserver(this, this.document.documentElement); - - var toolbox = this.browserToolbox; - if (toolbox) - this.browserToolboxObserver = new BrowserUIShowHideObserver(this, toolbox); - - var browserBox = this.browserBox; - if (browserBox) - this.browserBoxObserver = new BrowserUIShowHideObserver(this, browserBox); - - var bottomBox = this.browserBottomBox; - if (bottomBox) - this.browserBottomBoxObserver = new BrowserUIShowHideObserver(this, bottomBox); - }, - - destroy : function TSTWindow_destroy() - { - var w = this.window; - if (this.browser) { - this.base.inWindowDestoructionProcess = true; - try { - w.removeEventListener('unload', this, false); - - this.autoHideWindow.destroy(); - delete this._autoHideWindow; - - this.themeManager.destroy(); - delete this._themeManager; - - this.browser.treeStyleTab.saveCurrentState(); - this.destroyTabBrowser(this.browser); - - this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOHIDE); - this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); - - let d = this.document; - d.removeEventListener('popupshowing', this, false); - d.removeEventListener('popuphiding', this, true); - d.removeEventListener(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); - d.removeEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); - d.removeEventListener(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this, false); - d.removeEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); - w.removeEventListener('beforecustomization', this, true); - w.removeEventListener('aftercustomization', this, false); - - w.messageManager.removeMessageListener('SessionStore:restoreTabContentStarted', this); - - this.fullscreenObserver.destroy(); - delete this.fullscreenObserver; - - this.rootElementObserver.destroy(); - delete this.rootElementObserver; - - if (this.browserToolboxObserver) { - this.browserToolboxObserver.destroy(); - delete this.browserToolboxObserver; - } - if (this.browserBoxObserver) { - this.browserBoxObserver.destroy(); - delete this.browserBoxObserver; - } - if (this.browserBottomBoxObserver) { - this.browserBottomBoxObserver.destroy(); - delete this.browserBottomBoxObserver; - } - - for (let i = 0, maxi = this._tabFocusAllowance.length; i < maxi; i++) - { - w.removeEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this._tabFocusAllowance[i], false); - } - - var appcontent = d.getElementById('appcontent'); - appcontent.removeEventListener('SubBrowserAdded', this, false); - appcontent.removeEventListener('SubBrowserRemoveRequest', this, false); - - w.removeEventListener('UIOperationHistoryUndo:TabbarOperations', this, false); - w.removeEventListener('UIOperationHistoryRedo:TabbarOperations', this, false); - - prefs.removePrefListener(this); - } - catch(e) { - throw e; - } - finally { - this.base.inWindowDestoructionProcess = false; - } - } - - delete w.TreeStyleTabService; - delete this.window; - delete this.document; - }, - - destroyTabBrowser : function TSTWindow_destroyTabBrowser(aTabBrowser) - { - if (aTabBrowser.localName != 'tabbrowser') - return; - aTabBrowser.treeStyleTab.destroy(); - delete aTabBrowser.treeStyleTab; - }, - -/* Event Handling */ - - handleEvent : function TSTWindow_handleEvent(aEvent) - { - switch (aEvent.type) - { - case 'DOMContentLoaded': - return this.preInit(); - - case 'load': - return this.init(); - - case 'unload': - return this.destroy(); - - case 'SSTabRestoring': - return this.onTabRestored(aEvent); - - case 'popupshowing': - this.onPopupShown(aEvent.originalTarget); - if ((aEvent.originalTarget.getAttribute('anonid') || aEvent.originalTarget.id) == 'alltabs-popup') - this.updateAllTabsPopup(aEvent); - return; - - case 'popuphiding': - return this.onPopupHidden(aEvent.originalTarget); - - case this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED: - return this.updateAeroPeekPreviews(); - - case this.kEVENT_TYPE_TABBAR_POSITION_CHANGED: - case this.kEVENT_TYPE_TABBAR_STATE_CHANGED: - return this.updateTabsOnTop(); - - case this.kEVENT_TYPE_FOCUS_NEXT_TAB: - return this.onFocusNextTab(aEvent); - - case 'keydown': - return this.onKeyDown(aEvent); - - case 'keyup': - case 'keypress': - return this.onKeyRelease(aEvent); - - case 'blur': - return this.simulateKeyRelease(); - - case 'mousedown': - return this.onTabbarResizeStart(aEvent); - - case 'mouseup': - return this.onTabbarResizeEnd(aEvent); - - case 'mousemove': - return this.onTabbarResizing(aEvent); - - case 'dblclick': - return this.onTabbarReset(aEvent); - - case 'click': - return this.handleNewTabActionOnButton(aEvent); - - - case 'beforecustomization': - this.window.TreeStyleTabWindowHelper.destroyToolbarItems(); - return; - - case 'aftercustomization': - this.window.TreeStyleTabWindowHelper.initToolbarItems(); - return; - - - case 'SubBrowserAdded': - return this.initTabBrowser(aEvent.originalTarget.browser); - - case 'SubBrowserRemoveRequest': - return this.destroyTabBrowser(aEvent.originalTarget.browser); - - case 'UIOperationHistoryUndo:TabbarOperations': - switch (aEvent.entry.name) - { - case 'treestyletab-changeTabbarPosition': - this.position = aEvent.entry.data.oldPosition; - return; - case 'treestyletab-changeTabbarPosition-private': - aEvent.entry.data.target.treeStyleTab.position = aEvent.entry.data.oldPosition; - return; - } - return; - - case 'UIOperationHistoryRedo:TabbarOperations': - switch (aEvent.entry.name) - { - case 'treestyletab-changeTabbarPosition': - this.position = aEvent.entry.data.newPosition; - return; - case 'treestyletab-changeTabbarPosition-private': - aEvent.entry.data.target.treeStyleTab.position = aEvent.entry.data.newPosition; - return; - } - return; - } - }, - - keyEventListening : false, - keyEventListeningFlags : 0, - - LISTEN_FOR_AUTOHIDE : 1, - LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE : 2, - - startListenKeyEventsFor : function TSTWindow_startListenKeyEventsFor(aReason) - { - if (this.keyEventListeningFlags & aReason) - return; - if (!this.keyEventListening) { - let w = this.window; - w.addEventListener('keydown', this, true); - w.addEventListener('keyup', this, true); - w.addEventListener('keypress', this, true); - w.addEventListener('blur', this, true); - this.keyEventListening = true; - } - this.keyEventListeningFlags |= aReason; - }, - - endListenKeyEventsFor : function TSTWindow_endListenKeyEventsFor(aReason) - { - if (!(this.keyEventListeningFlags & aReason)) - return; - this.keyEventListeningFlags ^= aReason; - if (!this.keyEventListeningFlags && this.keyEventListening) { - let w = this.window; - w.removeEventListener('keydown', this, true); - w.removeEventListener('keyup', this, true); - w.removeEventListener('keypress', this, true); - w.removeEventListener('blur', this, true); - this.keyEventListening = false; - } - }, - - onKeyDown : function TSTWindow_onKeyDown(aEvent) - { - /** - * On Mac OS X, default accel key is the Command key (metaKey), but - * Cmd-Tab is used to switch applications by the OS itself. So Firefox - * uses Ctrl-Tab to switch tabs on all platforms. - */ - // this.accelKeyPressed = this.isAccelKeyPressed(aEvent); - this.accelKeyPressed = aEvent.ctrlKey || aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL; - - var left = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT; - var right = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT; - var up = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP; - var down = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN; - if ( - Services.focus.focusedElement == this.browser.selectedTab && - (up || down || left || right) - ) - this.arrowKeyEventOnTab = { - keyCode : aEvent.keyCode, - left : left, - right : right, - up : up, - down : down, - altKey : aEvent.altKey, - ctrlKey : aEvent.ctrlKey, - metaKey : aEvent.metaKey, - shiftKey : aEvent.shiftKey - }; - - var b = this.browser; - var data = { - sourceEvent : aEvent - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, b, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN.replace(/^nsDOM/, ''), b, true, false, data); - }, - accelKeyPressed : false, - arrowKeyEventOnTab : null, - - onKeyRelease : function TSTWindow_onKeyRelease(aEvent) - { - var b = this.browser; - if (!b || !b.treeStyleTab) - return; - var sv = b.treeStyleTab; - - var scrollDown, - scrollUp; - - // this.accelKeyPressed = this.isAccelKeyPressed(aEvent); - this.accelKeyPressed = aEvent.ctrlKey || aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL; - this.window.setTimeout(function(aSelf) { - aSelf.arrowKeyEventOnTab = null; - }, 10, this); - - var standBy = scrollDown = scrollUp = (!aEvent.altKey && this.accelKeyPressed); - - scrollDown = scrollDown && ( - !aEvent.shiftKey && - ( - aEvent.keyCode == aEvent.DOM_VK_TAB || - aEvent.keyCode == aEvent.DOM_VK_PAGE_DOWN - ) - ); - - scrollUp = scrollUp && ( - aEvent.shiftKey ? (aEvent.keyCode == aEvent.DOM_VK_TAB) : (aEvent.keyCode == aEvent.DOM_VK_PAGE_UP) - ); - - var onlyShiftKey = (!aEvent.shiftKey && aEvent.keyCode == 16 && (aEvent.type == 'keyup' || aEvent.charCode == 0)); - - var data = { - scrollDown : scrollDown, - scrollUp : scrollUp, - standBy : standBy, - onlyShiftKey : onlyShiftKey, - sourceEvent : aEvent - }; - - if ( - scrollDown || - scrollUp || - ( // when you release "shift" key - standBy && onlyShiftKey - ) - ) { - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, b, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START.replace(/^nsDOM/, ''), b, true, false, data); - return; - } - - if (aEvent.type == 'keypress' ? - // ignore keypress on Ctrl-R, Ctrl-T, etc. - aEvent.charCode != 0 : - // ignore keyup not on the Ctrl key - aEvent.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_CONTROL - ) - return; - - // when you just release accel key... - - /* PUBLIC API */ - let (event) { - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, b, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END.replace(/^nsDOM/, ''), b, true, false, data); - } - - if (this._tabShouldBeExpandedAfterKeyReleased) { - let tab = this._tabShouldBeExpandedAfterKeyReleased; - if (this.hasChildTabs(tab) && - this.isSubtreeCollapsed(tab)) { - this.getTabBrowserFromChild(tab) - .treeStyleTab - .collapseExpandTreesIntelligentlyFor(tab); - } - } - this._tabShouldBeExpandedAfterKeyReleased = null; - }, - // When the window lose its focus, we cannot detect any key-release events. - // So we have to simulate key-release event manually. - // See: https://github.com/piroor/treestyletab/issues/654 - simulateKeyRelease : function TSTWindow_simulateKeyRelease() - { - if (!this.accelKeyPressed) - return; - - this.accelKeyPressed = false; - var data = { - scrollDown : false, - scrollUp : false, - standBy : false, - onlyShiftKey : false, - sourceEvent : null - }; - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this.browser, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END.replace(/^nsDOM/, ''), this.browser, true, false, data); - }, - - get shouldListenKeyEventsForAutoExpandByFocusChange() - { - return !this.ctrlTabPreviewsEnabled && - ( - utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut') || - utils.getTreePref('autoCollapseExpandSubtreeOnSelect') - ); - }, - - get ctrlTabPreviewsEnabled() - { - return 'allTabs' in this.window && - prefs.getPref('browser.ctrlTab.previews'); - }, - - receiveMessage : function TSTWindow_receiveMessage(aMessage) - { - var browser = aMessage.target; - var tabbrowser = this.getTabBrowserFromChild(browser); - if (!tabbrowser) - return; - var tab = tabbrowser.treeStyleTab.getTabFromBrowser(browser); - if (!tab) - return; - - switch (aMessage.name) - { - case 'SessionStore:restoreTabContentStarted': - return tabbrowser.treeStyleTab.onRestoreTabContentStarted(tab); - } - }, - - onTabbarResizeStart : function TSTWindow_onTabbarResizeStart(aEvent) - { - if (aEvent.button != 0) - return; - - if (!this.isEventFiredOnGrippy(aEvent)) - aEvent.stopPropagation(); - - if ('setCapture' in aEvent.currentTarget) - aEvent.currentTarget.setCapture(true); - - aEvent.currentTarget.addEventListener('mousemove', this, false); - - var b = this.getTabBrowserFromChild(aEvent.currentTarget); - var box = aEvent.currentTarget.id == 'treestyletab-tabbar-resizer-splitter' ? - this.getTabStrip(b) : - b.treeStyleTab.tabStripPlaceHolder || b.tabContainer ; - this.tabbarResizeStartWidth = box.boxObject.width; - this.tabbarResizeStartHeight = box.boxObject.height; - this.tabbarResizeStartX = aEvent.screenX; - this.tabbarResizeStartY = aEvent.screenY; - }, - onTabbarResizeEnd : function TSTWindow_onTabbarResizeEnd(aEvent) - { - if (this.tabbarResizeStartWidth < 0) - return; - - var target = aEvent.currentTarget; - var b = this.getTabBrowserFromChild(target); - - aEvent.stopPropagation(); - if ('releaseCapture' in target) - target.releaseCapture(); - - target.removeEventListener('mousemove', this, false); - - this.tabbarResizeStartWidth = -1; - this.tabbarResizeStartHeight = -1; - this.tabbarResizeStartX = -1; - this.tabbarResizeStartY = -1; - - this.Deferred.next(function() { - b.treeStyleTab.fixTooNarrowTabbar(); - }).error(this.defaultDeferredErrorHandler); - }, - onTabbarResizing : function TSTWindow_onTabbarResizing(aEvent) - { - var target = aEvent.currentTarget; - var b = this.getTabBrowserFromChild(target); - - var expanded = target.id == 'treestyletab-tabbar-resizer-splitter'; - if (expanded) - aEvent.stopPropagation(); - - var width = this.tabbarResizeStartWidth; - var height = this.tabbarResizeStartHeight; - var pos = b.treeStyleTab.position; - if (b.treeStyleTab.isVertical) { - let delta = aEvent.screenX - this.tabbarResizeStartX; - width += (pos == 'left' ? delta : -delta ); - width = this.maxTabbarWidth(width, b); - if (expanded || b.treeStyleTab.autoHide.expanded) { - this.setPrefForActiveWindow(function() { - utils.setTreePref('tabbar.width', width); - }); - if (b.treeStyleTab.autoHide.mode == b.treeStyleTab.autoHide.kMODE_SHRINK && - b.treeStyleTab.tabStripPlaceHolder) - b.treeStyleTab.tabStripPlaceHolder.setAttribute('width', utils.getTreePref('tabbar.shrunkenWidth')); - } - else { - this.setPrefForActiveWindow(function() { - utils.setTreePref('tabbar.shrunkenWidth', width); - }); - } - } - else { - let delta = aEvent.screenY - this.tabbarResizeStartY; - height += (pos == 'top' ? delta : -delta ); - this.setPrefForActiveWindow(function() { - utils.setTreePref('tabbar.height', this.maxTabbarHeight(height, b)); - }); - } - b.treeStyleTab.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_TABBAR_RESIZE); - }, - tabbarResizeStartWidth : -1, - tabbarResizeStartHeight : -1, - tabbarResizeStartX : -1, - tabbarResizeStartY : -1, - - onTabbarReset : function TSTWindow_onTabbarReset(aEvent) - { - if (aEvent.button != 0) - return; - var b = this.getTabBrowserFromChild(aEvent.currentTarget); - if (b) { - b.treeStyleTab.resetTabbarSize(); - aEvent.stopPropagation(); - } - }, - - onFocusNextTab : function TSTWindow_onFocusNextTab(aEvent) - { - var tab = aEvent.originalTarget; - var b = this.getTabBrowserFromChild(tab); - if ( - prefs.getPref('browser.tabs.selectOwnerOnClose') && - tab.owner && - ( - !b._removingTabs || - b._removingTabs.indexOf(tab.owner) < 0 - ) - ) - aEvent.preventDefault(); - }, - - showHideSubtreeMenuItem : function TSTWindow_showHideSubtreeMenuItem(aMenuItem, aTabs) - { - if (!aMenuItem || - aMenuItem.getAttribute('hidden') == 'true' || - !aTabs || - !aTabs.length) - return; - - var hasSubtree = false; - for (var i = 0, maxi = aTabs.length; i < maxi; i++) - { - if (!this.hasChildTabs(aTabs[i])) - continue; - hasSubtree = true; - break; - } - if (hasSubtree) - aMenuItem.removeAttribute('hidden'); - else - aMenuItem.setAttribute('hidden', true); - }, - showHideSubTreeMenuItem : function(...aArgs) { - return this.showHideSubtreeMenuItem.apply(this, aArgs); - }, // obsolete, for backward compatibility - - updateAeroPeekPreviews : function TSTWindow_updateAeroPeekPreviews() - { - var w = this.window; - if ( - this.updateAeroPeekPreviewsTimer || - !prefs.getPref('browser.taskbar.previews.enable') || - !utils.getTreePref('taskbarPreviews.hideCollapsedTabs') || - !('Win7Features' in w) || - !w.Win7Features || - !this.AeroPeek || - !this.AeroPeek.windows - ) - return; - - this.updateAeroPeekPreviewsTimer = w.setTimeout(function(aSelf) { - aSelf.updateAeroPeekPreviewsTimer = null; - try { - aSelf.updateAeroPeekPreviewsInternal(); - } - catch(e) { - dump(e+'\n'); - aSelf.updateAeroPeekPreviews(); - } - }, 250, this); - }, - updateAeroPeekPreviewsTimer : null, - updateAeroPeekPreviewsInternal : function TSTWindow_updateAeroPeekPreviewsInternal() - { - if ( - !prefs.getPref('browser.taskbar.previews.enable') || - !utils.getTreePref('taskbarPreviews.hideCollapsedTabs') - ) - return; - - this.AeroPeek.windows.some(function(aTabWindow) { - if (aTabWindow.win == this.window) { - let previews = aTabWindow.previews; - for (let i = 0, maxi = previews.length; i < maxi; i++) - { - let preview = previews[i]; - if (!preview) - continue; - let tab = preview.controller.wrappedJSObject.tab; - preview.visible = !this.isCollapsed(tab); - } - this.AeroPeek.checkPreviewCount(); - return true; - } - return false; - }, this); - }, - - updateTabsOnTop : function TSTWindow_updateTabsOnTop() - { - if ( - this.isPopupWindow || - this.tabsOnTopChangingByUI || - this.tabsOnTopChangingByTST - ) - return; - - var TabsOnTop = this.window.TabsOnTop; - var TabsInTitlebar = this.window.TabsInTitlebar; - var isTopTabbar = this.browser.treeStyleTab.position == 'top'; - - this.tabsOnTopChangingByTST = true; - - try { - if (TabsOnTop) { - let originalState = utils.getTreePref('tabsOnTop.originalState'); - if (originalState === null) { - let current = prefs.getDefaultPref('browser.tabs.onTop') === null ? - TabsOnTop.enabled : - prefs.getPref('browser.tabs.onTop') ; - utils.setTreePref('tabsOnTop.originalState', originalState = current); - } - - if (!isTopTabbar || !this.browser.treeStyleTab.fixed) { - if (TabsOnTop.enabled) - TabsOnTop.enabled = false; - } - else { - if (TabsOnTop.enabled != originalState) - TabsOnTop.enabled = originalState; - utils.clearTreePref('tabsOnTop.originalState'); - } - } - if (TabsInTitlebar) { - let allowed = isTopTabbar && this.browser.treeStyleTab.fixed; - if ( - (this.window.TabsOnBottom && utils.getTreePref('compatibility.TabsOnBottom')) || - ('navbarontop' in this.window && utils.getTreePref('compatibility.NavbarOnTitlebar')) || - ('classicthemerestorerjs' in this.window && utils.getTreePref('compatibility.ClassicThemeRestorer')) - ) - allowed = true; - TabsInTitlebar.allowedBy('TreeStyleTab-tabsOnTop', allowed); - } - } - finally { - this.tabsOnTopChangingByTST = false; - } - }, - - onPopupShown : function TSTWindow_onPopupShown(aPopup) - { - if (!aPopup.boxObject || - this.evaluateXPath( - 'parent::*/ancestor-or-self::*[local-name()="tooltip" or local-name()="panel" or local-name()="popup" or local-name()="menupopup"]', - aPopup, - Ci.nsIDOMXPathResult.BOOLEAN_TYPE - ).booleanValue) - return; - - this.window.setTimeout(function(aSelf) { - if ((!aPopup.boxObject.width && !aPopup.boxObject.height) || - aPopup.boxObject.popupState == 'closed') - return; - - var id = aPopup.id; - var item = id && aSelf.document.getElementById(id) ? id : aPopup ; - var index = aSelf._shownPopups.indexOf(item); - if (index < 0) - aSelf._shownPopups.push(item); - }, 10, this); - }, - - onPopupHidden : function TSTWindow_onPopupHidden(aPopup) - { - var id = aPopup.id; - aPopup = id && this.document.getElementById(id) ? id : aPopup ; - var index = this._shownPopups.indexOf(aPopup); - if (index > -1) - this._shownPopups.splice(index, 1); - }, - - isPopupShown : function TSTWindow_isPopupShown() - { - this._shownPopups = this._shownPopups.filter(function(aItem) { - if (typeof aItem == 'string') - aItem = this.document.getElementById(aItem); - return ( - aItem && - aItem.getAttribute(this.kIGNORE_POPUP_STATE) != 'true' && - aItem.boxObject && - (aItem.boxObject.width || aItem.boxObject.height) && - aItem.state != 'closed' - ); - }, this); - return this._shownPopups.length > 0; - }, - - onBeforeNewTabCommand : function TSTWindow_onBeforeNewTabCommand(aTabBrowser) - { - var self = this.windowService || this; - if (self._clickEventOnNewTabButtonHandled) - return; - - var b = aTabBrowser || this.browser; - this.readyToOpenRelatedTabAs(b.selectedTab, utils.getTreePref('autoAttach.newTabCommand')); - }, - - handleNewTabActionOnButton : function TSTWindow_handleNewTabActionOnButton(aEvent) - { - // ignore non new-tab commands (middle click, Ctrl-click) - if (aEvent.button != 1 && (aEvent.button != 0 || !this.isAccelKeyPressed(aEvent))) - return; - - var newTabButton = this.getNewTabButtonFromEvent(aEvent); - if (newTabButton) { - this.readyToOpenRelatedTabAs(this.browser.selectedTab, utils.getTreePref('autoAttach.newTabButton')); - let self = this.windowService || this; - self._clickEventOnNewTabButtonHandled = true; - this.Deferred.next(function() { - self._clickEventOnNewTabButtonHandled = false; - }); - } - else if (aEvent.target.id == 'urlbar-go-button' || aEvent.target.id == 'go-button') { - this.readyToOpenRelatedTabAs(this.browser.selectedTab, utils.getTreePref('autoAttach.goButton')); - } - }, - _clickEventOnNewTabButtonHandled : false, - - onBeforeTabDuplicate : function TSTWindow_onBeforeTabDuplicate(aTab, aWhere, aDelta) - { - if (aWhere && aWhere.indexOf('tab') != 0) - return; - - var b = this.getTabBrowserFromChild(aTab) || this.browser; - var behaviorPref = !aDelta ? 'autoAttach.duplicateTabCommand' : - aDelta < 0 ? 'autoAttach.duplicateTabCommand.back' : - 'autoAttach.duplicateTabCommand.forward' - var behavior = utils.getTreePref(behaviorPref); - this.readyToOpenRelatedTabAs(aTab || b.selectedTab, behavior); - }, - - onBeforeOpenLink : function TSTWindow_onBeforeOpenLink(aWhere, aOwner) - { - if (aWhere == 'tab' || aWhere == 'tabshifted') - this.handleNewTabFromCurrent(aOwner); - }, - - onBeforeOpenLinkWithTab : function TSTWindow_onBeforeOpenLinkWithTab(aTab, aFromChrome) - { - if (!aFromChrome && aTab && !this.checkToOpenChildTab(aTab)) - this.handleNewTabFromCurrent(aTab); - }, - - onBeforeOpenNewTabByThirdParty : function TSTWindow_onBeforeOpenNewTabByThirdParty(aOwner) - { - if (!this.checkToOpenChildTab(aOwner)) - this.handleNewTabFromCurrent(aOwner); - }, - - onBeforeBrowserAccessOpenURI : function TSTWindow_onBeforeBrowserAccessOpenURI(aOpener, aWhere) - { - if (aOpener && - this.getTabFromFrame(aOpener.top) && - aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) - this.handleNewTabFromCurrent(aOpener); - }, - - onBeforeViewMedia : function TSTWindow_onBeforeViewMedia(aEvent, aOwner) - { - if (String(this.window.whereToOpenLink(aEvent, false, true)).indexOf('tab') == 0) - this.handleNewTabFromCurrent(aOwner); - }, - - onBeforeBrowserSearch : function TSTWindow_onBeforeBrowserSearch(aTerm, aForceNewTab) - { - if ((arguments.length == 1 || aForceNewTab) && - this.shouldOpenSearchResultAsChild(aTerm)) - this.handleNewTabFromCurrent(); - }, - -/* Tree Style Tabの初期化が行われる前に復元されたセッションについてツリー構造を復元 */ - - onTabRestored : function TSTWindow_onTabRestored(aEvent) - { - this._restoringTabs.push(aEvent.originalTarget); - }, - - processRestoredTabs : function TSTWindow_processRestoredTabs() - { - for (let i = 0, maxi = this._restoringTabs.length; i < maxi; i++) - { - let tab = this._restoringTabs[i]; - try { - let b = this.getTabBrowserFromChild(aTab); - if (b) - b.treeStyleTab.handleRestoredTab(aTab); - } - catch(e) { - } - } - this._restoringTabs = []; - }, - -/* Commands */ - - setTabbarWidth : function TSTWindow_setTabbarWidth(aWidth, aForceExpanded) /* PUBLIC API */ - { - this.browser.treeStyleTab.autoHide.setWidth(aWidth, aForceExpanded); - }, - - setContentWidth : function TSTWindow_setContentWidth(aWidth, aKeepWindowSize) /* PUBLIC API */ - { - var w = this.window; - var treeStyleTab = this.browser.treeStyleTab; - var strip = treeStyleTab.tabStrip; - var tabbarWidth = treeStyleTab.splitterWidth + (treeStyleTab.isVertical ? strip.boxObject.width : 0 ); - var contentWidth = this.browser.boxObject.width - tabbarWidth; - if (aKeepWindowSize || - w.fullScreen || - w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) { - this.setTabbarWidth(Math.max(10, this.browser.boxObject.width - aWidth)); - } - else if (tabbarWidth + aWidth <= w.screen.availWidth) { - w.resizeBy(aWidth - contentWidth, 0); - } - else { - w.resizeBy(w.screen.availWidth - w.outerWidth, 0); - this.setTabbarWidth(this.browser.boxObject.width - aWidth); - } - }, - - toggleAutoHide : function TSTWindow_toggleAutoHide(aTabBrowser) /* PUBLIC API, for backward compatibility */ - { - this.autoHideWindow.toggleMode(aTabBrowser || this.browser); - }, - - toggleFixed : function TSTWindow_toggleFixed(aTabBrowser) /* PUBLIC API */ - { - var b = aTabBrowser || this.browser; - var orient = b.treeStyleTab.isVertical ? 'vertical' : 'horizontal' ; - - var newFixed = b.getAttribute(this.kFIXED+'-'+orient) != 'true'; - this.setTabbrowserAttribute(this.kFIXED+'-'+orient, newFixed || null, b); - this.setPrefForActiveWindow(function() { - b.treeStyleTab.fixed = newFixed; - utils.setTreePref('tabbar.fixed.'+orient, newFixed); - }); - - b.treeStyleTab.updateTabbarState(); - }, - - removeTabSubtree : function TSTWindow_removeTabSubtree(aTabOrTabs, aOnlyChildren) - { - var tabs = this.gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren); - if (!this.warnAboutClosingTabs(tabs.length)) - return; - - if (aOnlyChildren) - tabs = this.gatherSubtreeMemberTabs(aTabOrTabs); - - var allSubtrees = this.splitTabsToSubtrees(tabs); - for (let i = 0, maxi = allSubtrees.length; i < maxi; i++) - { - let subtreeTabs = allSubtrees[i]; - if (!this.fireTabSubtreeClosingEvent(subtreeTabs[0], subtreeTabs)) - continue; - let b = this.getTabBrowserFromChild(subtreeTabs[0]); - if (aOnlyChildren) - subtreeTabs = subtreeTabs.slice(1); - if (!subtreeTabs.length) - continue; - this.stopRendering(); - this.markAsClosedSet(subtreeTabs); - for (let i = subtreeTabs.length-1; i > -1; i--) - { - b.removeTab(subtreeTabs[i], { animate : true }); - } - this.startRendering(); - this.fireTabSubtreeClosedEvent(b, subtreeTabs[0], subtreeTabs) - } - }, - removeTabSubTree : function(...aArgs) { - return this.removeTabSubtree.apply(this, aArgs); - }, // obsolete, for backward compatibility - - fireTabSubtreeClosingEvent : function TSTWindow_fireTabSubtreeClosingEvent(aParentTab, aClosedTabs) - { - var b = this.getTabBrowserFromChild(aParentTab); - var data = { - parent : aParentTab, - tabs : aClosedTabs - }; - var canClose = ( - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSING, b, true, true, data) && - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSING.replace(/^nsDOM/, ''), b, true, true, data) - ); - return canClose; - }, - - fireTabSubtreeClosedEvent : function TSTWindow_fireTabSubtreeClosedEvent(aTabBrowser, aParentTab, aClosedTabs) - { - aClosedTabs = aClosedTabs.filter(function(aTab) { return !aTab.parentNode; }); - var data = { - parent : aParentTab, - tabs : aClosedTabs - }; - - /* PUBLIC API */ - this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSED, aTabBrowser, true, false, data); - // for backward compatibility - this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSED.replace(/^nsDOM/, ''), aTabBrowser, true, false, data); - }, - - warnAboutClosingTabSubtreeOf : function TSTWindow_warnAboutClosingTabSubtreeOf(aTab) - { - if (!this.shouldCloseTabSubtreeOf(aTab)) - return true; - - var tabs = [aTab].concat(this.getDescendantTabs(aTab)); - return this.warnAboutClosingTabs(tabs.length); - }, - warnAboutClosingTabSubTreeOf : function(...aArgs) { - return this.warnAboutClosingTabSubtreeOf.apply(this, aArgs); - }, // obsolete, for backward compatibility - - warnAboutClosingTabs : function TSTWindow_warnAboutClosingTabs(aTabsCount) - { - if ( - aTabsCount <= 1 || - !prefs.getPref('browser.tabs.warnOnClose') - ) - return true; - var checked = { value:true }; - var w = this.window; - w.focus(); - var message = w.PluralForm.get(aTabsCount, utils.tabbrowserBundle.getString('tabs.closeWarningMultiple')).replace('#1', aTabsCount); - var shouldClose = Services.prompt.confirmEx(w, - utils.tabbrowserBundle.getString('tabs.closeWarningTitle'), - message, - (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + - (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1), - utils.tabbrowserBundle.getString('tabs.closeButtonMultiple'), - null, null, - utils.tabbrowserBundle.getString('tabs.closeWarningPromptMe'), - checked - ) == 0; - if (shouldClose && !checked.value) - prefs.setPref('browser.tabs.warnOnClose', false); - return shouldClose; - }, - - reloadTabSubtree : function TSTWindow_reloadTabSubtree(aTabOrTabs, aOnlyChildren) - { - var tabs = this.gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren); - var b = this.getTabBrowserFromChild(tabs[0]); - for (var i = tabs.length-1; i > -1; i--) - { - b.reloadTab(tabs[i]); - } - }, - reloadTabSubTree : function(...aArgs) { - return this.reloadTabSubtree.apply(this, aArgs); - }, // obsolete, for backward compatibility - - createSubtree : function TSTWindow_createSubtree(aTabs) - { - var rootTabs = this.getRootTabs(aTabs); - - var parent = this.getParentTab(aTabs[0]); - var next = aTabs[0]; - while ( - (next = this.getNextSiblingTab(next)) && - aTabs.indexOf(next) > -1 - ); - - var b = this.getTabBrowserFromChild(aTabs[0]); - - rootTabs.forEach(function(aRootTab) { - var parentTab = this.getParentTab(aRootTab); - var descendantTabs = this.getDescendantTabs(aRootTab); - descendantTabs.reverse().forEach(function(aDescendantTab) { - var inTargets = aTabs.indexOf(aDescendantTab) > -1; - var parentInTargets = aTabs.indexOf(this.getParentTab(aDescendantTab)) > -1; - if (inTargets || (inTargets == parentInTargets)) - return; - if (parentTab) - b.treeStyleTab.attachTabTo(aDescendantTab, parentTab, { - dontExpand : true, - dontMove : true, - insertBefore : this.getNextSiblingTab(aRootTab) - }); - else - b.treeStyleTab.detachTab(aDescendantTab); - }, this); - }, this); - - aTabs = rootTabs; - - var shouldCreateGroup = aTabs.length > 1 && utils.getTreePref('createSubtree.underParent'); - var root = shouldCreateGroup ? - b.addTab(this.getGroupTabURI({ - temporary: utils.getTreePref('createSubtree.underParent.temporaryGroup') - })) : - aTabs.shift() ; - var self = this; - this.Deferred.next(function(self) { - if (shouldCreateGroup) { - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - b.treeStyleTab.attachTabTo(tab, root); - b.treeStyleTab.collapseExpandTab(tab, false); - } - } - if (parent) { - b.treeStyleTab.attachTabTo(root, parent, { - insertBefore : next - }); - } - else if (next) { - b.treeStyleTab.moveTabSubtreeTo(root, next._tPos); - } - }).error(this.defaultDeferredErrorHandler); - }, - createSubTree : function(...aArgs) { - return this.createSubtree.apply(this, aArgs); - }, // obsolete, for backward compatibility - - canCreateSubtree : function TSTWindow_canCreateSubtree(aTabs) - { - var rootTabs = this.getRootTabs(aTabs); - if (rootTabs.length == 1) { - let descendants = this.getDescendantTabs(rootTabs[0]); - // are they already grouped? - // if it is a partial selection, I can create new group. - return (descendants.some(function(aDescendantTab) { - return aTabs.indexOf(aDescendantTab) < 0; - }, this)); - } - return true; - }, - canCreateSubTree : function(...aArgs) { - return this.canCreateSubtree.apply(this, aArgs); - }, // obsolete, for backward compatibility - - getRootTabs : function TSTWindow_getRootTabs(aTabs) - { - var roots = []; - if (!aTabs || !aTabs.length) - return roots; - aTabs = this.cleanUpTabsArray(aTabs); - for (let i = 0, maxi = aTabs.length; i < maxi; i++) - { - let tab = aTabs[i]; - let parent = this.getParentTab(tab); - if (parent && aTabs.indexOf(parent) > -1) - continue; - roots.push(tab); - } - return roots; - }, - - collapseExpandAllSubtree : function TSTWindow_collapseExpandAllSubtree(aCollapse) - { - Services.obs.notifyObservers( - this.window, - this.kTOPIC_COLLAPSE_EXPAND_ALL, - (aCollapse ? 'collapse' : 'open' ) - ); - }, - - promoteTab : function TSTWindow_promoteTab(aTab) /* PUBLIC API */ - { - var b = this.getTabBrowserFromChild(aTab); - var sv = b.treeStyleTab; - - var parent = sv.getParentTab(aTab); - if (!parent) - return; - - var nextSibling = sv.getNextSiblingTab(parent); - - var grandParent = sv.getParentTab(parent); - if (grandParent) { - sv.attachTabTo(aTab, grandParent, { - insertBefore : nextSibling - }); - } - else { - sv.detachTab(aTab); - let index = nextSibling ? nextSibling._tPos : b.mTabContainer.childNodes.length ; - if (index > aTab._tPos) - index--; - b.moveTabTo(aTab, index); - } - }, - - promoteCurrentTab : function TSTWindow_promoteCurrentTab() /* PUBLIC API */ - { - this.promoteTab(this.browser.selectedTab); - }, - - demoteTab : function TSTWindow_demoteTab(aTab) /* PUBLIC API */ - { - var b = this.getTabBrowserFromChild(aTab); - var sv = b.treeStyleTab; - - var previous = this.getPreviousSiblingTab(aTab); - if (previous) - sv.attachTabTo(aTab, previous); - }, - - demoteCurrentTab : function TSTWindow_demoteCurrentTab() /* PUBLIC API */ - { - this.demoteTab(this.browser.selectedTab); - }, - - expandTreeAfterKeyReleased : function TSTWindow_expandTreeAfterKeyReleased(aTab) - { - if (utils.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut')) - return; - this._tabShouldBeExpandedAfterKeyReleased = aTab || null; - }, - _tabShouldBeExpandedAfterKeyReleased : null, - - removeAllTabsBut : function TSTWindow_removeAllTabsBut(aTab) - { - var keepTabs = [aTab].concat(this.getDescendantTabs(aTab)); - var b = this.getTabBrowserFromChild(aTab); - var closeTabs = this.getTabs(b).filter(function(aTab) { - return keepTabs.indexOf(aTab) < 0 && !aTab.hasAttribute('pinned'); - }); - - if (!this.warnAboutClosingTabs(closeTabs.length)) - return; - - this.stopRendering(); - this.markAsClosedSet(closeTabs); - var tabs = closeTabs.reverse(); - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - b.removeTab(tabs[i]); - } - this.startRendering(); - }, - - // For backward compatibility. You should use DOM event to block TST's focus handling. - registerTabFocusAllowance : function TSTWindow_registerTabFocusAllowance(aProcess) /* PUBLIC API */ - { - var listener = { - process : aProcess, - handleEvent : function(aEvent) { - var tab = aEvent.originalTarget; - var b = tab.__treestyletab__linkedTabBrowser; - if (!this.process.call(b.treeStyleTab, b)) - aEvent.preventDefault(); - } - }; - this.window.addEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); - this._tabFocusAllowance.push(listener); - }, - _tabFocusAllowance : [], - - tearOffSubtreeFromRemote : function TSTWindow_tearOffSubtreeFromRemote() - { - var w = this.window; - var remoteTab = w.arguments[0]; - var remoteWindow = remoteTab.ownerDocument.defaultView; - var remoteService = remoteWindow.TreeStyleTabService; - var remoteMultipleTabService = remoteWindow.MultipleTabService; - if (remoteService.hasChildTabs(remoteTab) || - (remoteMultipleTabService && remoteMultipleTabService.isSelected(remoteTab))) { - let remoteBrowser = remoteService.getTabBrowserFromChild(remoteTab); - if (remoteBrowser.treeStyleTab.tabbarDNDObserver.isDraggingAllTabs(remoteTab)) { - w.close(); - } - else { - let actionInfo = { - action : remoteTab.__treestyletab__toBeDuplicated ? this.kACTION_DUPLICATE : this.kACTION_IMPORT - }; - - let b = this.browser; - let blankTab; - this.Deferred - .next(function() { - var blankTab = b.selectedTab; - b.treeStyleTab.tabbarDNDObserver.performDrop(actionInfo, remoteTab); - return blankTab; - }) - .next(function(aBlankTab) { - b.removeTab(aBlankTab); - remoteTab = null; - remoteBrowser = null; - remoteWindow = null - remoteService = null; - remoteMultipleTabService = null; - }) - .error(this.defaultDeferredErrorHandler); - } - return true; - } - return false; - }, - tearOffSubTreeFromRemote : function(...aArgs) { - return this.tearOffSubtreeFromRemote.apply(this, aArgs); - }, // obsolete, for backward compatibility - - onPrintPreviewEnter : function TSTWindow_onPrintPreviewEnter() - { - var d = this.document; - var event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, true, false); - d.documentElement.dispatchEvent(event); - - // for backward compatibility - event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED.replace(/^nsDOM/, ''), true, false); - d.documentElement.dispatchEvent(event); - }, - - onPrintPreviewExit : function TSTWindow_onPrintPreviewExit() - { - var d = this.document; - var event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, true, false); - d.documentElement.dispatchEvent(event); - - // for backward compatibility - event = d.createEvent('Events'); - event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED.replace(/^nsDOM/, ''), true, false); - d.documentElement.dispatchEvent(event); - }, - - observe : function TSTWindow_observe(aSubject, aTopic, aData) - { - switch (aTopic) - { - case 'nsPref:changed': - this.onPrefChange(aData); - return; - } - }, - get restoringTree() { - if (this._restoringTree || !!this.restoringCount) - return true; - - var count = 0; - this.browser.visibleTabs.some(function(aTab) { - if (aTab.linkedBrowser.__treestyletab__toBeRestored) - count++; - return count > 1; - }); - return count > 1; - }, - set restoringTree(aValue) { - return this._restoringTree = !!aValue; - }, - _restoringTree : false, - -/* Pref Listener */ - - domains : [ - 'extensions.treestyletab', - 'browser.ctrlTab.previews' - ], - - onPrefChange : function TSTWindow_onPrefChange(aPrefName) - { - var value = prefs.getPref(aPrefName); - switch (aPrefName) - { - case 'extensions.treestyletab.tabbar.autoHide.mode': - // don't set on this time, because appearance of all tabbrowsers are not updated yet. - // this.autoHide.mode = utils.getTreePref('tabbar.autoHide.mode'); - case 'extensions.treestyletab.tabbar.autoShow.accelKeyDown': - case 'extensions.treestyletab.tabbar.autoShow.tabSwitch': - case 'extensions.treestyletab.tabbar.autoShow.feedback': - this.autoHideWindow.updateKeyListeners(this.window); - break; - - case 'extensions.treestyletab.tabbar.style': - case 'extensions.treestyletab.tabbar.position': - this.themeManager.set(prefs.getPref('extensions.treestyletab.tabbar.style'), this.position); - break; - - case 'browser.ctrlTab.previews': - this.autoHideWindow.updateKeyListeners(this.window); - case 'extensions.treestyletab.autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut': - case 'extensions.treestyletab.autoCollapseExpandSubtreeOnSelect': - if (this.shouldListenKeyEventsForAutoExpandByFocusChange) - this.startListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); - else - this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); - break; - - default: - break; - } - } - -}); - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Tree Style Tab. + * + * The Initial Developer of the Original Code is YUKI "Piro" Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2012-2014 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * Tetsuharu OHZEKI + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +const EXPORTED_SYMBOLS = ['TreeStyleTabWindow']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); + +XPCOMUtils.defineLazyGetter(this, 'window', function() { + Cu.import('resource://treestyletab-modules/lib/namespace.jsm'); + return getNamespaceFor('piro.sakura.ne.jp'); +}); +XPCOMUtils.defineLazyGetter(this, 'prefs', function() { + Cu.import('resource://treestyletab-modules/lib/prefs.js'); + return window['piro.sakura.ne.jp'].prefs; +}); + +XPCOMUtils.defineLazyModuleGetter(this, 'UninstallationListener', + 'resource://treestyletab-modules/lib/UninstallationListener.js'); + +XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm'); + +Cu.import('resource://treestyletab-modules/base.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabBrowser', 'resource://treestyletab-modules/browser.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); +XPCOMUtils.defineLazyModuleGetter(this, 'AutoHideWindow', 'resource://treestyletab-modules/autoHide.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabThemeManager', 'resource://treestyletab-modules/themeManager.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'FullscreenObserver', 'resource://treestyletab-modules/fullscreenObserver.js'); +XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUIShowHideObserver', 'resource://treestyletab-modules/browserUIShowHideObserver.js'); + +function TreeStyleTabWindow(aWindow) +{ + this.window = aWindow; + this.document = aWindow.document; + + this._restoringTabs = []; + this._shownPopups = []; + this.restoringCount = 0; + + aWindow.addEventListener('DOMContentLoaded', this, true); + aWindow.addEventListener('load', this, false); + aWindow.TreeStyleTabService = this; + + XPCOMUtils.defineLazyModuleGetter(aWindow, 'TreeStyleTabBrowser', 'resource://treestyletab-modules/browser.js'); +} + +TreeStyleTabWindow.prototype = inherit(TreeStyleTabBase, { + + base : TreeStyleTabBase, + + window : null, + document : null, + +/* API */ + + changeTabbarPosition : function TSTWindow_changeTabbarPosition(aNewPosition) /* PUBLIC API (obsolete, for backward compatibility) */ + { + this.position = aNewPosition; + }, + + get position() /* PUBLIC API */ + { + return this.preInitialized && this.browser.treeStyleTab ? + this.browser.treeStyleTab.position : + this.base.position ; + }, + set position(aValue) + { + if ('UndoTabService' in this.window && this.window.UndoTabService.isUndoable()) { + var current = this.base.position; + var self = this; + this.window.UndoTabService.doOperation( + function() { + self.base.position = aValue; + }, + { + label : utils.treeBundle.getString('undo_changeTabbarPosition_label'), + name : 'treestyletab-changeTabbarPosition', + data : { + oldPosition : current, + newPosition : aValue + } + } + ); + } + else { + this.base.position = aValue; + } + return aValue; + }, + + undoChangeTabbarPosition : function TSTWindow_undoChangeTabbarPosition() /* PUBLIC API */ + { + return this.base.undoChangeTabbarPosition(); + }, + + redoChangeTabbarPosition : function TSTWindow_redoChangeTabbarPosition() /* PUBLIC API */ + { + return this.base.redoChangeTabbarPosition(); + }, + + get treeViewEnabled() /* PUBLIC API */ + { + return this.base.treeViewEnabled; + }, + set treeViewEnabled(aValue) + { + return this.base.treeViewEnabled = aValue; + }, + + get useTMPSessionAPI() /* PUBLIC API */ + { + return this.base.useTMPSessionAPI; + }, + set useTMPSessionAPI(aValue) + { + return this.base.useTMPSessionAPI = aValue; + }, + + get browser() + { + var w = this.window; + this.assertBeforeDestruction(w); + return 'SplitBrowser' in w ? w.SplitBrowser.activeBrowser : + w.gBrowser ; + }, + + get browserToolbox() + { + var w = this.window; + return w.gToolbox || w.gNavToolbox; + }, + + get browserBox() + { + return this.document.getElementById('browser'); + }, + + get browserBottomBox() + { + return this.document.getElementById('browser-bottombox'); + }, + + get isPopupWindow() + { + return ( + this.document && + this.document.documentElement.getAttribute('chromehidden') != '' && + !this.window.gBrowser.treeStyleTab.isVisible + ); + }, + +/* backward compatibility */ + getTempTreeStyleTab : function TSTWindow_getTempTreeStyleTab(aTabBrowser) + { + return aTabBrowser.treeStyleTab || new TreeStyleTabBrowser(this, aTabBrowser); + }, + + initTabAttributes : function TSTWindow_initTabAttributes(aTab, aTabBrowser) + { + var b = aTabBrowser || this.getTabBrowserFromChild(aTab); + this.getTempTreeStyleTab(b).initTabAttributes(aTab); + }, + + initTabContents : function TSTWindow_initTabContents(aTab, aTabBrowser) + { + var b = aTabBrowser || this.getTabBrowserFromChild(aTab); + this.getTempTreeStyleTab(b).initTabContents(aTab); + }, + + initTabContentsOrder : function TSTWindow_initTabContentsOrder(aTab, aTabBrowser) + { + var b = aTabBrowser || this.getTabBrowserFromChild(aTab); + this.getTempTreeStyleTab(b).initTabContentsOrder(aTab); + }, + +/* Utilities */ + + stopRendering : function TSTWindow_stopRendering() + { + this.window['piro.sakura.ne.jp'].stopRendering.stop(); + }, + startRendering : function TSTWindow_startRendering() + { + this.window['piro.sakura.ne.jp'].stopRendering.start(); + }, + + getPropertyPixelValue : function TSTWindow_getPropertyPixelValue(aElementOrStyle, aProp) + { + var style = aElementOrStyle instanceof Ci.nsIDOMCSSStyleDeclaration ? + aElementOrStyle : + this.window.getComputedStyle(aElementOrStyle, null) ; + return Number(style.getPropertyValue(aProp).replace(/px$/, '')); + }, + + get isToolbarCustomizing() + { + var toolbox = this.browserToolbox; + return toolbox && toolbox.customizing; + }, + + get maximized() + { + var sizemode = this.document.documentElement.getAttribute('sizemode'); + return ( + this.window.fullScreen || + this.window.windowState == this.window.STATE_MAXIMIZED || + sizemode == 'maximized' || + sizemode == 'fullscreen' + ); + }, + + maxTabbarWidth : function TSTWindow_maxTabbarWidth(aWidth, aTabBrowser) + { + aTabBrowser = aTabBrowser || this.browser; + var safePadding = 20; // for window border, etc. + var windowWidth = this.maximized ? this.window.screen.availWidth - safePadding : this.window.outerWidth ; + var rootWidth = parseInt(this.document.documentElement.getAttribute('width') || 0); + var max = Math.max(windowWidth, rootWidth); + return Math.max(0, Math.min(aWidth, max * this.MAX_TABBAR_SIZE_RATIO)); + }, + + maxTabbarHeight : function TSTWindow_maxTabbarHeight(aHeight, aTabBrowser) + { + aTabBrowser = aTabBrowser || this.browser; + var safePadding = 20; // for window border, etc. + var windowHeight = this.maximized ? this.window.screen.availHeight - safePadding : this.window.outerHeight ; + var rootHeight = parseInt(this.document.documentElement.getAttribute('height') || 0); + var max = Math.max(windowHeight, rootHeight); + return Math.max(0, Math.min(aHeight, max * this.MAX_TABBAR_SIZE_RATIO)); + }, + + shouldOpenSearchResultAsChild : function TSTWindow_shouldOpenSearchResultAsChild(aTerm) + { + aTerm = aTerm.trim(); + + var mode = utils.getTreePref('autoAttach.searchResult'); + if (mode == this.kSEARCH_RESULT_ATTACH_ALWAYS) { + return true; + } + else if (!aTerm || mode == this.kSEARCH_RESULT_DO_NOT_ATTACH) { + return false; + } + + var selection = this.window.getBrowserSelection(); + return selection.trim() == aTerm; + }, + kSEARCH_RESULT_DO_NOT_ATTACH : 0, + kSEARCH_RESULT_ATTACH_IF_SELECTED : 1, + kSEARCH_RESULT_ATTACH_ALWAYS : 2, + + get isAutoHide() + { + return this.window.fullScreen ? + ( + prefs.getPref('browser.fullscreen.autohide') && + utils.getTreePref('tabbar.autoHide.mode.fullscreen') + ) : + utils.getTreePref('tabbar.autoHide.mode'); + }, + + get autoHideWindow() + { + if (!this._autoHideWindow) { + this._autoHideWindow = new AutoHideWindow(this.window); + } + return this._autoHideWindow; + }, + + get themeManager() + { + if (!this._themeManager) { + this._themeManager = new TreeStyleTabThemeManager(this.window); + } + return this._themeManager; + }, + +/* Initializing */ + + preInit : function TSTWindow_preInit() + { + if (this.preInitialized) + return; + this.preInitialized = true; + + var w = this.window; + w.removeEventListener('DOMContentLoaded', this, true); + if (w.location.href.indexOf('chrome://browser/content/browser.xul') != 0) + return; + + w.addEventListener('SSTabRestoring', this, true); + + w.TreeStyleTabWindowHelper.preInit(); + + // initialize theme + this.onPrefChange('extensions.treestyletab.tabbar.style'); + }, + preInitialized : false, + + init : function TSTWindow_init() + { + var w = this.window; + w.removeEventListener('load', this, false); + + w.addEventListener('unload', this, false); + + if ( + w.location.href.indexOf('chrome://browser/content/browser.xul') != 0 || + !this.browser + ) + return; + + if (this.initialized) + return; + + if (!this.preInitialized) { + this.preInit(); + } + w.removeEventListener('SSTabRestoring', this, true); + + var d = this.document; + d.addEventListener('popupshowing', this, false); + d.addEventListener('popuphiding', this, true); + d.addEventListener(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); + d.addEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); + d.addEventListener(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this, false); + d.addEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); + w.addEventListener('beforecustomization', this, true); + w.addEventListener('aftercustomization', this, false); + + w.messageManager.addMessageListener('SessionStore:restoreTabContentStarted', this); + + this.fullscreenObserver = new FullscreenObserver(this.window); + this.initUIShowHideObserver(); + + var appcontent = d.getElementById('appcontent'); + appcontent.addEventListener('SubBrowserAdded', this, false); + appcontent.addEventListener('SubBrowserRemoveRequest', this, false); + + w.addEventListener('UIOperationHistoryUndo:TabbarOperations', this, false); + w.addEventListener('UIOperationHistoryRedo:TabbarOperations', this, false); + + prefs.addPrefListener(this); + + this.initUninstallationListener(); + + w.TreeStyleTabWindowHelper.onBeforeBrowserInit(); + this.initTabBrowser(this.browser); + w.TreeStyleTabWindowHelper.onAfterBrowserInit(); + + this.processRestoredTabs(); + this.updateTabsOnTop(); + + // Init autohide service only if it have to be activated. + if (this.isAutoHide) + this.onPrefChange('extensions.treestyletab.tabbar.autoHide.mode'); + + this.onPrefChange('extensions.treestyletab.autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut'); + + this.initialized = true; + }, + initialized : false, + + initUninstallationListener : function TSTWindow_initUninstallationListener() + { + var restorePrefs = function() { + if (prefs.getPref('extensions.treestyletab.tabsOnTop.originalState')) { + prefs.clearPref('extensions.treestyletab.tabsOnTop.originalState'); + try { + this.browser.treeStyleTab.position = 'top'; + } + catch(e) { + } + this.window.TabsOnTop.enabled = true; + } + }.bind(this); + new UninstallationListener({ + id : 'treestyletab@piro.sakura.ne.jp', + onuninstalled : restorePrefs, + ondisabled : restorePrefs + }); + }, + + initTabBrowser : function TSTWindow_initTabBrowser(aTabBrowser) + { + if (aTabBrowser.localName != 'tabbrowser') + return; + (new TreeStyleTabBrowser(this, aTabBrowser)).init(); + }, + + updateAllTabsButton : function TSTWindow_updateAllTabsButton(aTabBrowser) + { + var d = this.document; + aTabBrowser = aTabBrowser || this.browser; + var allTabsButton = d.getElementById('alltabs-button') || + ( // Tab Mix Plus + utils.getTreePref('compatibility.TMP') && + d.getAnonymousElementByAttribute(aTabBrowser.mTabContainer, 'anonid', 'alltabs-button') + ); + + if (allTabsButton && allTabsButton.hasChildNodes() && aTabBrowser.treeStyleTab) + allTabsButton.firstChild.setAttribute('position', aTabBrowser.treeStyleTab.isVertical ? 'before_start' : 'after_end' ); + }, + + updateAllTabsPopup : function TSTWindow_updateAllTabsPopup(aEvent) + { + if (!utils.getTreePref('enableSubtreeIndent.allTabsPopup')) + return; + + Array.forEach(aEvent.originalTarget.childNodes, function(aItem) { + if (aItem.classList.contains('alltabs-item') && 'tab' in aItem) + aItem.style.marginLeft = aItem.tab.getAttribute(this.kNEST) + 'em'; + }, this); + }, + + initUIShowHideObserver : function TSTWindow_initUIShowHideObserver() + { + this.rootElementObserver = new BrowserUIShowHideObserver(this, this.document.documentElement); + + var toolbox = this.browserToolbox; + if (toolbox) + this.browserToolboxObserver = new BrowserUIShowHideObserver(this, toolbox); + + var browserBox = this.browserBox; + if (browserBox) + this.browserBoxObserver = new BrowserUIShowHideObserver(this, browserBox); + + var bottomBox = this.browserBottomBox; + if (bottomBox) + this.browserBottomBoxObserver = new BrowserUIShowHideObserver(this, bottomBox); + }, + + destroy : function TSTWindow_destroy() + { + var w = this.window; + if (this.browser) { + this.base.inWindowDestoructionProcess = true; + try { + w.removeEventListener('unload', this, false); + + this.autoHideWindow.destroy(); + delete this._autoHideWindow; + + this.themeManager.destroy(); + delete this._themeManager; + + this.browser.treeStyleTab.saveCurrentState(); + this.destroyTabBrowser(this.browser); + + this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOHIDE); + this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); + + let d = this.document; + d.removeEventListener('popupshowing', this, false); + d.removeEventListener('popuphiding', this, true); + d.removeEventListener(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, this, false); + d.removeEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this, false); + d.removeEventListener(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this, false); + d.removeEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this, false); + w.removeEventListener('beforecustomization', this, true); + w.removeEventListener('aftercustomization', this, false); + + w.messageManager.removeMessageListener('SessionStore:restoreTabContentStarted', this); + + this.fullscreenObserver.destroy(); + delete this.fullscreenObserver; + + this.rootElementObserver.destroy(); + delete this.rootElementObserver; + + if (this.browserToolboxObserver) { + this.browserToolboxObserver.destroy(); + delete this.browserToolboxObserver; + } + if (this.browserBoxObserver) { + this.browserBoxObserver.destroy(); + delete this.browserBoxObserver; + } + if (this.browserBottomBoxObserver) { + this.browserBottomBoxObserver.destroy(); + delete this.browserBottomBoxObserver; + } + + for (let i = 0, maxi = this._tabFocusAllowance.length; i < maxi; i++) + { + w.removeEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, this._tabFocusAllowance[i], false); + } + + var appcontent = d.getElementById('appcontent'); + appcontent.removeEventListener('SubBrowserAdded', this, false); + appcontent.removeEventListener('SubBrowserRemoveRequest', this, false); + + w.removeEventListener('UIOperationHistoryUndo:TabbarOperations', this, false); + w.removeEventListener('UIOperationHistoryRedo:TabbarOperations', this, false); + + prefs.removePrefListener(this); + } + catch(e) { + throw e; + } + finally { + this.base.inWindowDestoructionProcess = false; + } + } + + delete w.TreeStyleTabService; + delete this.window; + delete this.document; + }, + + destroyTabBrowser : function TSTWindow_destroyTabBrowser(aTabBrowser) + { + if (aTabBrowser.localName != 'tabbrowser') + return; + aTabBrowser.treeStyleTab.destroy(); + delete aTabBrowser.treeStyleTab; + }, + +/* Event Handling */ + + handleEvent : function TSTWindow_handleEvent(aEvent) + { + switch (aEvent.type) + { + case 'DOMContentLoaded': + return this.preInit(); + + case 'load': + return this.init(); + + case 'unload': + return this.destroy(); + + case 'SSTabRestoring': + return this.onTabRestored(aEvent); + + case 'popupshowing': + this.onPopupShown(aEvent.originalTarget); + if ((aEvent.originalTarget.getAttribute('anonid') || aEvent.originalTarget.id) == 'alltabs-popup') + this.updateAllTabsPopup(aEvent); + return; + + case 'popuphiding': + return this.onPopupHidden(aEvent.originalTarget); + + case this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED: + return this.updateAeroPeekPreviews(); + + case this.kEVENT_TYPE_TABBAR_POSITION_CHANGED: + case this.kEVENT_TYPE_TABBAR_STATE_CHANGED: + return this.updateTabsOnTop(); + + case this.kEVENT_TYPE_FOCUS_NEXT_TAB: + return this.onFocusNextTab(aEvent); + + case 'keydown': + return this.onKeyDown(aEvent); + + case 'keyup': + case 'keypress': + return this.onKeyRelease(aEvent); + + case 'blur': + return this.simulateKeyRelease(); + + case 'mousedown': + return this.onTabbarResizeStart(aEvent); + + case 'mouseup': + return this.onTabbarResizeEnd(aEvent); + + case 'mousemove': + return this.onTabbarResizing(aEvent); + + case 'dblclick': + return this.onTabbarReset(aEvent); + + case 'click': + return this.handleNewTabActionOnButton(aEvent); + + + case 'beforecustomization': + this.window.TreeStyleTabWindowHelper.destroyToolbarItems(); + return; + + case 'aftercustomization': + this.window.TreeStyleTabWindowHelper.initToolbarItems(); + return; + + + case 'SubBrowserAdded': + return this.initTabBrowser(aEvent.originalTarget.browser); + + case 'SubBrowserRemoveRequest': + return this.destroyTabBrowser(aEvent.originalTarget.browser); + + case 'UIOperationHistoryUndo:TabbarOperations': + switch (aEvent.entry.name) + { + case 'treestyletab-changeTabbarPosition': + this.position = aEvent.entry.data.oldPosition; + return; + case 'treestyletab-changeTabbarPosition-private': + aEvent.entry.data.target.treeStyleTab.position = aEvent.entry.data.oldPosition; + return; + } + return; + + case 'UIOperationHistoryRedo:TabbarOperations': + switch (aEvent.entry.name) + { + case 'treestyletab-changeTabbarPosition': + this.position = aEvent.entry.data.newPosition; + return; + case 'treestyletab-changeTabbarPosition-private': + aEvent.entry.data.target.treeStyleTab.position = aEvent.entry.data.newPosition; + return; + } + return; + } + }, + + keyEventListening : false, + keyEventListeningFlags : 0, + + LISTEN_FOR_AUTOHIDE : 1, + LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE : 2, + + startListenKeyEventsFor : function TSTWindow_startListenKeyEventsFor(aReason) + { + if (this.keyEventListeningFlags & aReason) + return; + if (!this.keyEventListening) { + let w = this.window; + w.addEventListener('keydown', this, true); + w.addEventListener('keyup', this, true); + w.addEventListener('keypress', this, true); + w.addEventListener('blur', this, true); + this.keyEventListening = true; + } + this.keyEventListeningFlags |= aReason; + }, + + endListenKeyEventsFor : function TSTWindow_endListenKeyEventsFor(aReason) + { + if (!(this.keyEventListeningFlags & aReason)) + return; + this.keyEventListeningFlags ^= aReason; + if (!this.keyEventListeningFlags && this.keyEventListening) { + let w = this.window; + w.removeEventListener('keydown', this, true); + w.removeEventListener('keyup', this, true); + w.removeEventListener('keypress', this, true); + w.removeEventListener('blur', this, true); + this.keyEventListening = false; + } + }, + + onKeyDown : function TSTWindow_onKeyDown(aEvent) + { + /** + * On Mac OS X, default accel key is the Command key (metaKey), but + * Cmd-Tab is used to switch applications by the OS itself. So Firefox + * uses Ctrl-Tab to switch tabs on all platforms. + */ + // this.accelKeyPressed = this.isAccelKeyPressed(aEvent); + this.accelKeyPressed = aEvent.ctrlKey || aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL; + + var left = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT; + var right = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT; + var up = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP; + var down = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN; + if ( + Services.focus.focusedElement == this.browser.selectedTab && + (up || down || left || right) + ) + this.arrowKeyEventOnTab = { + keyCode : aEvent.keyCode, + left : left, + right : right, + up : up, + down : down, + altKey : aEvent.altKey, + ctrlKey : aEvent.ctrlKey, + metaKey : aEvent.metaKey, + shiftKey : aEvent.shiftKey + }; + + var b = this.browser; + var data = { + sourceEvent : aEvent + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN, b, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_KEY_DOWN.replace(/^nsDOM/, ''), b, true, false, data); + }, + accelKeyPressed : false, + arrowKeyEventOnTab : null, + + onKeyRelease : function TSTWindow_onKeyRelease(aEvent) + { + var b = this.browser; + if (!b || !b.treeStyleTab) + return; + var sv = b.treeStyleTab; + + var scrollDown, + scrollUp; + + // this.accelKeyPressed = this.isAccelKeyPressed(aEvent); + this.accelKeyPressed = aEvent.ctrlKey || aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL; + this.window.setTimeout(function(aSelf) { + aSelf.arrowKeyEventOnTab = null; + }, 10, this); + + var standBy = scrollDown = scrollUp = (!aEvent.altKey && this.accelKeyPressed); + + scrollDown = scrollDown && ( + !aEvent.shiftKey && + ( + aEvent.keyCode == aEvent.DOM_VK_TAB || + aEvent.keyCode == aEvent.DOM_VK_PAGE_DOWN + ) + ); + + scrollUp = scrollUp && ( + aEvent.shiftKey ? (aEvent.keyCode == aEvent.DOM_VK_TAB) : (aEvent.keyCode == aEvent.DOM_VK_PAGE_UP) + ); + + var onlyShiftKey = (!aEvent.shiftKey && aEvent.keyCode == 16 && (aEvent.type == 'keyup' || aEvent.charCode == 0)); + + var data = { + scrollDown : scrollDown, + scrollUp : scrollUp, + standBy : standBy, + onlyShiftKey : onlyShiftKey, + sourceEvent : aEvent + }; + + if ( + scrollDown || + scrollUp || + ( // when you release "shift" key + standBy && onlyShiftKey + ) + ) { + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START, b, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_START.replace(/^nsDOM/, ''), b, true, false, data); + return; + } + + if (aEvent.type == 'keypress' ? + // ignore keypress on Ctrl-R, Ctrl-T, etc. + aEvent.charCode != 0 : + // ignore keyup not on the Ctrl key + aEvent.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_CONTROL + ) + return; + + // when you just release accel key... + + /* PUBLIC API */ + let (event) { + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, b, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END.replace(/^nsDOM/, ''), b, true, false, data); + } + + if (this._tabShouldBeExpandedAfterKeyReleased) { + let tab = this._tabShouldBeExpandedAfterKeyReleased; + if (this.hasChildTabs(tab) && + this.isSubtreeCollapsed(tab)) { + this.getTabBrowserFromChild(tab) + .treeStyleTab + .collapseExpandTreesIntelligentlyFor(tab); + } + } + this._tabShouldBeExpandedAfterKeyReleased = null; + }, + // When the window lose its focus, we cannot detect any key-release events. + // So we have to simulate key-release event manually. + // See: https://github.com/piroor/treestyletab/issues/654 + simulateKeyRelease : function TSTWindow_simulateKeyRelease() + { + if (!this.accelKeyPressed) + return; + + this.accelKeyPressed = false; + var data = { + scrollDown : false, + scrollUp : false, + standBy : false, + onlyShiftKey : false, + sourceEvent : null + }; + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this.browser, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END.replace(/^nsDOM/, ''), this.browser, true, false, data); + }, + + get shouldListenKeyEventsForAutoExpandByFocusChange() + { + return !this.ctrlTabPreviewsEnabled && + ( + utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut') || + utils.getTreePref('autoCollapseExpandSubtreeOnSelect') + ); + }, + + get ctrlTabPreviewsEnabled() + { + return 'allTabs' in this.window && + prefs.getPref('browser.ctrlTab.previews'); + }, + + receiveMessage : function TSTWindow_receiveMessage(aMessage) + { + var browser = aMessage.target; + var tabbrowser = this.getTabBrowserFromChild(browser); + if (!tabbrowser) + return; + var tab = tabbrowser.treeStyleTab.getTabFromBrowser(browser); + if (!tab) + return; + + switch (aMessage.name) + { + case 'SessionStore:restoreTabContentStarted': + return tabbrowser.treeStyleTab.onRestoreTabContentStarted(tab); + } + }, + + onTabbarResizeStart : function TSTWindow_onTabbarResizeStart(aEvent) + { + if (aEvent.button != 0) + return; + + if (!this.isEventFiredOnGrippy(aEvent)) + aEvent.stopPropagation(); + + if ('setCapture' in aEvent.currentTarget) + aEvent.currentTarget.setCapture(true); + + aEvent.currentTarget.addEventListener('mousemove', this, false); + + var b = this.getTabBrowserFromChild(aEvent.currentTarget); + var box = aEvent.currentTarget.id == 'treestyletab-tabbar-resizer-splitter' ? + this.getTabStrip(b) : + b.treeStyleTab.tabStripPlaceHolder || b.tabContainer ; + this.tabbarResizeStartWidth = box.boxObject.width; + this.tabbarResizeStartHeight = box.boxObject.height; + this.tabbarResizeStartX = aEvent.screenX; + this.tabbarResizeStartY = aEvent.screenY; + }, + onTabbarResizeEnd : function TSTWindow_onTabbarResizeEnd(aEvent) + { + if (this.tabbarResizeStartWidth < 0) + return; + + var target = aEvent.currentTarget; + var b = this.getTabBrowserFromChild(target); + + aEvent.stopPropagation(); + if ('releaseCapture' in target) + target.releaseCapture(); + + target.removeEventListener('mousemove', this, false); + + this.tabbarResizeStartWidth = -1; + this.tabbarResizeStartHeight = -1; + this.tabbarResizeStartX = -1; + this.tabbarResizeStartY = -1; + + this.Deferred.next(function() { + b.treeStyleTab.fixTooNarrowTabbar(); + }).error(this.defaultDeferredErrorHandler); + }, + onTabbarResizing : function TSTWindow_onTabbarResizing(aEvent) + { + var target = aEvent.currentTarget; + var b = this.getTabBrowserFromChild(target); + + var expanded = target.id == 'treestyletab-tabbar-resizer-splitter'; + if (expanded) + aEvent.stopPropagation(); + + var width = this.tabbarResizeStartWidth; + var height = this.tabbarResizeStartHeight; + var pos = b.treeStyleTab.position; + if (b.treeStyleTab.isVertical) { + let delta = aEvent.screenX - this.tabbarResizeStartX; + width += (pos == 'left' ? delta : -delta ); + width = this.maxTabbarWidth(width, b); + if (expanded || b.treeStyleTab.autoHide.expanded) { + this.setPrefForActiveWindow(function() { + utils.setTreePref('tabbar.width', width); + }); + if (b.treeStyleTab.autoHide.mode == b.treeStyleTab.autoHide.kMODE_SHRINK && + b.treeStyleTab.tabStripPlaceHolder) + b.treeStyleTab.tabStripPlaceHolder.setAttribute('width', utils.getTreePref('tabbar.shrunkenWidth')); + } + else { + this.setPrefForActiveWindow(function() { + utils.setTreePref('tabbar.shrunkenWidth', width); + }); + } + } + else { + let delta = aEvent.screenY - this.tabbarResizeStartY; + height += (pos == 'top' ? delta : -delta ); + this.setPrefForActiveWindow(function() { + utils.setTreePref('tabbar.height', this.maxTabbarHeight(height, b)); + }); + } + b.treeStyleTab.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_TABBAR_RESIZE); + }, + tabbarResizeStartWidth : -1, + tabbarResizeStartHeight : -1, + tabbarResizeStartX : -1, + tabbarResizeStartY : -1, + + onTabbarReset : function TSTWindow_onTabbarReset(aEvent) + { + if (aEvent.button != 0) + return; + var b = this.getTabBrowserFromChild(aEvent.currentTarget); + if (b) { + b.treeStyleTab.resetTabbarSize(); + aEvent.stopPropagation(); + } + }, + + onFocusNextTab : function TSTWindow_onFocusNextTab(aEvent) + { + var tab = aEvent.originalTarget; + var b = this.getTabBrowserFromChild(tab); + if ( + prefs.getPref('browser.tabs.selectOwnerOnClose') && + tab.owner && + ( + !b._removingTabs || + b._removingTabs.indexOf(tab.owner) < 0 + ) + ) + aEvent.preventDefault(); + }, + + showHideSubtreeMenuItem : function TSTWindow_showHideSubtreeMenuItem(aMenuItem, aTabs) + { + if (!aMenuItem || + aMenuItem.getAttribute('hidden') == 'true' || + !aTabs || + !aTabs.length) + return; + + var hasSubtree = false; + for (var i = 0, maxi = aTabs.length; i < maxi; i++) + { + if (!this.hasChildTabs(aTabs[i])) + continue; + hasSubtree = true; + break; + } + if (hasSubtree) + aMenuItem.removeAttribute('hidden'); + else + aMenuItem.setAttribute('hidden', true); + }, + showHideSubTreeMenuItem : function(...aArgs) { + return this.showHideSubtreeMenuItem.apply(this, aArgs); + }, // obsolete, for backward compatibility + + updateAeroPeekPreviews : function TSTWindow_updateAeroPeekPreviews() + { + var w = this.window; + if ( + this.updateAeroPeekPreviewsTimer || + !prefs.getPref('browser.taskbar.previews.enable') || + !utils.getTreePref('taskbarPreviews.hideCollapsedTabs') || + !('Win7Features' in w) || + !w.Win7Features || + !this.AeroPeek || + !this.AeroPeek.windows + ) + return; + + this.updateAeroPeekPreviewsTimer = w.setTimeout(function(aSelf) { + aSelf.updateAeroPeekPreviewsTimer = null; + try { + aSelf.updateAeroPeekPreviewsInternal(); + } + catch(e) { + dump(e+'\n'); + aSelf.updateAeroPeekPreviews(); + } + }, 250, this); + }, + updateAeroPeekPreviewsTimer : null, + updateAeroPeekPreviewsInternal : function TSTWindow_updateAeroPeekPreviewsInternal() + { + if ( + !prefs.getPref('browser.taskbar.previews.enable') || + !utils.getTreePref('taskbarPreviews.hideCollapsedTabs') + ) + return; + + this.AeroPeek.windows.some(function(aTabWindow) { + if (aTabWindow.win == this.window) { + let previews = aTabWindow.previews; + for (let i = 0, maxi = previews.length; i < maxi; i++) + { + let preview = previews[i]; + if (!preview) + continue; + let tab = preview.controller.wrappedJSObject.tab; + preview.visible = !this.isCollapsed(tab); + } + this.AeroPeek.checkPreviewCount(); + return true; + } + return false; + }, this); + }, + + updateTabsOnTop : function TSTWindow_updateTabsOnTop() + { + if ( + this.isPopupWindow || + this.tabsOnTopChangingByUI || + this.tabsOnTopChangingByTST + ) + return; + + var TabsOnTop = this.window.TabsOnTop; + var TabsInTitlebar = this.window.TabsInTitlebar; + var isTopTabbar = this.browser.treeStyleTab.position == 'top'; + + this.tabsOnTopChangingByTST = true; + + try { + if (TabsOnTop) { + let originalState = utils.getTreePref('tabsOnTop.originalState'); + if (originalState === null) { + let current = prefs.getDefaultPref('browser.tabs.onTop') === null ? + TabsOnTop.enabled : + prefs.getPref('browser.tabs.onTop') ; + utils.setTreePref('tabsOnTop.originalState', originalState = current); + } + + if (!isTopTabbar || !this.browser.treeStyleTab.fixed) { + if (TabsOnTop.enabled) + TabsOnTop.enabled = false; + } + else { + if (TabsOnTop.enabled != originalState) + TabsOnTop.enabled = originalState; + utils.clearTreePref('tabsOnTop.originalState'); + } + } + if (TabsInTitlebar) { + let allowed = isTopTabbar && this.browser.treeStyleTab.fixed; + if ( + (this.window.TabsOnBottom && utils.getTreePref('compatibility.TabsOnBottom')) || + ('navbarontop' in this.window && utils.getTreePref('compatibility.NavbarOnTitlebar')) || + ('classicthemerestorerjs' in this.window && utils.getTreePref('compatibility.ClassicThemeRestorer')) + ) + allowed = true; + TabsInTitlebar.allowedBy('TreeStyleTab-tabsOnTop', allowed); + } + } + finally { + this.tabsOnTopChangingByTST = false; + } + }, + + onPopupShown : function TSTWindow_onPopupShown(aPopup) + { + if (!aPopup.boxObject || + this.evaluateXPath( + 'parent::*/ancestor-or-self::*[local-name()="tooltip" or local-name()="panel" or local-name()="popup" or local-name()="menupopup"]', + aPopup, + Ci.nsIDOMXPathResult.BOOLEAN_TYPE + ).booleanValue) + return; + + this.window.setTimeout(function(aSelf) { + if ((!aPopup.boxObject.width && !aPopup.boxObject.height) || + aPopup.boxObject.popupState == 'closed') + return; + + var id = aPopup.id; + var item = id && aSelf.document.getElementById(id) ? id : aPopup ; + var index = aSelf._shownPopups.indexOf(item); + if (index < 0) + aSelf._shownPopups.push(item); + }, 10, this); + }, + + onPopupHidden : function TSTWindow_onPopupHidden(aPopup) + { + var id = aPopup.id; + aPopup = id && this.document.getElementById(id) ? id : aPopup ; + var index = this._shownPopups.indexOf(aPopup); + if (index > -1) + this._shownPopups.splice(index, 1); + }, + + isPopupShown : function TSTWindow_isPopupShown() + { + this._shownPopups = this._shownPopups.filter(function(aItem) { + if (typeof aItem == 'string') + aItem = this.document.getElementById(aItem); + return ( + aItem && + aItem.getAttribute(this.kIGNORE_POPUP_STATE) != 'true' && + aItem.boxObject && + (aItem.boxObject.width || aItem.boxObject.height) && + aItem.state != 'closed' + ); + }, this); + return this._shownPopups.length > 0; + }, + + onBeforeNewTabCommand : function TSTWindow_onBeforeNewTabCommand(aTabBrowser) + { + var self = this.windowService || this; + if (self._clickEventOnNewTabButtonHandled) + return; + + var b = aTabBrowser || this.browser; + this.readyToOpenRelatedTabAs(b.selectedTab, utils.getTreePref('autoAttach.newTabCommand')); + }, + + handleNewTabActionOnButton : function TSTWindow_handleNewTabActionOnButton(aEvent) + { + // ignore non new-tab commands (middle click, Ctrl-click) + if (aEvent.button != 1 && (aEvent.button != 0 || !this.isAccelKeyPressed(aEvent))) + return; + + var newTabButton = this.getNewTabButtonFromEvent(aEvent); + if (newTabButton) { + this.readyToOpenRelatedTabAs(this.browser.selectedTab, utils.getTreePref('autoAttach.newTabButton')); + let self = this.windowService || this; + self._clickEventOnNewTabButtonHandled = true; + this.Deferred.next(function() { + self._clickEventOnNewTabButtonHandled = false; + }); + } + else if (aEvent.target.id == 'urlbar-go-button' || aEvent.target.id == 'go-button') { + this.readyToOpenRelatedTabAs(this.browser.selectedTab, utils.getTreePref('autoAttach.goButton')); + } + }, + _clickEventOnNewTabButtonHandled : false, + + onBeforeTabDuplicate : function TSTWindow_onBeforeTabDuplicate(aTab, aWhere, aDelta) + { + if (aWhere && aWhere.indexOf('tab') != 0) + return; + + var b = this.getTabBrowserFromChild(aTab) || this.browser; + var behaviorPref = !aDelta ? 'autoAttach.duplicateTabCommand' : + aDelta < 0 ? 'autoAttach.duplicateTabCommand.back' : + 'autoAttach.duplicateTabCommand.forward' + var behavior = utils.getTreePref(behaviorPref); + this.readyToOpenRelatedTabAs(aTab || b.selectedTab, behavior); + }, + + onBeforeOpenLink : function TSTWindow_onBeforeOpenLink(aWhere, aOwner) + { + if (aWhere == 'tab' || aWhere == 'tabshifted') + this.handleNewTabFromCurrent(aOwner); + }, + + onBeforeOpenLinkWithTab : function TSTWindow_onBeforeOpenLinkWithTab(aTab, aFromChrome) + { + if (!aFromChrome && aTab && !this.checkToOpenChildTab(aTab)) + this.handleNewTabFromCurrent(aTab); + }, + + onBeforeOpenNewTabByThirdParty : function TSTWindow_onBeforeOpenNewTabByThirdParty(aOwner) + { + if (!this.checkToOpenChildTab(aOwner)) + this.handleNewTabFromCurrent(aOwner); + }, + + onBeforeBrowserAccessOpenURI : function TSTWindow_onBeforeBrowserAccessOpenURI(aOpener, aWhere) + { + if (aOpener && + this.getTabFromFrame(aOpener.top) && + aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) + this.handleNewTabFromCurrent(aOpener); + }, + + onBeforeViewMedia : function TSTWindow_onBeforeViewMedia(aEvent, aOwner) + { + if (String(this.window.whereToOpenLink(aEvent, false, true)).indexOf('tab') == 0) + this.handleNewTabFromCurrent(aOwner); + }, + + onBeforeBrowserSearch : function TSTWindow_onBeforeBrowserSearch(aTerm, aForceNewTab) + { + if ((arguments.length == 1 || aForceNewTab) && + this.shouldOpenSearchResultAsChild(aTerm)) + this.handleNewTabFromCurrent(); + }, + +/* Tree Style Tabの初期化が行われる前に復元されたセッションについてツリー構造を復元 */ + + onTabRestored : function TSTWindow_onTabRestored(aEvent) + { + this._restoringTabs.push(aEvent.originalTarget); + }, + + processRestoredTabs : function TSTWindow_processRestoredTabs() + { + for (let i = 0, maxi = this._restoringTabs.length; i < maxi; i++) + { + let tab = this._restoringTabs[i]; + try { + let b = this.getTabBrowserFromChild(aTab); + if (b) + b.treeStyleTab.handleRestoredTab(aTab); + } + catch(e) { + } + } + this._restoringTabs = []; + }, + +/* Commands */ + + setTabbarWidth : function TSTWindow_setTabbarWidth(aWidth, aForceExpanded) /* PUBLIC API */ + { + this.browser.treeStyleTab.autoHide.setWidth(aWidth, aForceExpanded); + }, + + setContentWidth : function TSTWindow_setContentWidth(aWidth, aKeepWindowSize) /* PUBLIC API */ + { + var w = this.window; + var treeStyleTab = this.browser.treeStyleTab; + var strip = treeStyleTab.tabStrip; + var tabbarWidth = treeStyleTab.splitterWidth + (treeStyleTab.isVertical ? strip.boxObject.width : 0 ); + var contentWidth = this.browser.boxObject.width - tabbarWidth; + if (aKeepWindowSize || + w.fullScreen || + w.windowState != Ci.nsIDOMChromeWindow.STATE_NORMAL) { + this.setTabbarWidth(Math.max(10, this.browser.boxObject.width - aWidth)); + } + else if (tabbarWidth + aWidth <= w.screen.availWidth) { + w.resizeBy(aWidth - contentWidth, 0); + } + else { + w.resizeBy(w.screen.availWidth - w.outerWidth, 0); + this.setTabbarWidth(this.browser.boxObject.width - aWidth); + } + }, + + toggleAutoHide : function TSTWindow_toggleAutoHide(aTabBrowser) /* PUBLIC API, for backward compatibility */ + { + this.autoHideWindow.toggleMode(aTabBrowser || this.browser); + }, + + toggleFixed : function TSTWindow_toggleFixed(aTabBrowser) /* PUBLIC API */ + { + var b = aTabBrowser || this.browser; + var orient = b.treeStyleTab.isVertical ? 'vertical' : 'horizontal' ; + + var newFixed = b.getAttribute(this.kFIXED+'-'+orient) != 'true'; + this.setTabbrowserAttribute(this.kFIXED+'-'+orient, newFixed || null, b); + this.setPrefForActiveWindow(function() { + b.treeStyleTab.fixed = newFixed; + utils.setTreePref('tabbar.fixed.'+orient, newFixed); + }); + + b.treeStyleTab.updateTabbarState(); + }, + + removeTabSubtree : function TSTWindow_removeTabSubtree(aTabOrTabs, aOnlyChildren) + { + var tabs = this.gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren); + if (!this.warnAboutClosingTabs(tabs.length)) + return; + + if (aOnlyChildren) + tabs = this.gatherSubtreeMemberTabs(aTabOrTabs); + + var allSubtrees = this.splitTabsToSubtrees(tabs); + for (let i = 0, maxi = allSubtrees.length; i < maxi; i++) + { + let subtreeTabs = allSubtrees[i]; + if (!this.fireTabSubtreeClosingEvent(subtreeTabs[0], subtreeTabs)) + continue; + let b = this.getTabBrowserFromChild(subtreeTabs[0]); + if (aOnlyChildren) + subtreeTabs = subtreeTabs.slice(1); + if (!subtreeTabs.length) + continue; + this.stopRendering(); + this.markAsClosedSet(subtreeTabs); + for (let i = subtreeTabs.length-1; i > -1; i--) + { + b.removeTab(subtreeTabs[i], { animate : true }); + } + this.startRendering(); + this.fireTabSubtreeClosedEvent(b, subtreeTabs[0], subtreeTabs) + } + }, + removeTabSubTree : function(...aArgs) { + return this.removeTabSubtree.apply(this, aArgs); + }, // obsolete, for backward compatibility + + fireTabSubtreeClosingEvent : function TSTWindow_fireTabSubtreeClosingEvent(aParentTab, aClosedTabs) + { + var b = this.getTabBrowserFromChild(aParentTab); + var data = { + parent : aParentTab, + tabs : aClosedTabs + }; + var canClose = ( + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSING, b, true, true, data) && + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSING.replace(/^nsDOM/, ''), b, true, true, data) + ); + return canClose; + }, + + fireTabSubtreeClosedEvent : function TSTWindow_fireTabSubtreeClosedEvent(aTabBrowser, aParentTab, aClosedTabs) + { + aClosedTabs = aClosedTabs.filter(function(aTab) { return !aTab.parentNode; }); + var data = { + parent : aParentTab, + tabs : aClosedTabs + }; + + /* PUBLIC API */ + this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSED, aTabBrowser, true, false, data); + // for backward compatibility + this.fireCustomEvent(this.kEVENT_TYPE_SUBTREE_CLOSED.replace(/^nsDOM/, ''), aTabBrowser, true, false, data); + }, + + warnAboutClosingTabSubtreeOf : function TSTWindow_warnAboutClosingTabSubtreeOf(aTab) + { + if (!this.shouldCloseTabSubtreeOf(aTab)) + return true; + + var tabs = [aTab].concat(this.getDescendantTabs(aTab)); + return this.warnAboutClosingTabs(tabs.length); + }, + warnAboutClosingTabSubTreeOf : function(...aArgs) { + return this.warnAboutClosingTabSubtreeOf.apply(this, aArgs); + }, // obsolete, for backward compatibility + + warnAboutClosingTabs : function TSTWindow_warnAboutClosingTabs(aTabsCount) + { + if ( + aTabsCount <= 1 || + !prefs.getPref('browser.tabs.warnOnClose') + ) + return true; + var checked = { value:true }; + var w = this.window; + w.focus(); + var message = w.PluralForm.get(aTabsCount, utils.tabbrowserBundle.getString('tabs.closeWarningMultiple')).replace('#1', aTabsCount); + var shouldClose = Services.prompt.confirmEx(w, + utils.tabbrowserBundle.getString('tabs.closeWarningTitle'), + message, + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1), + utils.tabbrowserBundle.getString('tabs.closeButtonMultiple'), + null, null, + utils.tabbrowserBundle.getString('tabs.closeWarningPromptMe'), + checked + ) == 0; + if (shouldClose && !checked.value) + prefs.setPref('browser.tabs.warnOnClose', false); + return shouldClose; + }, + + reloadTabSubtree : function TSTWindow_reloadTabSubtree(aTabOrTabs, aOnlyChildren) + { + var tabs = this.gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren); + var b = this.getTabBrowserFromChild(tabs[0]); + for (var i = tabs.length-1; i > -1; i--) + { + b.reloadTab(tabs[i]); + } + }, + reloadTabSubTree : function(...aArgs) { + return this.reloadTabSubtree.apply(this, aArgs); + }, // obsolete, for backward compatibility + + createSubtree : function TSTWindow_createSubtree(aTabs) + { + var rootTabs = this.getRootTabs(aTabs); + + var parent = this.getParentTab(aTabs[0]); + var next = aTabs[0]; + while ( + (next = this.getNextSiblingTab(next)) && + aTabs.indexOf(next) > -1 + ); + + var b = this.getTabBrowserFromChild(aTabs[0]); + + rootTabs.forEach(function(aRootTab) { + var parentTab = this.getParentTab(aRootTab); + var descendantTabs = this.getDescendantTabs(aRootTab); + descendantTabs.reverse().forEach(function(aDescendantTab) { + var inTargets = aTabs.indexOf(aDescendantTab) > -1; + var parentInTargets = aTabs.indexOf(this.getParentTab(aDescendantTab)) > -1; + if (inTargets || (inTargets == parentInTargets)) + return; + if (parentTab) + b.treeStyleTab.attachTabTo(aDescendantTab, parentTab, { + dontExpand : true, + dontMove : true, + insertBefore : this.getNextSiblingTab(aRootTab) + }); + else + b.treeStyleTab.detachTab(aDescendantTab); + }, this); + }, this); + + aTabs = rootTabs; + + var shouldCreateGroup = aTabs.length > 1 && utils.getTreePref('createSubtree.underParent'); + var root = shouldCreateGroup ? + b.addTab(this.getGroupTabURI({ + temporary: utils.getTreePref('createSubtree.underParent.temporaryGroup') + })) : + aTabs.shift() ; + var self = this; + this.Deferred.next(function(self) { + if (shouldCreateGroup) { + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + b.treeStyleTab.attachTabTo(tab, root); + b.treeStyleTab.collapseExpandTab(tab, false); + } + } + if (parent) { + b.treeStyleTab.attachTabTo(root, parent, { + insertBefore : next + }); + } + else if (next) { + b.treeStyleTab.moveTabSubtreeTo(root, next._tPos); + } + }).error(this.defaultDeferredErrorHandler); + }, + createSubTree : function(...aArgs) { + return this.createSubtree.apply(this, aArgs); + }, // obsolete, for backward compatibility + + canCreateSubtree : function TSTWindow_canCreateSubtree(aTabs) + { + var rootTabs = this.getRootTabs(aTabs); + if (rootTabs.length == 1) { + let descendants = this.getDescendantTabs(rootTabs[0]); + // are they already grouped? + // if it is a partial selection, I can create new group. + return (descendants.some(function(aDescendantTab) { + return aTabs.indexOf(aDescendantTab) < 0; + }, this)); + } + return true; + }, + canCreateSubTree : function(...aArgs) { + return this.canCreateSubtree.apply(this, aArgs); + }, // obsolete, for backward compatibility + + getRootTabs : function TSTWindow_getRootTabs(aTabs) + { + var roots = []; + if (!aTabs || !aTabs.length) + return roots; + aTabs = this.cleanUpTabsArray(aTabs); + for (let i = 0, maxi = aTabs.length; i < maxi; i++) + { + let tab = aTabs[i]; + let parent = this.getParentTab(tab); + if (parent && aTabs.indexOf(parent) > -1) + continue; + roots.push(tab); + } + return roots; + }, + + collapseExpandAllSubtree : function TSTWindow_collapseExpandAllSubtree(aCollapse) + { + Services.obs.notifyObservers( + this.window, + this.kTOPIC_COLLAPSE_EXPAND_ALL, + (aCollapse ? 'collapse' : 'open' ) + ); + }, + + promoteTab : function TSTWindow_promoteTab(aTab) /* PUBLIC API */ + { + var b = this.getTabBrowserFromChild(aTab); + var sv = b.treeStyleTab; + + var parent = sv.getParentTab(aTab); + if (!parent) + return; + + var nextSibling = sv.getNextSiblingTab(parent); + + var grandParent = sv.getParentTab(parent); + if (grandParent) { + sv.attachTabTo(aTab, grandParent, { + insertBefore : nextSibling + }); + } + else { + sv.detachTab(aTab); + let index = nextSibling ? nextSibling._tPos : b.mTabContainer.childNodes.length ; + if (index > aTab._tPos) + index--; + b.moveTabTo(aTab, index); + } + }, + + promoteCurrentTab : function TSTWindow_promoteCurrentTab() /* PUBLIC API */ + { + this.promoteTab(this.browser.selectedTab); + }, + + demoteTab : function TSTWindow_demoteTab(aTab) /* PUBLIC API */ + { + var b = this.getTabBrowserFromChild(aTab); + var sv = b.treeStyleTab; + + var previous = this.getPreviousSiblingTab(aTab); + if (previous) + sv.attachTabTo(aTab, previous); + }, + + demoteCurrentTab : function TSTWindow_demoteCurrentTab() /* PUBLIC API */ + { + this.demoteTab(this.browser.selectedTab); + }, + + expandTreeAfterKeyReleased : function TSTWindow_expandTreeAfterKeyReleased(aTab) + { + if (utils.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut')) + return; + this._tabShouldBeExpandedAfterKeyReleased = aTab || null; + }, + _tabShouldBeExpandedAfterKeyReleased : null, + + removeAllTabsBut : function TSTWindow_removeAllTabsBut(aTab) + { + var keepTabs = [aTab].concat(this.getDescendantTabs(aTab)); + var b = this.getTabBrowserFromChild(aTab); + var closeTabs = this.getTabs(b).filter(function(aTab) { + return keepTabs.indexOf(aTab) < 0 && !aTab.hasAttribute('pinned'); + }); + + if (!this.warnAboutClosingTabs(closeTabs.length)) + return; + + this.stopRendering(); + this.markAsClosedSet(closeTabs); + var tabs = closeTabs.reverse(); + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + b.removeTab(tabs[i]); + } + this.startRendering(); + }, + + // For backward compatibility. You should use DOM event to block TST's focus handling. + registerTabFocusAllowance : function TSTWindow_registerTabFocusAllowance(aProcess) /* PUBLIC API */ + { + var listener = { + process : aProcess, + handleEvent : function(aEvent) { + var tab = aEvent.originalTarget; + var b = tab.__treestyletab__linkedTabBrowser; + if (!this.process.call(b.treeStyleTab, b)) + aEvent.preventDefault(); + } + }; + this.window.addEventListener(this.kEVENT_TYPE_FOCUS_NEXT_TAB, listener, false); + this._tabFocusAllowance.push(listener); + }, + _tabFocusAllowance : [], + + tearOffSubtreeFromRemote : function TSTWindow_tearOffSubtreeFromRemote() + { + var w = this.window; + var remoteTab = w.arguments[0]; + var remoteWindow = remoteTab.ownerDocument.defaultView; + var remoteService = remoteWindow.TreeStyleTabService; + var remoteMultipleTabService = remoteWindow.MultipleTabService; + if (remoteService.hasChildTabs(remoteTab) || + (remoteMultipleTabService && remoteMultipleTabService.isSelected(remoteTab))) { + let remoteBrowser = remoteService.getTabBrowserFromChild(remoteTab); + if (remoteBrowser.treeStyleTab.tabbarDNDObserver.isDraggingAllTabs(remoteTab)) { + w.close(); + } + else { + let actionInfo = { + action : remoteTab.__treestyletab__toBeDuplicated ? this.kACTION_DUPLICATE : this.kACTION_IMPORT + }; + + let b = this.browser; + let blankTab; + this.Deferred + .next(function() { + var blankTab = b.selectedTab; + b.treeStyleTab.tabbarDNDObserver.performDrop(actionInfo, remoteTab); + return blankTab; + }) + .next(function(aBlankTab) { + b.removeTab(aBlankTab); + remoteTab = null; + remoteBrowser = null; + remoteWindow = null + remoteService = null; + remoteMultipleTabService = null; + }) + .error(this.defaultDeferredErrorHandler); + } + return true; + } + return false; + }, + tearOffSubTreeFromRemote : function(...aArgs) { + return this.tearOffSubtreeFromRemote.apply(this, aArgs); + }, // obsolete, for backward compatibility + + onPrintPreviewEnter : function TSTWindow_onPrintPreviewEnter() + { + var d = this.document; + var event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, true, false); + d.documentElement.dispatchEvent(event); + + // for backward compatibility + event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED.replace(/^nsDOM/, ''), true, false); + d.documentElement.dispatchEvent(event); + }, + + onPrintPreviewExit : function TSTWindow_onPrintPreviewExit() + { + var d = this.document; + var event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, true, false); + d.documentElement.dispatchEvent(event); + + // for backward compatibility + event = d.createEvent('Events'); + event.initEvent(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED.replace(/^nsDOM/, ''), true, false); + d.documentElement.dispatchEvent(event); + }, + + observe : function TSTWindow_observe(aSubject, aTopic, aData) + { + switch (aTopic) + { + case 'nsPref:changed': + this.onPrefChange(aData); + return; + } + }, + get restoringTree() { + if (this._restoringTree || !!this.restoringCount) + return true; + + var count = 0; + this.browser.visibleTabs.some(function(aTab) { + if (aTab.linkedBrowser.__treestyletab__toBeRestored) + count++; + return count > 1; + }); + return count > 1; + }, + set restoringTree(aValue) { + return this._restoringTree = !!aValue; + }, + _restoringTree : false, + +/* Pref Listener */ + + domains : [ + 'extensions.treestyletab', + 'browser.ctrlTab.previews' + ], + + onPrefChange : function TSTWindow_onPrefChange(aPrefName) + { + var value = prefs.getPref(aPrefName); + switch (aPrefName) + { + case 'extensions.treestyletab.tabbar.autoHide.mode': + // don't set on this time, because appearance of all tabbrowsers are not updated yet. + // this.autoHide.mode = utils.getTreePref('tabbar.autoHide.mode'); + case 'extensions.treestyletab.tabbar.autoShow.accelKeyDown': + case 'extensions.treestyletab.tabbar.autoShow.tabSwitch': + case 'extensions.treestyletab.tabbar.autoShow.feedback': + this.autoHideWindow.updateKeyListeners(this.window); + break; + + case 'extensions.treestyletab.tabbar.style': + case 'extensions.treestyletab.tabbar.position': + this.themeManager.set(prefs.getPref('extensions.treestyletab.tabbar.style'), this.position); + break; + + case 'browser.ctrlTab.previews': + this.autoHideWindow.updateKeyListeners(this.window); + case 'extensions.treestyletab.autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut': + case 'extensions.treestyletab.autoCollapseExpandSubtreeOnSelect': + if (this.shouldListenKeyEventsForAutoExpandByFocusChange) + this.startListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); + else + this.endListenKeyEventsFor(this.LISTEN_FOR_AUTOEXPAND_BY_FOCUSCHANGE); + break; + + default: + break; + } + } + +}); +