summaryrefslogtreecommitdiffstats
path: root/layout/xul
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul')
-rw-r--r--layout/xul/BoxObject.cpp614
-rw-r--r--layout/xul/BoxObject.h91
-rw-r--r--layout/xul/ContainerBoxObject.cpp75
-rw-r--r--layout/xul/ContainerBoxObject.h31
-rw-r--r--layout/xul/ListBoxObject.cpp238
-rw-r--r--layout/xul/ListBoxObject.h54
-rw-r--r--layout/xul/MenuBoxObject.cpp151
-rw-r--r--layout/xul/MenuBoxObject.h38
-rw-r--r--layout/xul/PopupBoxObject.cpp384
-rw-r--r--layout/xul/PopupBoxObject.h115
-rw-r--r--layout/xul/ScrollBoxObject.cpp384
-rw-r--r--layout/xul/ScrollBoxObject.h64
-rw-r--r--layout/xul/crashtests/131008-1.xul11
-rw-r--r--layout/xul/crashtests/137216-1.xul4
-rw-r--r--layout/xul/crashtests/140218-1.xml4
-rw-r--r--layout/xul/crashtests/151826-1.xul27
-rw-r--r--layout/xul/crashtests/168724-1.xul18
-rw-r--r--layout/xul/crashtests/189814-1.xul21
-rw-r--r--layout/xul/crashtests/237787-1.xul9
-rw-r--r--layout/xul/crashtests/265161-1.xul9
-rw-r--r--layout/xul/crashtests/289410-1.xul14
-rw-r--r--layout/xul/crashtests/290743.html6
-rw-r--r--layout/xul/crashtests/291702-1.xul11
-rw-r--r--layout/xul/crashtests/291702-2.xul11
-rw-r--r--layout/xul/crashtests/291702-3.xul137
-rw-r--r--layout/xul/crashtests/294371-1.xul53
-rw-r--r--layout/xul/crashtests/311457-1.html12
-rw-r--r--layout/xul/crashtests/321056-1.xhtml10
-rw-r--r--layout/xul/crashtests/322786-1.xul6
-rw-r--r--layout/xul/crashtests/325377.xul16
-rw-r--r--layout/xul/crashtests/326834-1-inner.xul17
-rw-r--r--layout/xul/crashtests/326834-1.html9
-rw-r--r--layout/xul/crashtests/326879-1.xul31
-rw-r--r--layout/xul/crashtests/327776-1.xul24
-rw-r--r--layout/xul/crashtests/328135-1.xul27
-rw-r--r--layout/xul/crashtests/329327-1.xul2
-rw-r--r--layout/xul/crashtests/329407-1.xml14
-rw-r--r--layout/xul/crashtests/329477-1.xhtml31
-rw-r--r--layout/xul/crashtests/336962-1.xul17
-rw-r--r--layout/xul/crashtests/344228-1.xul27
-rw-r--r--layout/xul/crashtests/346083-1.xul13
-rw-r--r--layout/xul/crashtests/346281-1.xul17
-rw-r--r--layout/xul/crashtests/350460.xul8
-rw-r--r--layout/xul/crashtests/360642-1.xul9
-rw-r--r--layout/xul/crashtests/365151.xul39
-rw-r--r--layout/xul/crashtests/366112-1.xul9
-rw-r--r--layout/xul/crashtests/366203-1.xul40
-rw-r--r--layout/xul/crashtests/367185-1.xhtml11
-rw-r--r--layout/xul/crashtests/369942-1.xhtml36
-rw-r--r--layout/xul/crashtests/374102-1.xul5
-rw-r--r--layout/xul/crashtests/376137-1.html18
-rw-r--r--layout/xul/crashtests/376137-2.html11
-rw-r--r--layout/xul/crashtests/377592-1.svg27
-rw-r--r--layout/xul/crashtests/378961.html9
-rw-r--r--layout/xul/crashtests/381862.html23
-rw-r--r--layout/xul/crashtests/382746-1.xul15
-rw-r--r--layout/xul/crashtests/382899-1.xul9
-rw-r--r--layout/xul/crashtests/383236-1.xul5
-rw-r--r--layout/xul/crashtests/384037-1.xhtml9
-rw-r--r--layout/xul/crashtests/384105-1-inner.xul21
-rw-r--r--layout/xul/crashtests/384105-1.html9
-rw-r--r--layout/xul/crashtests/384373-1.xul10
-rw-r--r--layout/xul/crashtests/384373-2.xul4
-rw-r--r--layout/xul/crashtests/384373.html23
-rw-r--r--layout/xul/crashtests/384491-1.xhtml8
-rw-r--r--layout/xul/crashtests/384871-1-inner.xul9
-rw-r--r--layout/xul/crashtests/384871-1.html9
-rw-r--r--layout/xul/crashtests/386642.xul31
-rw-r--r--layout/xul/crashtests/387033-1.xhtml31
-rw-r--r--layout/xul/crashtests/387080-1.xul6
-rw-r--r--layout/xul/crashtests/391974-1-inner.xul19
-rw-r--r--layout/xul/crashtests/391974-1.html9
-rw-r--r--layout/xul/crashtests/394120-1.xhtml19
-rw-r--r--layout/xul/crashtests/397293.xhtml21
-rw-r--r--layout/xul/crashtests/397304-1.html1
-rw-r--r--layout/xul/crashtests/398326-1.xhtml17
-rw-r--r--layout/xul/crashtests/399013.xul31
-rw-r--r--layout/xul/crashtests/400779-1.xhtml16
-rw-r--r--layout/xul/crashtests/402912-1.xhtml5
-rw-r--r--layout/xul/crashtests/404192.xhtml12
-rw-r--r--layout/xul/crashtests/407152.xul7
-rw-r--r--layout/xul/crashtests/408904-1.xul1
-rw-r--r--layout/xul/crashtests/412479-1.xhtml4
-rw-r--r--layout/xul/crashtests/415394-1.xhtml28
-rw-r--r--layout/xul/crashtests/417509.xul7
-rw-r--r--layout/xul/crashtests/420424-1.xul6
-rw-r--r--layout/xul/crashtests/430356-1.xhtml5
-rw-r--r--layout/xul/crashtests/431738.xhtml7
-rw-r--r--layout/xul/crashtests/432058-1.xul31
-rw-r--r--layout/xul/crashtests/432068-1.xul31
-rw-r--r--layout/xul/crashtests/432068-2.xul24
-rw-r--r--layout/xul/crashtests/433296-1.xul5
-rw-r--r--layout/xul/crashtests/433429.xul23
-rw-r--r--layout/xul/crashtests/434458-1.xul20
-rw-r--r--layout/xul/crashtests/452185.html3
-rw-r--r--layout/xul/crashtests/452185.xml5
-rw-r--r--layout/xul/crashtests/460900-1.xul3
-rw-r--r--layout/xul/crashtests/464149-1.xul24
-rw-r--r--layout/xul/crashtests/464407-1.xhtml9
-rw-r--r--layout/xul/crashtests/467080.xul24
-rw-r--r--layout/xul/crashtests/467481-1.xul6
-rw-r--r--layout/xul/crashtests/470063-1.html15
-rw-r--r--layout/xul/crashtests/470272.html21
-rw-r--r--layout/xul/crashtests/472189.xul13
-rw-r--r--layout/xul/crashtests/475133.html20
-rw-r--r--layout/xul/crashtests/488210-1.xhtml19
-rw-r--r--layout/xul/crashtests/495728-1.xul239
-rw-r--r--layout/xul/crashtests/508927-1.xul6
-rw-r--r--layout/xul/crashtests/508927-2.xul6
-rw-r--r--layout/xul/crashtests/514300-1.xul14
-rw-r--r--layout/xul/crashtests/536931-1.xhtml4
-rw-r--r--layout/xul/crashtests/538308-1.xul32
-rw-r--r--layout/xul/crashtests/557174-1.xml1
-rw-r--r--layout/xul/crashtests/564705-1.xul6
-rw-r--r--layout/xul/crashtests/583957-1.html20
-rw-r--r--layout/xul/crashtests/617089.html9
-rw-r--r--layout/xul/crashtests/716503.html11
-rw-r--r--layout/xul/crashtests/crashtests.list99
-rw-r--r--layout/xul/crashtests/menulist-focused.xhtml5
-rw-r--r--layout/xul/grid/crashtests/306911-crash.xul4
-rw-r--r--layout/xul/grid/crashtests/306911-grid-testcases.xul99
-rw-r--r--layout/xul/grid/crashtests/306911-grid-testcases2.xul98
-rw-r--r--layout/xul/grid/crashtests/311710-1.xul22
-rw-r--r--layout/xul/grid/crashtests/312784-1.xul29
-rw-r--r--layout/xul/grid/crashtests/313173-1-inner.xul41
-rw-r--r--layout/xul/grid/crashtests/313173-1.html9
-rw-r--r--layout/xul/grid/crashtests/321066-1.xul8
-rw-r--r--layout/xul/grid/crashtests/321073-1.xul7
-rw-r--r--layout/xul/grid/crashtests/382750-1.xul5
-rw-r--r--layout/xul/grid/crashtests/400790-1.xul20
-rw-r--r--layout/xul/grid/crashtests/423802-crash.xul13
-rw-r--r--layout/xul/grid/crashtests/crashtests.list11
-rw-r--r--layout/xul/grid/examples/borderedcolumns.xul43
-rw-r--r--layout/xul/grid/examples/borderedrowscolumns.xul55
-rw-r--r--layout/xul/grid/examples/borderedrowscolumns2.xul44
-rw-r--r--layout/xul/grid/examples/borderedrowscolumns3.xul57
-rw-r--r--layout/xul/grid/examples/bordermargincolumns1.xul44
-rw-r--r--layout/xul/grid/examples/collapsetest.xul67
-rw-r--r--layout/xul/grid/examples/divcolumngrid.xul33
-rw-r--r--layout/xul/grid/examples/divrowgrid.xul36
-rw-r--r--layout/xul/grid/examples/dynamicgrid.xul370
-rw-r--r--layout/xul/grid/examples/flexgroupgrid.xul47
-rw-r--r--layout/xul/grid/examples/javascriptappend.xul42
-rw-r--r--layout/xul/grid/examples/jumpygrid.xul82
-rw-r--r--layout/xul/grid/examples/nestedrows.xul48
-rw-r--r--layout/xul/grid/examples/rowspan.xul41
-rw-r--r--layout/xul/grid/examples/scrollingcolumns.xul80
-rw-r--r--layout/xul/grid/examples/scrollingrows.xul80
-rw-r--r--layout/xul/grid/examples/splitter.xul40
-rw-r--r--layout/xul/grid/moz.build43
-rw-r--r--layout/xul/grid/nsGrid.cpp1276
-rw-r--r--layout/xul/grid/nsGrid.h132
-rw-r--r--layout/xul/grid/nsGridCell.cpp127
-rw-r--r--layout/xul/grid/nsGridCell.h53
-rw-r--r--layout/xul/grid/nsGridLayout2.cpp266
-rw-r--r--layout/xul/grid/nsGridLayout2.h78
-rw-r--r--layout/xul/grid/nsGridRow.cpp57
-rw-r--r--layout/xul/grid/nsGridRow.h57
-rw-r--r--layout/xul/grid/nsGridRowGroupFrame.cpp63
-rw-r--r--layout/xul/grid/nsGridRowGroupFrame.h49
-rw-r--r--layout/xul/grid/nsGridRowGroupLayout.cpp265
-rw-r--r--layout/xul/grid/nsGridRowGroupLayout.h51
-rw-r--r--layout/xul/grid/nsGridRowLayout.cpp197
-rw-r--r--layout/xul/grid/nsGridRowLayout.h60
-rw-r--r--layout/xul/grid/nsGridRowLeafFrame.cpp94
-rw-r--r--layout/xul/grid/nsGridRowLeafFrame.h54
-rw-r--r--layout/xul/grid/nsGridRowLeafLayout.cpp328
-rw-r--r--layout/xul/grid/nsGridRowLeafLayout.h62
-rw-r--r--layout/xul/grid/nsIGridPart.h95
-rw-r--r--layout/xul/grid/reftests/column-sizing-1-ref.xul13
-rw-r--r--layout/xul/grid/reftests/column-sizing-1.xul15
-rw-r--r--layout/xul/grid/reftests/not-full-basic-ref.xhtml27
-rw-r--r--layout/xul/grid/reftests/not-full-basic.xul45
-rw-r--r--layout/xul/grid/reftests/not-full-grid-pack-align.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml27
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-align.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml27
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-direction.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml31
-rw-r--r--layout/xul/grid/reftests/not-full-row-group-pack.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-leaf-align.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-leaf-direction.xul46
-rw-r--r--layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml27
-rw-r--r--layout/xul/grid/reftests/not-full-row-leaf-pack.xul46
-rw-r--r--layout/xul/grid/reftests/reftest-stylo.list38
-rw-r--r--layout/xul/grid/reftests/reftest.list18
-rw-r--r--layout/xul/grid/reftests/row-or-column-sizing-1.xul21
-rw-r--r--layout/xul/grid/reftests/row-or-column-sizing-2.xul21
-rw-r--r--layout/xul/grid/reftests/row-or-column-sizing-3.xul27
-rw-r--r--layout/xul/grid/reftests/row-or-column-sizing-4.xul27
-rw-r--r--layout/xul/grid/reftests/row-sizing-1-ref.xul14
-rw-r--r--layout/xul/grid/reftests/row-sizing-1.xul16
-rw-r--r--layout/xul/grid/reftests/scrollable-columns-ref.xhtml25
-rw-r--r--layout/xul/grid/reftests/scrollable-columns.xul49
-rw-r--r--layout/xul/grid/reftests/scrollable-rows-ref.xhtml25
-rw-r--r--layout/xul/grid/reftests/scrollable-rows.xul49
-rw-r--r--layout/xul/grid/reftests/sizing-2d-ref.xul12
-rw-r--r--layout/xul/grid/reftests/sizing-2d.xul26
-rw-r--r--layout/xul/grid/reftests/z-order-1-ref.xul30
-rw-r--r--layout/xul/grid/reftests/z-order-1.xul47
-rw-r--r--layout/xul/grid/reftests/z-order-2-ref.xul30
-rw-r--r--layout/xul/grid/reftests/z-order-2.xul47
-rw-r--r--layout/xul/moz.build107
-rw-r--r--layout/xul/nsBox.cpp981
-rw-r--r--layout/xul/nsBox.h127
-rw-r--r--layout/xul/nsBoxFrame.cpp2111
-rw-r--r--layout/xul/nsBoxFrame.h257
-rw-r--r--layout/xul/nsBoxLayout.cpp94
-rw-r--r--layout/xul/nsBoxLayout.h65
-rw-r--r--layout/xul/nsBoxLayoutState.cpp38
-rw-r--r--layout/xul/nsBoxLayoutState.h77
-rw-r--r--layout/xul/nsButtonBoxFrame.cpp230
-rw-r--r--layout/xul/nsButtonBoxFrame.h75
-rw-r--r--layout/xul/nsDeckFrame.cpp231
-rw-r--r--layout/xul/nsDeckFrame.h77
-rw-r--r--layout/xul/nsDocElementBoxFrame.cpp148
-rw-r--r--layout/xul/nsGroupBoxFrame.cpp311
-rw-r--r--layout/xul/nsIBoxObject.idl40
-rw-r--r--layout/xul/nsIBrowserBoxObject.idl16
-rw-r--r--layout/xul/nsIContainerBoxObject.idl14
-rw-r--r--layout/xul/nsIListBoxObject.idl19
-rw-r--r--layout/xul/nsIMenuBoxObject.idl14
-rw-r--r--layout/xul/nsIRootBox.h33
-rw-r--r--layout/xul/nsIScrollBoxObject.idl12
-rw-r--r--layout/xul/nsIScrollbarMediator.h91
-rw-r--r--layout/xul/nsISliderListener.idl26
-rw-r--r--layout/xul/nsImageBoxFrame.cpp828
-rw-r--r--layout/xul/nsImageBoxFrame.h163
-rw-r--r--layout/xul/nsLeafBoxFrame.cpp390
-rw-r--r--layout/xul/nsLeafBoxFrame.h95
-rw-r--r--layout/xul/nsListBoxBodyFrame.cpp1535
-rw-r--r--layout/xul/nsListBoxBodyFrame.h217
-rw-r--r--layout/xul/nsListBoxLayout.cpp212
-rw-r--r--layout/xul/nsListBoxLayout.h32
-rw-r--r--layout/xul/nsListItemFrame.cpp69
-rw-r--r--layout/xul/nsListItemFrame.h34
-rw-r--r--layout/xul/nsMenuBarFrame.cpp434
-rw-r--r--layout/xul/nsMenuBarFrame.h125
-rw-r--r--layout/xul/nsMenuBarListener.cpp455
-rw-r--r--layout/xul/nsMenuBarListener.h74
-rw-r--r--layout/xul/nsMenuFrame.cpp1514
-rw-r--r--layout/xul/nsMenuFrame.h288
-rw-r--r--layout/xul/nsMenuParent.h69
-rw-r--r--layout/xul/nsMenuPopupFrame.cpp2442
-rw-r--r--layout/xul/nsMenuPopupFrame.h628
-rw-r--r--layout/xul/nsPIBoxObject.h37
-rw-r--r--layout/xul/nsPIListBoxObject.h30
-rw-r--r--layout/xul/nsPopupSetFrame.cpp160
-rw-r--r--layout/xul/nsPopupSetFrame.h64
-rw-r--r--layout/xul/nsProgressMeterFrame.cpp184
-rw-r--r--layout/xul/nsProgressMeterFrame.h46
-rw-r--r--layout/xul/nsRepeatService.cpp86
-rw-r--r--layout/xul/nsRepeatService.h60
-rw-r--r--layout/xul/nsResizerFrame.cpp538
-rw-r--r--layout/xul/nsResizerFrame.h71
-rw-r--r--layout/xul/nsRootBoxFrame.cpp291
-rw-r--r--layout/xul/nsScrollBoxFrame.cpp178
-rw-r--r--layout/xul/nsScrollbarButtonFrame.cpp298
-rw-r--r--layout/xul/nsScrollbarButtonFrame.h81
-rw-r--r--layout/xul/nsScrollbarFrame.cpp311
-rw-r--r--layout/xul/nsScrollbarFrame.h110
-rw-r--r--layout/xul/nsSliderFrame.cpp1446
-rw-r--r--layout/xul/nsSliderFrame.h206
-rw-r--r--layout/xul/nsSplitterFrame.cpp1046
-rw-r--r--layout/xul/nsSplitterFrame.h83
-rw-r--r--layout/xul/nsSprocketLayout.cpp1652
-rw-r--r--layout/xul/nsSprocketLayout.h142
-rw-r--r--layout/xul/nsStackFrame.cpp63
-rw-r--r--layout/xul/nsStackFrame.h46
-rw-r--r--layout/xul/nsStackLayout.cpp385
-rw-r--r--layout/xul/nsStackLayout.h56
-rw-r--r--layout/xul/nsTextBoxFrame.cpp1241
-rw-r--r--layout/xul/nsTextBoxFrame.h134
-rw-r--r--layout/xul/nsTitleBarFrame.cpp172
-rw-r--r--layout/xul/nsTitleBarFrame.h39
-rw-r--r--layout/xul/nsXULLabelFrame.cpp116
-rw-r--r--layout/xul/nsXULLabelFrame.h57
-rw-r--r--layout/xul/nsXULPopupManager.cpp2870
-rw-r--r--layout/xul/nsXULPopupManager.h823
-rw-r--r--layout/xul/nsXULTooltipListener.cpp729
-rw-r--r--layout/xul/nsXULTooltipListener.h102
-rw-r--r--layout/xul/reftest/image-scaling-min-height-1-ref.xul14
-rw-r--r--layout/xul/reftest/image-scaling-min-height-1.xul14
-rw-r--r--layout/xul/reftest/image-size-ref.xul115
-rw-r--r--layout/xul/reftest/image-size.xul123
-rw-r--r--layout/xul/reftest/image4x3.pngbin0 -> 176 bytes
-rw-r--r--layout/xul/reftest/popup-explicit-size-ref.xul6
-rw-r--r--layout/xul/reftest/popup-explicit-size.xul7
-rw-r--r--layout/xul/reftest/reftest-stylo.list14
-rw-r--r--layout/xul/reftest/reftest.list6
-rw-r--r--layout/xul/reftest/textbox-multiline-noresize.xul5
-rw-r--r--layout/xul/reftest/textbox-multiline-ref.xul5
-rw-r--r--layout/xul/reftest/textbox-multiline-resize.xul5
-rw-r--r--layout/xul/reftest/textbox-text-transform-ref.xul6
-rw-r--r--layout/xul/reftest/textbox-text-transform.xul6
-rw-r--r--layout/xul/test/browser.ini8
-rw-r--r--layout/xul/test/browser_bug1163304.js35
-rw-r--r--layout/xul/test/browser_bug685470.js19
-rw-r--r--layout/xul/test/browser_bug703210.js33
-rw-r--r--layout/xul/test/browser_bug706743.js84
-rw-r--r--layout/xul/test/chrome.ini24
-rw-r--r--layout/xul/test/mochitest.ini12
-rw-r--r--layout/xul/test/test_bug1197913.xul68
-rw-r--r--layout/xul/test/test_bug159346.xul135
-rw-r--r--layout/xul/test/test_bug372685.xul49
-rw-r--r--layout/xul/test/test_bug381167.xhtml49
-rw-r--r--layout/xul/test/test_bug386386.html34
-rw-r--r--layout/xul/test/test_bug393970.xul91
-rw-r--r--layout/xul/test/test_bug394800.xhtml39
-rw-r--r--layout/xul/test/test_bug398982-1.xul31
-rw-r--r--layout/xul/test/test_bug398982-2.xul33
-rw-r--r--layout/xul/test/test_bug467442.xul54
-rw-r--r--layout/xul/test/test_bug477754.xul49
-rw-r--r--layout/xul/test/test_bug511075.html121
-rw-r--r--layout/xul/test/test_bug563416.html53
-rw-r--r--layout/xul/test/test_bug703150.xul69
-rw-r--r--layout/xul/test/test_bug987230.xul125
-rw-r--r--layout/xul/test/test_popupReflowPos.xul76
-rw-r--r--layout/xul/test/test_popupSizeTo.xul55
-rw-r--r--layout/xul/test/test_popupZoom.xul57
-rw-r--r--layout/xul/test/test_resizer.xul94
-rw-r--r--layout/xul/test/test_resizer_incontent.xul42
-rw-r--r--layout/xul/test/test_splitter.xul117
-rw-r--r--layout/xul/test/test_stack.xul327
-rw-r--r--layout/xul/test/test_submenuClose.xul91
-rw-r--r--layout/xul/test/test_windowminmaxsize.xul245
-rw-r--r--layout/xul/test/window_resizer.xul113
-rw-r--r--layout/xul/test/window_resizer_element.xul188
-rw-r--r--layout/xul/tree/TreeBoxObject.cpp695
-rw-r--r--layout/xul/tree/TreeBoxObject.h128
-rw-r--r--layout/xul/tree/crashtests/307298-1.xul21
-rw-r--r--layout/xul/tree/crashtests/309732-1.xul30
-rw-r--r--layout/xul/tree/crashtests/309732-2.xul31
-rw-r--r--layout/xul/tree/crashtests/366583-1.xul43
-rw-r--r--layout/xul/tree/crashtests/380217-1.xul24
-rw-r--r--layout/xul/tree/crashtests/382444-1-inner.html15
-rw-r--r--layout/xul/tree/crashtests/382444-1.html9
-rw-r--r--layout/xul/tree/crashtests/391178-1.xhtml41
-rw-r--r--layout/xul/tree/crashtests/391178-2.xul20
-rw-r--r--layout/xul/tree/crashtests/393665-1.xul3
-rw-r--r--layout/xul/tree/crashtests/399227-1.xul44
-rw-r--r--layout/xul/tree/crashtests/399227-2.xul50
-rw-r--r--layout/xul/tree/crashtests/399692-1.xhtml10
-rw-r--r--layout/xul/tree/crashtests/399715-1.xhtml9
-rw-r--r--layout/xul/tree/crashtests/409807-1.xul25
-rw-r--r--layout/xul/tree/crashtests/414170-1.xul20
-rw-r--r--layout/xul/tree/crashtests/430394-1.xul8
-rw-r--r--layout/xul/tree/crashtests/454186-1.xul23
-rw-r--r--layout/xul/tree/crashtests/479931-1.xhtml19
-rw-r--r--layout/xul/tree/crashtests/509602-1-overlay.xul12
-rw-r--r--layout/xul/tree/crashtests/509602-1.xul3
-rw-r--r--layout/xul/tree/crashtests/585815-iframe.xul72
-rw-r--r--layout/xul/tree/crashtests/585815.html18
-rw-r--r--layout/xul/tree/crashtests/601427.html30
-rw-r--r--layout/xul/tree/crashtests/730441-1.xul54
-rw-r--r--layout/xul/tree/crashtests/730441-2.xul52
-rw-r--r--layout/xul/tree/crashtests/730441-3.xul38
-rw-r--r--layout/xul/tree/crashtests/crashtests.list24
-rw-r--r--layout/xul/tree/moz.build53
-rw-r--r--layout/xul/tree/nsITreeBoxObject.idl189
-rw-r--r--layout/xul/tree/nsITreeColumns.idl96
-rw-r--r--layout/xul/tree/nsITreeContentView.idl22
-rw-r--r--layout/xul/tree/nsITreeSelection.idl130
-rw-r--r--layout/xul/tree/nsITreeView.idl215
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.cpp4945
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.h651
-rw-r--r--layout/xul/tree/nsTreeColFrame.cpp201
-rw-r--r--layout/xul/tree/nsTreeColFrame.h55
-rw-r--r--layout/xul/tree/nsTreeColumns.cpp765
-rw-r--r--layout/xul/tree/nsTreeColumns.h215
-rw-r--r--layout/xul/tree/nsTreeContentView.cpp1372
-rw-r--r--layout/xul/tree/nsTreeContentView.h101
-rw-r--r--layout/xul/tree/nsTreeImageListener.cpp115
-rw-r--r--layout/xul/tree/nsTreeImageListener.h66
-rw-r--r--layout/xul/tree/nsTreeSelection.cpp866
-rw-r--r--layout/xul/tree/nsTreeSelection.h56
-rw-r--r--layout/xul/tree/nsTreeStyleCache.cpp99
-rw-r--r--layout/xul/tree/nsTreeStyleCache.h89
-rw-r--r--layout/xul/tree/nsTreeUtils.cpp140
-rw-r--r--layout/xul/tree/nsTreeUtils.h39
380 files changed, 54040 insertions, 0 deletions
diff --git a/layout/xul/BoxObject.cpp b/layout/xul/BoxObject.cpp
new file mode 100644
index 000000000..6636a6d62
--- /dev/null
+++ b/layout/xul/BoxObject.cpp
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BoxObject.h"
+#include "nsCOMPtr.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsContainerFrame.h"
+#include "nsIDocShell.h"
+#include "nsReadableUtils.h"
+#include "nsDOMClassInfoID.h"
+#include "nsView.h"
+#ifdef MOZ_XUL
+#include "nsIDOMXULElement.h"
+#else
+#include "nsIDOMElement.h"
+#endif
+#include "nsLayoutUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsSupportsPrimitives.h"
+#include "mozilla/dom/Element.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/dom/BoxObjectBinding.h"
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+namespace mozilla {
+namespace dom {
+
+// Static member variable initialization
+
+// Implement our nsISupports methods
+NS_IMPL_CYCLE_COLLECTION_CLASS(BoxObject)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BoxObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BoxObject)
+
+// QueryInterface implementation for BoxObject
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BoxObject)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIBoxObject)
+ NS_INTERFACE_MAP_ENTRY(nsPIBoxObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BoxObject)
+ // XXX jmorton: why aren't we unlinking mPropertyTable?
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BoxObject)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ if (tmp->mPropertyTable) {
+ for (auto iter = tmp->mPropertyTable->Iter(); !iter.Done(); iter.Next()) {
+ cb.NoteXPCOMChild(iter.UserData());
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BoxObject)
+
+// Constructors/Destructors
+BoxObject::BoxObject()
+ : mContent(nullptr)
+{
+}
+
+BoxObject::~BoxObject()
+{
+}
+
+NS_IMETHODIMP
+BoxObject::GetElement(nsIDOMElement** aResult)
+{
+ if (mContent) {
+ return CallQueryInterface(mContent, aResult);
+ }
+
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+// nsPIBoxObject //////////////////////////////////////////////////////////////////////////
+
+nsresult
+BoxObject::Init(nsIContent* aContent)
+{
+ mContent = aContent;
+ return NS_OK;
+}
+
+void
+BoxObject::Clear()
+{
+ mPropertyTable = nullptr;
+ mContent = nullptr;
+}
+
+void
+BoxObject::ClearCachedValues()
+{
+}
+
+nsIFrame*
+BoxObject::GetFrame(bool aFlushLayout)
+{
+ nsIPresShell* shell = GetPresShell(aFlushLayout);
+ if (!shell)
+ return nullptr;
+
+ if (!aFlushLayout) {
+ // If we didn't flush layout when getting the presshell, we should at least
+ // flush to make sure our frame model is up to date.
+ // XXXbz should flush on document, no? Except people call this from
+ // frame code, maybe?
+ shell->FlushPendingNotifications(Flush_Frames);
+ }
+
+ // The flush might have killed mContent.
+ if (!mContent) {
+ return nullptr;
+ }
+
+ return mContent->GetPrimaryFrame();
+}
+
+nsIPresShell*
+BoxObject::GetPresShell(bool aFlushLayout)
+{
+ if (!mContent) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mContent->GetUncomposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ if (aFlushLayout) {
+ doc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ return doc->GetShell();
+}
+
+nsresult
+BoxObject::GetOffsetRect(nsIntRect& aRect)
+{
+ aRect.SetRect(0, 0, 0, 0);
+
+ if (!mContent)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Get the Frame for our content
+ nsIFrame* frame = GetFrame(true);
+ if (frame) {
+ // Get its origin
+ nsPoint origin = frame->GetPositionIgnoringScrolling();
+
+ // Find the frame parent whose content is the document element.
+ Element* docElement = mContent->GetComposedDoc()->GetRootElement();
+ nsIFrame* parent = frame->GetParent();
+ for (;;) {
+ // If we've hit the document element, break here
+ if (parent->GetContent() == docElement) {
+ break;
+ }
+
+ nsIFrame* next = parent->GetParent();
+ if (!next) {
+ NS_WARNING("We should have hit the document element...");
+ origin += parent->GetPosition();
+ break;
+ }
+
+ // Add the parent's origin to our own to get to the
+ // right coordinate system
+ origin += next->GetPositionOfChildIgnoringScrolling(parent);
+ parent = next;
+ }
+
+ // For the origin, add in the border for the frame
+ const nsStyleBorder* border = frame->StyleBorder();
+ origin.x += border->GetComputedBorderWidth(NS_SIDE_LEFT);
+ origin.y += border->GetComputedBorderWidth(NS_SIDE_TOP);
+
+ // And subtract out the border for the parent
+ const nsStyleBorder* parentBorder = parent->StyleBorder();
+ origin.x -= parentBorder->GetComputedBorderWidth(NS_SIDE_LEFT);
+ origin.y -= parentBorder->GetComputedBorderWidth(NS_SIDE_TOP);
+
+ aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
+ aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
+
+ // Get the union of all rectangles in this and continuation frames.
+ // It doesn't really matter what we use as aRelativeTo here, since
+ // we only care about the size. Using 'parent' might make things
+ // a bit faster by speeding up the internal GetOffsetTo operations.
+ nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, parent);
+ aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width);
+ aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+BoxObject::GetScreenPosition(nsIntPoint& aPoint)
+{
+ aPoint.x = aPoint.y = 0;
+
+ if (!mContent)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsIFrame* frame = GetFrame(true);
+ if (frame) {
+ nsIntRect rect = frame->GetScreenRect();
+ aPoint.x = rect.x;
+ aPoint.y = rect.y;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetX(int32_t* aResult)
+{
+ nsIntRect rect;
+ GetOffsetRect(rect);
+ *aResult = rect.x;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetY(int32_t* aResult)
+{
+ nsIntRect rect;
+ GetOffsetRect(rect);
+ *aResult = rect.y;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetWidth(int32_t* aResult)
+{
+ nsIntRect rect;
+ GetOffsetRect(rect);
+ *aResult = rect.width;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetHeight(int32_t* aResult)
+{
+ nsIntRect rect;
+ GetOffsetRect(rect);
+ *aResult = rect.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetScreenX(int32_t *_retval)
+{
+ nsIntPoint position;
+ nsresult rv = GetScreenPosition(position);
+ if (NS_FAILED(rv)) return rv;
+ *_retval = position.x;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetScreenY(int32_t *_retval)
+{
+ nsIntPoint position;
+ nsresult rv = GetScreenPosition(position);
+ if (NS_FAILED(rv)) return rv;
+ *_retval = position.y;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetPropertyAsSupports(const char16_t* aPropertyName, nsISupports** aResult)
+{
+ NS_ENSURE_ARG(aPropertyName && *aPropertyName);
+ if (!mPropertyTable) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+ nsDependentString propertyName(aPropertyName);
+ mPropertyTable->Get(propertyName, aResult); // Addref here.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::SetPropertyAsSupports(const char16_t* aPropertyName, nsISupports* aValue)
+{
+ NS_ENSURE_ARG(aPropertyName && *aPropertyName);
+
+ if (!mPropertyTable) {
+ mPropertyTable = new nsInterfaceHashtable<nsStringHashKey,nsISupports>(4);
+ }
+
+ nsDependentString propertyName(aPropertyName);
+ mPropertyTable->Put(propertyName, aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetProperty(const char16_t* aPropertyName, char16_t** aResult)
+{
+ nsCOMPtr<nsISupports> data;
+ nsresult rv = GetPropertyAsSupports(aPropertyName,getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!data) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsString> supportsStr = do_QueryInterface(data);
+ if (!supportsStr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return supportsStr->ToString(aResult);
+}
+
+NS_IMETHODIMP
+BoxObject::SetProperty(const char16_t* aPropertyName, const char16_t* aPropertyValue)
+{
+ NS_ENSURE_ARG(aPropertyName && *aPropertyName);
+
+ nsDependentString propertyName(aPropertyName);
+ nsDependentString propertyValue;
+ if (aPropertyValue) {
+ propertyValue.Rebind(aPropertyValue);
+ } else {
+ propertyValue.SetIsVoid(true);
+ }
+
+ nsCOMPtr<nsISupportsString> supportsStr(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(supportsStr, NS_ERROR_OUT_OF_MEMORY);
+ supportsStr->SetData(propertyValue);
+
+ return SetPropertyAsSupports(aPropertyName,supportsStr);
+}
+
+NS_IMETHODIMP
+BoxObject::RemoveProperty(const char16_t* aPropertyName)
+{
+ NS_ENSURE_ARG(aPropertyName && *aPropertyName);
+
+ if (!mPropertyTable) return NS_OK;
+
+ nsDependentString propertyName(aPropertyName);
+ mPropertyTable->Remove(propertyName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetParentBox(nsIDOMElement * *aParentBox)
+{
+ *aParentBox = nullptr;
+ nsIFrame* frame = GetFrame(false);
+ if (!frame) return NS_OK;
+ nsIFrame* parent = frame->GetParent();
+ if (!parent) return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> el = do_QueryInterface(parent->GetContent());
+ *aParentBox = el;
+ NS_IF_ADDREF(*aParentBox);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetFirstChild(nsIDOMElement * *aFirstVisibleChild)
+{
+ *aFirstVisibleChild = nullptr;
+ nsIFrame* frame = GetFrame(false);
+ if (!frame) return NS_OK;
+ nsIFrame* firstFrame = frame->PrincipalChildList().FirstChild();
+ if (!firstFrame) return NS_OK;
+ // get the content for the box and query to a dom element
+ nsCOMPtr<nsIDOMElement> el = do_QueryInterface(firstFrame->GetContent());
+ el.swap(*aFirstVisibleChild);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetLastChild(nsIDOMElement * *aLastVisibleChild)
+{
+ *aLastVisibleChild = nullptr;
+ nsIFrame* frame = GetFrame(false);
+ if (!frame) return NS_OK;
+ return GetPreviousSibling(frame, nullptr, aLastVisibleChild);
+}
+
+NS_IMETHODIMP
+BoxObject::GetNextSibling(nsIDOMElement **aNextOrdinalSibling)
+{
+ *aNextOrdinalSibling = nullptr;
+ nsIFrame* frame = GetFrame(false);
+ if (!frame) return NS_OK;
+ nsIFrame* nextFrame = frame->GetNextSibling();
+ if (!nextFrame) return NS_OK;
+ // get the content for the box and query to a dom element
+ nsCOMPtr<nsIDOMElement> el = do_QueryInterface(nextFrame->GetContent());
+ el.swap(*aNextOrdinalSibling);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BoxObject::GetPreviousSibling(nsIDOMElement **aPreviousOrdinalSibling)
+{
+ *aPreviousOrdinalSibling = nullptr;
+ nsIFrame* frame = GetFrame(false);
+ if (!frame) return NS_OK;
+ nsIFrame* parentFrame = frame->GetParent();
+ if (!parentFrame) return NS_OK;
+ return GetPreviousSibling(parentFrame, frame, aPreviousOrdinalSibling);
+}
+
+nsresult
+BoxObject::GetPreviousSibling(nsIFrame* aParentFrame, nsIFrame* aFrame,
+ nsIDOMElement** aResult)
+{
+ *aResult = nullptr;
+ nsIFrame* nextFrame = aParentFrame->PrincipalChildList().FirstChild();
+ nsIFrame* prevFrame = nullptr;
+ while (nextFrame) {
+ if (nextFrame == aFrame)
+ break;
+ prevFrame = nextFrame;
+ nextFrame = nextFrame->GetNextSibling();
+ }
+
+ if (!prevFrame) return NS_OK;
+ // get the content for the box and query to a dom element
+ nsCOMPtr<nsIDOMElement> el = do_QueryInterface(prevFrame->GetContent());
+ el.swap(*aResult);
+ return NS_OK;
+}
+
+nsIContent*
+BoxObject::GetParentObject() const
+{
+ return mContent;
+}
+
+JSObject*
+BoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return BoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+Element*
+BoxObject::GetElement() const
+{
+ return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr;
+}
+
+int32_t
+BoxObject::X()
+{
+ int32_t ret = 0;
+ GetX(&ret);
+ return ret;
+}
+
+int32_t
+BoxObject::Y()
+{
+ int32_t ret = 0;
+ GetY(&ret);
+ return ret;
+}
+
+int32_t
+BoxObject::GetScreenX(ErrorResult& aRv)
+{
+ int32_t ret = 0;
+ aRv = GetScreenX(&ret);
+ return ret;
+}
+
+int32_t
+BoxObject::GetScreenY(ErrorResult& aRv)
+{
+ int32_t ret = 0;
+ aRv = GetScreenY(&ret);
+ return ret;
+}
+
+int32_t
+BoxObject::Width()
+{
+ int32_t ret = 0;
+ GetWidth(&ret);
+ return ret;
+}
+
+int32_t
+BoxObject::Height()
+{
+ int32_t ret = 0;
+ GetHeight(&ret);
+ return ret;
+}
+
+already_AddRefed<nsISupports>
+BoxObject::GetPropertyAsSupports(const nsAString& propertyName)
+{
+ nsCOMPtr<nsISupports> ret;
+ GetPropertyAsSupports(PromiseFlatString(propertyName).get(), getter_AddRefs(ret));
+ return ret.forget();
+}
+
+void
+BoxObject::SetPropertyAsSupports(const nsAString& propertyName, nsISupports* value)
+{
+ SetPropertyAsSupports(PromiseFlatString(propertyName).get(), value);
+}
+
+void
+BoxObject::GetProperty(const nsAString& propertyName, nsString& aRetVal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsISupports> data(GetPropertyAsSupports(propertyName));
+ if (!data) {
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> supportsStr(do_QueryInterface(data));
+ if (!supportsStr) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ supportsStr->GetData(aRetVal);
+}
+
+void
+BoxObject::SetProperty(const nsAString& propertyName, const nsAString& propertyValue)
+{
+ SetProperty(PromiseFlatString(propertyName).get(), PromiseFlatString(propertyValue).get());
+}
+
+void
+BoxObject::RemoveProperty(const nsAString& propertyName)
+{
+ RemoveProperty(PromiseFlatString(propertyName).get());
+}
+
+already_AddRefed<Element>
+BoxObject::GetParentBox()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetParentBox(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+BoxObject::GetFirstChild()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetFirstChild(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+BoxObject::GetLastChild()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetLastChild(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+BoxObject::GetNextSibling()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetNextSibling(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+BoxObject::GetPreviousSibling()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetPreviousSibling(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new BoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/BoxObject.h b/layout/xul/BoxObject.h
new file mode 100644
index 000000000..ac3df420b
--- /dev/null
+++ b/layout/xul/BoxObject.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BoxObject_h__
+#define mozilla_dom_BoxObject_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsIBoxObject.h"
+#include "nsPIBoxObject.h"
+#include "nsPoint.h"
+#include "nsAutoPtr.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsRect.h"
+
+class nsIFrame;
+class nsIPresShell;
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+
+class BoxObject : public nsPIBoxObject,
+ public nsWrapperCache
+{
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BoxObject)
+ NS_DECL_NSIBOXOBJECT
+
+public:
+ BoxObject();
+
+ // nsPIBoxObject
+ virtual nsresult Init(nsIContent* aContent) override;
+ virtual void Clear() override;
+ virtual void ClearCachedValues() override;
+
+ nsIFrame* GetFrame(bool aFlushLayout);
+ nsIPresShell* GetPresShell(bool aFlushLayout);
+ nsresult GetOffsetRect(nsIntRect& aRect);
+ nsresult GetScreenPosition(nsIntPoint& aPoint);
+
+ // Given a parent frame and a child frame, find the frame whose
+ // next sibling is the given child frame and return its element
+ static nsresult GetPreviousSibling(nsIFrame* aParentFrame, nsIFrame* aFrame,
+ nsIDOMElement** aResult);
+
+ // WebIDL (wraps old impls)
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ Element* GetElement() const;
+
+ int32_t X();
+ int32_t Y();
+ int32_t GetScreenX(ErrorResult& aRv);
+ int32_t GetScreenY(ErrorResult& aRv);
+ int32_t Width();
+ int32_t Height();
+
+ already_AddRefed<nsISupports> GetPropertyAsSupports(const nsAString& propertyName);
+ void SetPropertyAsSupports(const nsAString& propertyName, nsISupports* value);
+ void GetProperty(const nsAString& propertyName, nsString& aRetVal, ErrorResult& aRv);
+ void SetProperty(const nsAString& propertyName, const nsAString& propertyValue);
+ void RemoveProperty(const nsAString& propertyName);
+
+ already_AddRefed<Element> GetParentBox();
+ already_AddRefed<Element> GetFirstChild();
+ already_AddRefed<Element> GetLastChild();
+ already_AddRefed<Element> GetNextSibling();
+ already_AddRefed<Element> GetPreviousSibling();
+
+protected:
+ virtual ~BoxObject();
+
+ nsAutoPtr<nsInterfaceHashtable<nsStringHashKey,nsISupports> > mPropertyTable; //[OWNER]
+
+ nsIContent* mContent; // [WEAK]
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/layout/xul/ContainerBoxObject.cpp b/layout/xul/ContainerBoxObject.cpp
new file mode 100644
index 000000000..0464a6fec
--- /dev/null
+++ b/layout/xul/ContainerBoxObject.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContainerBoxObject.h"
+#include "mozilla/dom/ContainerBoxObjectBinding.h"
+#include "nsCOMPtr.h"
+#include "nsIDocShell.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsSubDocumentFrame.h"
+
+namespace mozilla {
+namespace dom {
+
+ContainerBoxObject::ContainerBoxObject()
+{
+}
+
+ContainerBoxObject::~ContainerBoxObject()
+{
+}
+
+JSObject*
+ContainerBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ContainerBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<nsIDocShell>
+ContainerBoxObject::GetDocShell()
+{
+ nsSubDocumentFrame *subDocFrame = do_QueryFrame(GetFrame(false));
+ if (subDocFrame) {
+ // Ok, the frame for mContent is an nsSubDocumentFrame, it knows how
+ // to reach the docshell, so ask it...
+ nsCOMPtr<nsIDocShell> ret;
+ subDocFrame->GetDocShell(getter_AddRefs(ret));
+ return ret.forget();
+ }
+
+ if (!mContent) {
+ return nullptr;
+ }
+
+ // No nsSubDocumentFrame available for mContent, try if there's a mapping
+ // between mContent's document to mContent's subdocument.
+
+ nsIDocument *doc = mContent->GetComposedDoc();
+
+ if (!doc) {
+ return nullptr;
+ }
+
+ nsIDocument *sub_doc = doc->GetSubDocumentFor(mContent);
+
+ if (!sub_doc) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> result = sub_doc->GetDocShell();
+ return result.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+nsresult
+NS_NewContainerBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new mozilla::dom::ContainerBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/ContainerBoxObject.h b/layout/xul/ContainerBoxObject.h
new file mode 100644
index 000000000..b3da97991
--- /dev/null
+++ b/layout/xul/ContainerBoxObject.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ContainerBoxObject_h
+#define mozilla_dom_ContainerBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class ContainerBoxObject final : public BoxObject
+{
+public:
+ ContainerBoxObject();
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<nsIDocShell> GetDocShell();
+
+private:
+ ~ContainerBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ContainerBoxObject_h
diff --git a/layout/xul/ListBoxObject.cpp b/layout/xul/ListBoxObject.cpp
new file mode 100644
index 000000000..435065f5b
--- /dev/null
+++ b/layout/xul/ListBoxObject.cpp
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ListBoxObject.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIScrollableFrame.h"
+#include "nsListBoxBodyFrame.h"
+#include "ChildIterator.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ListBoxObjectBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS_INHERITED(ListBoxObject, BoxObject, nsIListBoxObject,
+ nsPIListBoxObject)
+
+ListBoxObject::ListBoxObject()
+ : mListBoxBody(nullptr)
+{
+}
+
+ListBoxObject::~ListBoxObject()
+{
+}
+
+JSObject* ListBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ListBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// nsIListBoxObject
+NS_IMETHODIMP
+ListBoxObject::GetRowCount(int32_t *aResult)
+{
+ *aResult = GetRowCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ListBoxObject::GetItemAtIndex(int32_t index, nsIDOMElement **_retval)
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ return body->GetItemAtIndex(index, _retval);
+ }
+ return NS_OK;
+ }
+
+NS_IMETHODIMP
+ListBoxObject::GetIndexOfItem(nsIDOMElement* aElement, int32_t *aResult)
+{
+ *aResult = 0;
+
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ return body->GetIndexOfItem(aElement, aResult);
+ }
+ return NS_OK;
+}
+
+// ListBoxObject
+
+int32_t
+ListBoxObject::GetRowCount()
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ return body->GetRowCount();
+ }
+ return 0;
+}
+
+int32_t
+ListBoxObject::GetNumberOfVisibleRows()
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ return body->GetNumberOfVisibleRows();
+ }
+ return 0;
+}
+
+int32_t
+ListBoxObject::GetIndexOfFirstVisibleRow()
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ return body->GetIndexOfFirstVisibleRow();
+ }
+ return 0;
+}
+
+void
+ListBoxObject::EnsureIndexIsVisible(int32_t aRowIndex)
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ body->EnsureIndexIsVisible(aRowIndex);
+ }
+}
+
+void
+ListBoxObject::ScrollToIndex(int32_t aRowIndex)
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ body->ScrollToIndex(aRowIndex);
+ }
+}
+
+void
+ListBoxObject::ScrollByLines(int32_t aNumLines)
+{
+ nsListBoxBodyFrame* body = GetListBoxBody(true);
+ if (body) {
+ body->ScrollByLines(aNumLines);
+ }
+}
+
+already_AddRefed<Element>
+ListBoxObject::GetItemAtIndex(int32_t index)
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetItemAtIndex(index, getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+int32_t
+ListBoxObject::GetIndexOfItem(Element& aElement)
+{
+ int32_t ret;
+ nsCOMPtr<nsIDOMElement> el(do_QueryInterface(&aElement));
+ GetIndexOfItem(el, &ret);
+ return ret;
+}
+
+//////////////////////
+
+static nsIContent*
+FindBodyContent(nsIContent* aParent)
+{
+ if (aParent->IsXULElement(nsGkAtoms::listboxbody)) {
+ return aParent;
+ }
+
+ mozilla::dom::FlattenedChildIterator iter(aParent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ nsIContent* result = FindBodyContent(child);
+ if (result) {
+ return result;
+ }
+ }
+
+ return nullptr;
+}
+
+nsListBoxBodyFrame*
+ListBoxObject::GetListBoxBody(bool aFlush)
+{
+ if (mListBoxBody) {
+ return mListBoxBody;
+ }
+
+ nsIPresShell* shell = GetPresShell(false);
+ if (!shell) {
+ return nullptr;
+ }
+
+ nsIFrame* frame = aFlush ?
+ GetFrame(false) /* does Flush_Frames */ :
+ mContent->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ // Iterate over our content model children looking for the body.
+ nsCOMPtr<nsIContent> content = FindBodyContent(frame->GetContent());
+
+ if (!content) {
+ return nullptr;
+ }
+
+ // this frame will be a nsGFXScrollFrame
+ frame = content->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(frame);
+ if (!scrollFrame) {
+ return nullptr;
+ }
+
+ // this frame will be the one we want
+ nsIFrame* yeahBaby = scrollFrame->GetScrolledFrame();
+ if (!yeahBaby) {
+ return nullptr;
+ }
+
+ // It's a frame. Refcounts are irrelevant.
+ nsListBoxBodyFrame* listBoxBody = do_QueryFrame(yeahBaby);
+ NS_ENSURE_TRUE(listBoxBody &&
+ listBoxBody->SetBoxObject(this),
+ nullptr);
+ mListBoxBody = listBoxBody;
+ return mListBoxBody;
+}
+
+void
+ListBoxObject::Clear()
+{
+ ClearCachedValues();
+ BoxObject::Clear();
+}
+
+void
+ListBoxObject::ClearCachedValues()
+{
+ mListBoxBody = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewListBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new mozilla::dom::ListBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/ListBoxObject.h b/layout/xul/ListBoxObject.h
new file mode 100644
index 000000000..39cdf5c8c
--- /dev/null
+++ b/layout/xul/ListBoxObject.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ListBoxObject_h
+#define mozilla_dom_ListBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+#include "nsPIListBoxObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class ListBoxObject final : public BoxObject,
+ public nsPIListBoxObject
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSILISTBOXOBJECT
+
+ ListBoxObject();
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsPIListBoxObject
+ virtual nsListBoxBodyFrame* GetListBoxBody(bool aFlush) override;
+
+ // nsPIBoxObject
+ virtual void Clear() override;
+ virtual void ClearCachedValues() override;
+
+ // ListBoxObject.webidl
+ int32_t GetRowCount();
+ int32_t GetNumberOfVisibleRows();
+ int32_t GetIndexOfFirstVisibleRow();
+ void EnsureIndexIsVisible(int32_t rowIndex);
+ void ScrollToIndex(int32_t rowIndex);
+ void ScrollByLines(int32_t numLines);
+ already_AddRefed<Element> GetItemAtIndex(int32_t index);
+ int32_t GetIndexOfItem(Element& item);
+
+protected:
+ nsListBoxBodyFrame *mListBoxBody;
+
+private:
+ ~ListBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ListBoxObject_h
diff --git a/layout/xul/MenuBoxObject.cpp b/layout/xul/MenuBoxObject.cpp
new file mode 100644
index 000000000..ab105fd17
--- /dev/null
+++ b/layout/xul/MenuBoxObject.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MenuBoxObject.h"
+#include "mozilla/dom/MenuBoxObjectBinding.h"
+
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIFrame.h"
+#include "nsMenuBarFrame.h"
+#include "nsMenuBarListener.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+
+namespace mozilla {
+namespace dom {
+
+MenuBoxObject::MenuBoxObject()
+{
+}
+
+MenuBoxObject::~MenuBoxObject()
+{
+}
+
+JSObject* MenuBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MenuBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void MenuBoxObject::OpenMenu(bool aOpenFlag)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsIFrame* frame = GetFrame(false);
+ if (frame) {
+ if (aOpenFlag) {
+ nsCOMPtr<nsIContent> content = mContent;
+ pm->ShowMenu(content, false, false);
+ }
+ else {
+ nsMenuFrame* menu = do_QueryFrame(frame);
+ if (menu) {
+ nsMenuPopupFrame* popupFrame = menu->GetPopup();
+ if (popupFrame)
+ pm->HidePopup(popupFrame->GetContent(), false, true, false, false);
+ }
+ }
+ }
+ }
+}
+
+already_AddRefed<Element>
+MenuBoxObject::GetActiveChild()
+{
+ nsMenuFrame* menu = do_QueryFrame(GetFrame(false));
+ if (menu) {
+ nsCOMPtr<nsIDOMElement> el;
+ menu->GetActiveChild(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+ }
+ return nullptr;
+}
+
+void MenuBoxObject::SetActiveChild(Element* arg)
+{
+ nsMenuFrame* menu = do_QueryFrame(GetFrame(false));
+ if (menu) {
+ nsCOMPtr<nsIDOMElement> el(do_QueryInterface(arg));
+ menu->SetActiveChild(el);
+ }
+}
+
+bool MenuBoxObject::HandleKeyPress(KeyboardEvent& keyEvent)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return false;
+ }
+
+ // if event has already been handled, bail
+ bool eventHandled = false;
+ keyEvent.GetDefaultPrevented(&eventHandled);
+ if (eventHandled) {
+ return false;
+ }
+
+ if (nsMenuBarListener::IsAccessKeyPressed(&keyEvent))
+ return false;
+
+ nsMenuFrame* menu = do_QueryFrame(GetFrame(false));
+ if (!menu) {
+ return false;
+ }
+
+ nsMenuPopupFrame* popupFrame = menu->GetPopup();
+ if (!popupFrame) {
+ return false;
+ }
+
+ uint32_t keyCode = keyEvent.KeyCode();
+ switch (keyCode) {
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+ case nsIDOMKeyEvent::DOM_VK_HOME:
+ case nsIDOMKeyEvent::DOM_VK_END:
+ {
+ nsNavigationDirection theDirection;
+ theDirection = NS_DIRECTION_FROM_KEY_CODE(popupFrame, keyCode);
+ return pm->HandleKeyboardNavigationInPopup(popupFrame, theDirection);
+ }
+ default:
+ return pm->HandleShortcutNavigation(&keyEvent, popupFrame);
+ }
+}
+
+bool MenuBoxObject::OpenedWithKey()
+{
+ nsMenuFrame* menuframe = do_QueryFrame(GetFrame(false));
+ if (!menuframe) {
+ return false;
+ }
+
+ nsIFrame* frame = menuframe->GetParent();
+ while (frame) {
+ nsMenuBarFrame* menubar = do_QueryFrame(frame);
+ if (menubar) {
+ return menubar->IsActiveByKeyboard();
+ }
+ frame = frame->GetParent();
+ }
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewMenuBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new MenuBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/MenuBoxObject.h b/layout/xul/MenuBoxObject.h
new file mode 100644
index 000000000..652e3231f
--- /dev/null
+++ b/layout/xul/MenuBoxObject.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MenuBoxObject_h
+#define mozilla_dom_MenuBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class KeyboardEvent;
+
+class MenuBoxObject final : public BoxObject
+{
+public:
+
+ MenuBoxObject();
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void OpenMenu(bool aOpenFlag);
+ already_AddRefed<Element> GetActiveChild();
+ void SetActiveChild(Element* arg);
+ bool HandleKeyPress(KeyboardEvent& keyEvent);
+ bool OpenedWithKey();
+
+private:
+ ~MenuBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MenuBoxObject_h
diff --git a/layout/xul/PopupBoxObject.cpp b/layout/xul/PopupBoxObject.cpp
new file mode 100644
index 000000000..00ecc943d
--- /dev/null
+++ b/layout/xul/PopupBoxObject.cpp
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIRootBox.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsMenuPopupFrame.h"
+#include "nsView.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/PopupBoxObject.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/PopupBoxObjectBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ADDREF_INHERITED(PopupBoxObject, BoxObject)
+NS_IMPL_RELEASE_INHERITED(PopupBoxObject, BoxObject)
+NS_INTERFACE_MAP_BEGIN(PopupBoxObject)
+NS_INTERFACE_MAP_END_INHERITING(BoxObject)
+
+PopupBoxObject::PopupBoxObject()
+{
+}
+
+PopupBoxObject::~PopupBoxObject()
+{
+}
+
+nsIContent* PopupBoxObject::GetParentObject() const
+{
+ return BoxObject::GetParentObject();
+}
+
+JSObject* PopupBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PopupBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPopupSetFrame*
+PopupBoxObject::GetPopupSetFrame()
+{
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(GetPresShell(false));
+ if (!rootBox)
+ return nullptr;
+
+ return rootBox->GetPopupSetFrame();
+}
+
+void
+PopupBoxObject::HidePopup(bool aCancel)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && mContent) {
+ pm->HidePopup(mContent, false, true, false, aCancel);
+ }
+}
+
+void
+PopupBoxObject::ShowPopup(Element* aAnchorElement,
+ Element& aPopupElement,
+ int32_t aXPos, int32_t aYPos,
+ const nsAString& aPopupType,
+ const nsAString& aAnchorAlignment,
+ const nsAString& aPopupAlignment)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && mContent) {
+ nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement));
+ nsAutoString popupType(aPopupType);
+ nsAutoString anchor(aAnchorAlignment);
+ nsAutoString align(aPopupAlignment);
+ pm->ShowPopupWithAnchorAlign(mContent, anchorContent, anchor, align,
+ aXPos, aYPos,
+ popupType.EqualsLiteral("context"));
+ }
+}
+
+void
+PopupBoxObject::OpenPopup(Element* aAnchorElement,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ Event* aTriggerEvent)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && mContent) {
+ nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement));
+ pm->ShowPopup(mContent, anchorContent, aPosition, aXPos, aYPos,
+ aIsContextMenu, aAttributesOverride, false, aTriggerEvent);
+ }
+}
+
+void
+PopupBoxObject::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ Event* aTriggerEvent)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && mContent)
+ pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
+}
+
+void
+PopupBoxObject::OpenPopupAtScreenRect(const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ int32_t aWidth, int32_t aHeight,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ Event* aTriggerEvent)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && mContent) {
+ pm->ShowPopupAtScreenRect(mContent, aPosition,
+ nsIntRect(aXPos, aYPos, aWidth, aHeight),
+ aIsContextMenu, aAttributesOverride, aTriggerEvent);
+ }
+}
+
+void
+PopupBoxObject::MoveTo(int32_t aLeft, int32_t aTop)
+{
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ if (menuPopupFrame) {
+ menuPopupFrame->MoveTo(CSSIntPoint(aLeft, aTop), true);
+ }
+}
+
+void
+PopupBoxObject::MoveToAnchor(Element* aAnchorElement,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aAttributesOverride)
+{
+ if (mContent) {
+ nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement));
+
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (menuPopupFrame && menuPopupFrame->IsVisible()) {
+ menuPopupFrame->MoveToAnchor(anchorContent, aPosition, aXPos, aYPos, aAttributesOverride);
+ }
+ }
+}
+
+void
+PopupBoxObject::SizeTo(int32_t aWidth, int32_t aHeight)
+{
+ if (!mContent)
+ return;
+
+ nsAutoString width, height;
+ width.AppendInt(aWidth);
+ height.AppendInt(aHeight);
+
+ nsCOMPtr<nsIContent> content = mContent;
+
+ // We only want to pass aNotify=true to SetAttr once, but must make sure
+ // we pass it when a value is being changed. Thus, we check if the height
+ // is the same and if so, pass true when setting the width.
+ bool heightSame = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::height, height, eCaseMatters);
+
+ content->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, heightSame);
+ content->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
+}
+
+bool
+PopupBoxObject::AutoPosition()
+{
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ if (menuPopupFrame) {
+ return menuPopupFrame->GetAutoPosition();
+ }
+ return true;
+}
+
+void
+PopupBoxObject::SetAutoPosition(bool aShouldAutoPosition)
+{
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ if (menuPopupFrame) {
+ menuPopupFrame->SetAutoPosition(aShouldAutoPosition);
+ }
+}
+
+void
+PopupBoxObject::EnableRollup(bool aShouldRollup)
+{
+ // this does nothing now
+}
+
+void
+PopupBoxObject::SetConsumeRollupEvent(uint32_t aConsume)
+{
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false));
+ if (menuPopupFrame) {
+ menuPopupFrame->SetConsumeRollupEvent(aConsume);
+ }
+}
+
+void
+PopupBoxObject::EnableKeyboardNavigator(bool aEnableKeyboardNavigator)
+{
+ if (!mContent)
+ return;
+
+ // Use ignorekeys="true" on the popup instead of using this function.
+ if (aEnableKeyboardNavigator)
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, true);
+ else
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys,
+ NS_LITERAL_STRING("true"), true);
+}
+
+void
+PopupBoxObject::GetPopupState(nsString& aState)
+{
+ // set this here in case there's no frame for the popup
+ aState.AssignLiteral("closed");
+
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ if (menuPopupFrame) {
+ switch (menuPopupFrame->PopupState()) {
+ case ePopupShown:
+ aState.AssignLiteral("open");
+ break;
+ case ePopupShowing:
+ case ePopupPositioning:
+ case ePopupOpening:
+ case ePopupVisible:
+ aState.AssignLiteral("showing");
+ break;
+ case ePopupHiding:
+ case ePopupInvisible:
+ aState.AssignLiteral("hiding");
+ break;
+ case ePopupClosed:
+ break;
+ default:
+ NS_NOTREACHED("Bad popup state");
+ break;
+ }
+ }
+}
+
+nsINode*
+PopupBoxObject::GetTriggerNode() const
+{
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame);
+}
+
+Element*
+PopupBoxObject::GetAnchorNode() const
+{
+ nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr;
+ if (!menuPopupFrame) {
+ return nullptr;
+ }
+
+ nsIContent* anchor = menuPopupFrame->GetAnchor();
+ return anchor && anchor->IsElement() ? anchor->AsElement() : nullptr;
+}
+
+already_AddRefed<DOMRect>
+PopupBoxObject::GetOuterScreenRect()
+{
+ RefPtr<DOMRect> rect = new DOMRect(mContent);
+
+ // Return an empty rectangle if the popup is not open.
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false));
+ if (!menuPopupFrame || !menuPopupFrame->IsOpen()) {
+ return rect.forget();
+ }
+
+ nsView* view = menuPopupFrame->GetView();
+ if (view) {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ LayoutDeviceIntRect screenRect = widget->GetScreenBounds();
+
+ int32_t pp = menuPopupFrame->PresContext()->AppUnitsPerDevPixel();
+ rect->SetLayoutRect(LayoutDeviceIntRect::ToAppUnits(screenRect, pp));
+ }
+ }
+ return rect.forget();
+}
+
+void
+PopupBoxObject::GetAlignmentPosition(nsString& positionStr)
+{
+ positionStr.Truncate();
+
+ // This needs to flush layout.
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(true));
+ if (!menuPopupFrame)
+ return;
+
+ int8_t position = menuPopupFrame->GetAlignmentPosition();
+ switch (position) {
+ case POPUPPOSITION_AFTERSTART:
+ positionStr.AssignLiteral("after_start");
+ break;
+ case POPUPPOSITION_AFTEREND:
+ positionStr.AssignLiteral("after_end");
+ break;
+ case POPUPPOSITION_BEFORESTART:
+ positionStr.AssignLiteral("before_start");
+ break;
+ case POPUPPOSITION_BEFOREEND:
+ positionStr.AssignLiteral("before_end");
+ break;
+ case POPUPPOSITION_STARTBEFORE:
+ positionStr.AssignLiteral("start_before");
+ break;
+ case POPUPPOSITION_ENDBEFORE:
+ positionStr.AssignLiteral("end_before");
+ break;
+ case POPUPPOSITION_STARTAFTER:
+ positionStr.AssignLiteral("start_after");
+ break;
+ case POPUPPOSITION_ENDAFTER:
+ positionStr.AssignLiteral("end_after");
+ break;
+ case POPUPPOSITION_OVERLAP:
+ positionStr.AssignLiteral("overlap");
+ break;
+ case POPUPPOSITION_AFTERPOINTER:
+ positionStr.AssignLiteral("after_pointer");
+ break;
+ case POPUPPOSITION_SELECTION:
+ positionStr.AssignLiteral("selection");
+ break;
+ default:
+ // Leave as an empty string.
+ break;
+ }
+}
+
+int32_t
+PopupBoxObject::AlignmentOffset()
+{
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false));
+ if (!menuPopupFrame)
+ return 0;
+
+ int32_t pp = mozilla::AppUnitsPerCSSPixel();
+ // Note that the offset might be along either the X or Y axis, but for the
+ // sake of simplicity we use a point with only the X axis set so we can
+ // use ToNearestPixels().
+ nsPoint appOffset(menuPopupFrame->GetAlignmentOffset(), 0);
+ nsIntPoint popupOffset = appOffset.ToNearestPixels(pp);
+ return popupOffset.x;
+}
+
+void
+PopupBoxObject::SetConstraintRect(dom::DOMRectReadOnly& aRect)
+{
+ nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false));
+ if (menuPopupFrame) {
+ menuPopupFrame->SetOverrideConstraintRect(
+ LayoutDeviceIntRect::Truncate(aRect.Left(), aRect.Top(), aRect.Width(), aRect.Height()));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewPopupBoxObject(nsIBoxObject** aResult)
+{
+ *aResult = new mozilla::dom::PopupBoxObject();
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
diff --git a/layout/xul/PopupBoxObject.h b/layout/xul/PopupBoxObject.h
new file mode 100644
index 000000000..a660393b5
--- /dev/null
+++ b/layout/xul/PopupBoxObject.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PopupBoxObject_h
+#define mozilla_dom_PopupBoxObject_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/BoxObject.h"
+#include "nsString.h"
+
+struct JSContext;
+class nsPopupSetFrame;
+
+namespace mozilla {
+namespace dom {
+
+class DOMRect;
+class Element;
+class Event;
+
+class PopupBoxObject final : public BoxObject
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // also in PopupBoxObject.webidl
+ static const uint32_t ROLLUP_DEFAULT = 0; /* widget/platform default */
+ static const uint32_t ROLLUP_CONSUME = 1; /* consume the rollup event */
+ static const uint32_t ROLLUP_NO_CONSUME = 2; /* don't consume the rollup event */
+
+ PopupBoxObject();
+
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void ShowPopup(Element* aAnchorElement,
+ Element& aPopupElement,
+ int32_t aXPos,
+ int32_t aYPos,
+ const nsAString& aPopupType,
+ const nsAString& aAnchorAlignment,
+ const nsAString& aPopupAlignment);
+
+ void HidePopup(bool aCancel);
+
+ bool AutoPosition();
+
+ void SetAutoPosition(bool aShouldAutoPosition);
+
+ void EnableKeyboardNavigator(bool aEnableKeyboardNavigator);
+
+ void EnableRollup(bool aShouldRollup);
+
+ void SetConsumeRollupEvent(uint32_t aConsume);
+
+ void SizeTo(int32_t aWidth, int32_t aHeight);
+
+ void MoveTo(int32_t aLeft, int32_t aTop);
+
+ void OpenPopup(Element* aAnchorElement,
+ const nsAString& aPosition,
+ int32_t aXPos,
+ int32_t aYPos,
+ bool aIsContextMenu, bool aAttributesOverride,
+ Event* aTriggerEvent);
+
+ void OpenPopupAtScreen(int32_t aXPos,
+ int32_t aYPos,
+ bool aIsContextMenu,
+ Event* aTriggerEvent);
+
+ void OpenPopupAtScreenRect(const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ int32_t aWidth, int32_t aHeight,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ Event* aTriggerEvent);
+
+ void GetPopupState(nsString& aState);
+
+ nsINode* GetTriggerNode() const;
+
+ Element* GetAnchorNode() const;
+
+ already_AddRefed<DOMRect> GetOuterScreenRect();
+
+ void MoveToAnchor(Element* aAnchorElement,
+ const nsAString& aPosition,
+ int32_t aXPos,
+ int32_t aYPos,
+ bool aAttributesOverride);
+
+ void GetAlignmentPosition(nsString& positionStr);
+
+ int32_t AlignmentOffset();
+
+ void SetConstraintRect(dom::DOMRectReadOnly& aRect);
+
+private:
+ ~PopupBoxObject();
+
+protected:
+ nsPopupSetFrame* GetPopupSetFrame();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PopupBoxObject_h
diff --git a/layout/xul/ScrollBoxObject.cpp b/layout/xul/ScrollBoxObject.cpp
new file mode 100644
index 000000000..26f7bc9bb
--- /dev/null
+++ b/layout/xul/ScrollBoxObject.cpp
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ScrollBoxObject.h"
+#include "mozilla/dom/ScrollBoxObjectBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsCOMPtr.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsPresContext.h"
+#include "nsBox.h"
+#include "nsIScrollableFrame.h"
+
+namespace mozilla {
+namespace dom {
+
+ScrollBoxObject::ScrollBoxObject()
+{
+}
+
+ScrollBoxObject::~ScrollBoxObject()
+{
+}
+
+JSObject* ScrollBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ScrollBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIScrollableFrame* ScrollBoxObject::GetScrollFrame()
+{
+ return do_QueryFrame(GetFrame(false));
+}
+
+void ScrollBoxObject::ScrollTo(int32_t x, int32_t y, ErrorResult& aRv)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ sf->ScrollToCSSPixels(CSSIntPoint(x, y));
+}
+
+void ScrollBoxObject::ScrollBy(int32_t dx, int32_t dy, ErrorResult& aRv)
+{
+ CSSIntPoint pt;
+ GetPosition(pt, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ ScrollTo(pt.x + dx, pt.y + dy, aRv);
+}
+
+void ScrollBoxObject::ScrollByLine(int32_t dlines, ErrorResult& aRv)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ sf->ScrollBy(nsIntPoint(0, dlines), nsIScrollableFrame::LINES,
+ nsIScrollableFrame::SMOOTH);
+}
+
+// XUL <scrollbox> elements have a single box child element.
+// Get a pointer to that box.
+// Note that now that the <scrollbox> is just a regular box
+// with 'overflow:hidden', the boxobject's frame is an nsXULScrollFrame,
+// the <scrollbox>'s box frame is the scrollframe's "scrolled frame", and
+// the <scrollbox>'s child box is a child of that.
+static nsIFrame* GetScrolledBox(BoxObject* aScrollBox) {
+ nsIFrame* frame = aScrollBox->GetFrame(false);
+ if (!frame) {
+ return nullptr;
+ }
+
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(frame);
+ if (!scrollFrame) {
+ NS_WARNING("ScrollBoxObject attached to something that's not a scroll frame!");
+ return nullptr;
+ }
+
+ nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
+ if (!scrolledFrame)
+ return nullptr;
+ return nsBox::GetChildXULBox(scrolledFrame);
+}
+
+void ScrollBoxObject::ScrollByIndex(int32_t dindexes, ErrorResult& aRv)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIFrame* scrolledBox = GetScrolledBox(this);
+ if (!scrolledBox) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsRect rect;
+
+ // now get the scrolled boxes first child.
+ nsIFrame* child = nsBox::GetChildXULBox(scrolledBox);
+
+ bool horiz = scrolledBox->IsXULHorizontal();
+ nsPoint cp = sf->GetScrollPosition();
+ nscoord diff = 0;
+ int32_t curIndex = 0;
+ bool isLTR = scrolledBox->IsXULNormalDirection();
+
+ int32_t frameWidth = 0;
+ if (!isLTR && horiz) {
+ GetWidth(&frameWidth);
+ nsCOMPtr<nsIPresShell> shell = GetPresShell(false);
+ if (!shell) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ frameWidth = nsPresContext::CSSPixelsToAppUnits(frameWidth);
+ }
+
+ // first find out what index we are currently at
+ while(child) {
+ rect = child->GetRect();
+ if (horiz) {
+ // In the left-to-right case we break from the loop when the center of
+ // the current child rect is greater than the scrolled position of
+ // the left edge of the scrollbox
+ // In the right-to-left case we break when the center of the current
+ // child rect is less than the scrolled position of the right edge of
+ // the scrollbox.
+ diff = rect.x + rect.width/2; // use the center, to avoid rounding errors
+ if ((isLTR && diff > cp.x) ||
+ (!isLTR && diff < cp.x + frameWidth)) {
+ break;
+ }
+ } else {
+ diff = rect.y + rect.height/2;// use the center, to avoid rounding errors
+ if (diff > cp.y) {
+ break;
+ }
+ }
+ child = nsBox::GetNextXULBox(child);
+ curIndex++;
+ }
+
+ int32_t count = 0;
+
+ if (dindexes == 0)
+ return;
+
+ if (dindexes > 0) {
+ while(child) {
+ child = nsBox::GetNextXULBox(child);
+ if (child) {
+ rect = child->GetRect();
+ }
+ count++;
+ if (count >= dindexes) {
+ break;
+ }
+ }
+
+ } else if (dindexes < 0) {
+ child = nsBox::GetChildXULBox(scrolledBox);
+ while(child) {
+ rect = child->GetRect();
+ if (count >= curIndex + dindexes) {
+ break;
+ }
+
+ count++;
+ child = nsBox::GetNextXULBox(child);
+
+ }
+ }
+
+ nscoord csspixel = nsPresContext::CSSPixelsToAppUnits(1);
+ if (horiz) {
+ // In the left-to-right case we scroll so that the left edge of the
+ // selected child is scrolled to the left edge of the scrollbox.
+ // In the right-to-left case we scroll so that the right edge of the
+ // selected child is scrolled to the right edge of the scrollbox.
+
+ nsPoint pt(isLTR ? rect.x : rect.x + rect.width - frameWidth,
+ cp.y);
+
+ // Use a destination range that ensures the left edge (or right edge,
+ // for RTL) will indeed be visible. Also ensure that the top edge
+ // is visible.
+ nsRect range(pt.x, pt.y, csspixel, 0);
+ if (isLTR) {
+ range.x -= csspixel;
+ }
+ sf->ScrollTo(pt, nsIScrollableFrame::INSTANT, &range);
+ } else {
+ // Use a destination range that ensures the top edge will be visible.
+ nsRect range(cp.x, rect.y - csspixel, 0, csspixel);
+ sf->ScrollTo(nsPoint(cp.x, rect.y), nsIScrollableFrame::INSTANT, &range);
+ }
+}
+
+void ScrollBoxObject::ScrollToLine(int32_t line, ErrorResult& aRv)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nscoord y = sf->GetLineScrollAmount().height * line;
+ nsRect range(0, y - nsPresContext::CSSPixelsToAppUnits(1),
+ 0, nsPresContext::CSSPixelsToAppUnits(1));
+ sf->ScrollTo(nsPoint(0, y), nsIScrollableFrame::INSTANT, &range);
+}
+
+void ScrollBoxObject::ScrollToElement(Element& child, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIPresShell> shell = GetPresShell(false);
+ if (!shell) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ shell->ScrollContentIntoView(&child,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_TOP,
+ nsIPresShell::SCROLL_ALWAYS),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_LEFT,
+ nsIPresShell::SCROLL_ALWAYS),
+ nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY |
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+void ScrollBoxObject::ScrollToIndex(int32_t index, ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+int32_t ScrollBoxObject::GetPositionX(ErrorResult& aRv)
+{
+ CSSIntPoint pt;
+ GetPosition(pt, aRv);
+ return pt.x;
+}
+
+int32_t ScrollBoxObject::GetPositionY(ErrorResult& aRv)
+{
+ CSSIntPoint pt;
+ GetPosition(pt, aRv);
+ return pt.y;
+}
+
+int32_t ScrollBoxObject::GetScrolledWidth(ErrorResult& aRv)
+{
+ nsRect scrollRect;
+ GetScrolledSize(scrollRect, aRv);
+ return scrollRect.width;
+}
+
+int32_t ScrollBoxObject::GetScrolledHeight(ErrorResult& aRv)
+{
+ nsRect scrollRect;
+ GetScrolledSize(scrollRect, aRv);
+ return scrollRect.height;
+}
+
+/* private helper */
+void ScrollBoxObject::GetPosition(CSSIntPoint& aPos, ErrorResult& aRv)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aPos = sf->GetScrollPositionCSSPixels();
+}
+
+/* private helper */
+void ScrollBoxObject::GetScrolledSize(nsRect& aRect, ErrorResult& aRv)
+{
+ nsIFrame* scrolledBox = GetScrolledBox(this);
+ if (!scrolledBox) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRect = scrolledBox->GetRect();
+ aRect.width = nsPresContext::AppUnitsToIntCSSPixels(aRect.width);
+ aRect.height = nsPresContext::AppUnitsToIntCSSPixels(aRect.height);
+}
+
+void ScrollBoxObject::GetPosition(JSContext* cx,
+ JS::Handle<JSObject*> x,
+ JS::Handle<JSObject*> y,
+ ErrorResult& aRv)
+{
+ CSSIntPoint pt;
+ GetPosition(pt, aRv);
+ JS::Rooted<JS::Value> v(cx);
+ if (!ToJSValue(cx, pt.x, &v) ||
+ !JS_SetProperty(cx, x, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!ToJSValue(cx, pt.y, &v) ||
+ !JS_SetProperty(cx, y, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+void ScrollBoxObject::GetScrolledSize(JSContext* cx,
+ JS::Handle<JSObject*> width,
+ JS::Handle<JSObject*> height,
+ ErrorResult& aRv)
+{
+ nsRect rect;
+ GetScrolledSize(rect, aRv);
+ JS::Rooted<JS::Value> v(cx);
+ if (!ToJSValue(cx, rect.width, &v) ||
+ !JS_SetProperty(cx, width, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!ToJSValue(cx, rect.height, &v) ||
+ !JS_SetProperty(cx, height, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+void ScrollBoxObject::EnsureElementIsVisible(Element& child, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIPresShell> shell = GetPresShell(false);
+ if (!shell) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ shell->ScrollContentIntoView(&child,
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY |
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+void ScrollBoxObject::EnsureIndexIsVisible(int32_t index, ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void ScrollBoxObject::EnsureLineIsVisible(int32_t line, ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewScrollBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new ScrollBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/ScrollBoxObject.h b/layout/xul/ScrollBoxObject.h
new file mode 100644
index 000000000..3344bf3f0
--- /dev/null
+++ b/layout/xul/ScrollBoxObject.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScrollBoxObject_h
+#define mozilla_dom_ScrollBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+#include "Units.h"
+
+class nsIScrollableFrame;
+struct nsRect;
+
+namespace mozilla {
+namespace dom {
+
+class ScrollBoxObject final : public BoxObject
+{
+public:
+ ScrollBoxObject();
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual nsIScrollableFrame* GetScrollFrame();
+
+ void ScrollTo(int32_t x, int32_t y, ErrorResult& aRv);
+ void ScrollBy(int32_t dx, int32_t dy, ErrorResult& aRv);
+ void ScrollByLine(int32_t dlines, ErrorResult& aRv);
+ void ScrollByIndex(int32_t dindexes, ErrorResult& aRv);
+ void ScrollToLine(int32_t line, ErrorResult& aRv);
+ void ScrollToElement(Element& child, ErrorResult& aRv);
+ void ScrollToIndex(int32_t index, ErrorResult& aRv);
+ int32_t GetPositionX(ErrorResult& aRv);
+ int32_t GetPositionY(ErrorResult& aRv);
+ int32_t GetScrolledWidth(ErrorResult& aRv);
+ int32_t GetScrolledHeight(ErrorResult& aRv);
+ void EnsureElementIsVisible(Element& child, ErrorResult& aRv);
+ void EnsureIndexIsVisible(int32_t index, ErrorResult& aRv);
+ void EnsureLineIsVisible(int32_t line, ErrorResult& aRv);
+
+ // Deprecated APIs from old IDL
+ void GetPosition(JSContext* cx,
+ JS::Handle<JSObject*> x,
+ JS::Handle<JSObject*> y,
+ ErrorResult& aRv);
+
+ void GetScrolledSize(JSContext* cx,
+ JS::Handle<JSObject*> width,
+ JS::Handle<JSObject*> height,
+ ErrorResult& aRv);
+
+protected:
+ void GetScrolledSize(nsRect& aRect, ErrorResult& aRv);
+ void GetPosition(CSSIntPoint& aPos, ErrorResult& aRv);
+
+private:
+ ~ScrollBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScrollBoxObject_h
diff --git a/layout/xul/crashtests/131008-1.xul b/layout/xul/crashtests/131008-1.xul
new file mode 100644
index 000000000..d505f8696
--- /dev/null
+++ b/layout/xul/crashtests/131008-1.xul
@@ -0,0 +1,11 @@
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="MainWindow"
+ title="IWindow Test">
+<div style="position:absolute">abc</div>
+
+
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/137216-1.xul b/layout/xul/crashtests/137216-1.xul
new file mode 100644
index 000000000..a3fc043c8
--- /dev/null
+++ b/layout/xul/crashtests/137216-1.xul
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <iframe style="position:absolute;"/>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/140218-1.xml b/layout/xul/crashtests/140218-1.xml
new file mode 100644
index 000000000..311afc218
--- /dev/null
+++ b/layout/xul/crashtests/140218-1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <treechildren style = " display: block; " />
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/151826-1.xul b/layout/xul/crashtests/151826-1.xul
new file mode 100644
index 000000000..1115dae72
--- /dev/null
+++ b/layout/xul/crashtests/151826-1.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window
+ title = "Arrowscrollbox->Splitter Crash Testcase"
+ xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ width = "300"
+ height = "200"
+ orient = "vertical"
+>
+<vbox flex="1">
+
+<scrollbox flex="1">
+<vbox flex="1">
+<vbox id="box_1">
+<hbox><label value="Test"/></hbox>
+</vbox>
+<splitter collapse="none"/>
+<vbox id="box_2">
+<hbox><label value="Test"/></hbox>
+</vbox>
+</vbox>
+</scrollbox>
+
+</vbox>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/168724-1.xul b/layout/xul/crashtests/168724-1.xul
new file mode 100644
index 000000000..8456406c2
--- /dev/null
+++ b/layout/xul/crashtests/168724-1.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
+
+<window
+ id="nodeCreator" title="Node Creator"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+<description context="context">Right-click here, and expect a crash.</description>
+
+<popupset id="context-set">
+<popup id="context">
+<deck selectedItem="0">
+<menuitem label="You should never see this" />
+</deck>
+</popup>
+</popupset>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/189814-1.xul b/layout/xul/crashtests/189814-1.xul
new file mode 100644
index 000000000..79462348c
--- /dev/null
+++ b/layout/xul/crashtests/189814-1.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<window
+ id="sliderprint" title="Print These Sliders"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="background-color: white">
+
+ <label>
+ With the Classic theme, printing causes the browser to crash. adding style="-moz-appearance: none" to the
+ thumb prevents the crash. The crash doesn't happen at all with Modern.
+ </label>
+ <spacer height="10"/>
+ <hbox>
+
+ <slider style="height: 174px; width: 24px" orient="vertical">
+ <thumb/>
+ </slider>
+
+ </hbox>
+
+</window>
diff --git a/layout/xul/crashtests/237787-1.xul b/layout/xul/crashtests/237787-1.xul
new file mode 100644
index 000000000..96cebca9f
--- /dev/null
+++ b/layout/xul/crashtests/237787-1.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <toolbar>
+ <arrowscrollbox>
+ <menulist editable="true">
+ </menulist>
+ </arrowscrollbox>
+ </toolbar>
+</window>
diff --git a/layout/xul/crashtests/265161-1.xul b/layout/xul/crashtests/265161-1.xul
new file mode 100644
index 000000000..75a995790
--- /dev/null
+++ b/layout/xul/crashtests/265161-1.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:div>
+ <listitem>
+ </listitem>
+ </html:div>
+</window>
diff --git a/layout/xul/crashtests/289410-1.xul b/layout/xul/crashtests/289410-1.xul
new file mode 100644
index 000000000..0de792f2c
--- /dev/null
+++ b/layout/xul/crashtests/289410-1.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window id="crash-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <scrollbox>
+ <tree id="crash-tree">
+ <treecols/>
+ <treechildren/>
+ </tree>
+ </scrollbox>
+
+</window>
diff --git a/layout/xul/crashtests/290743.html b/layout/xul/crashtests/290743.html
new file mode 100644
index 000000000..fd273d8bf
--- /dev/null
+++ b/layout/xul/crashtests/290743.html
@@ -0,0 +1,6 @@
+<html>
+<head><title>Testcase bug 290743 - This display:-moz-grid testcase freezes Mozilla</title></head>
+<body style="display:-moz-grid;">
+<input type="radio"><input type="radio">
+</body>
+</html> \ No newline at end of file
diff --git a/layout/xul/crashtests/291702-1.xul b/layout/xul/crashtests/291702-1.xul
new file mode 100644
index 000000000..4c052630d
--- /dev/null
+++ b/layout/xul/crashtests/291702-1.xul
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window title="Negative flex bug #2"
+ orient="horizontal"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <button label="Button" flex="2"/>
+ <label value="This is a label" flex="1"/>
+ <label value="This is the second label" flex="-2"/>
+ <label value="This is another label" flex="-1"/>
+</window>
diff --git a/layout/xul/crashtests/291702-2.xul b/layout/xul/crashtests/291702-2.xul
new file mode 100644
index 000000000..53d8a51f7
--- /dev/null
+++ b/layout/xul/crashtests/291702-2.xul
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window title="Negative flex bug #2"
+ orient="horizontal"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <button label="Button" flex="1073741824"/>
+ <label value="This is a label" flex="1073741824"/>
+ <label value="This is the second label" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+</window>
diff --git a/layout/xul/crashtests/291702-3.xul b/layout/xul/crashtests/291702-3.xul
new file mode 100644
index 000000000..b34404f36
--- /dev/null
+++ b/layout/xul/crashtests/291702-3.xul
@@ -0,0 +1,137 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window title="Negative flex bug #2"
+ orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ </hbox>
+
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ </hbox>
+
+
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="1"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="1"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="1"/>
+ <label value="This is another label" style="-moz-box-flex: 1;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741823"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741823;"/>
+ <button label="Button" flex="2"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741824"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741824;"/>
+ <button label="Button" flex="2"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+ <hbox>
+ <button label="Button" flex="1073741825"/>
+ <label value="This is another label" style="-moz-box-flex: 1073741825;"/>
+ <button label="Button" flex="2"/>
+ <label value="This is another label" style="-moz-box-flex: 2;"/>
+ </hbox>
+</window>
diff --git a/layout/xul/crashtests/294371-1.xul b/layout/xul/crashtests/294371-1.xul
new file mode 100644
index 000000000..ca5b54914
--- /dev/null
+++ b/layout/xul/crashtests/294371-1.xul
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window
+ id = "overflow crash"
+ title = "scrollbox crasher"
+ xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ persist="sizemode width height screenX screenY"
+ width="320"
+ height="240">
+
+ <scrollbox flex="1">
+ <grid style="overflow: auto">
+ <columns>
+ <column flex="0"/>
+ </columns>
+ <rows>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ <row><label value="Date"/></row>
+ </rows>
+ </grid>
+ </scrollbox>
+
+</window>
diff --git a/layout/xul/crashtests/311457-1.html b/layout/xul/crashtests/311457-1.html
new file mode 100644
index 000000000..e5b6ecdd6
--- /dev/null
+++ b/layout/xul/crashtests/311457-1.html
@@ -0,0 +1,12 @@
+<html><head>
+
+</head>
+
+<body>
+
+<div style="display: -moz-deck"><div style="display: -moz-popup"></div></div>
+
+<div style="position: relative">Y</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/xul/crashtests/321056-1.xhtml b/layout/xul/crashtests/321056-1.xhtml
new file mode 100644
index 000000000..a7ba11793
--- /dev/null
+++ b/layout/xul/crashtests/321056-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<xul:titlebar id="a"/>
+
+<script>
+var html = document.firstChild;
+var a = document.getElementById('a')
+document.removeChild(html)
+document.appendChild(a)
+</script>
+</html>
diff --git a/layout/xul/crashtests/322786-1.xul b/layout/xul/crashtests/322786-1.xul
new file mode 100644
index 000000000..79bb092c4
--- /dev/null
+++ b/layout/xul/crashtests/322786-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <foo style="display: inline;">
+ <scrollbox/>
+ </foo>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/325377.xul b/layout/xul/crashtests/325377.xul
new file mode 100644
index 000000000..8ea30473d
--- /dev/null
+++ b/layout/xul/crashtests/325377.xul
@@ -0,0 +1,16 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Testcase bug 325377 - Crash on reload with evil xul textcase, using menulist and nested tooltips">
+<menulist style="display: table-cell;">
+<tooltip style="display: none;">
+ <tooltip/>
+</tooltip>
+</menulist>
+
+<html:script>
+function removestyles(){
+document.getElementsByTagName('tooltip')[0].removeAttribute('style');
+}
+try { document.getElementsByTagName('tooltip')[0].offsetHeight; } catch(e) {}
+setTimeout(removestyles,0);
+</html:script>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/326834-1-inner.xul b/layout/xul/crashtests/326834-1-inner.xul
new file mode 100644
index 000000000..0fbdca7ab
--- /dev/null
+++ b/layout/xul/crashtests/326834-1-inner.xul
@@ -0,0 +1,17 @@
+<window title="Testcase bug 326834 - Crash with evil xul testcase, using listbox/listitem and display: table-cell"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<listbox>
+ <listitem label="This page should not crash Mozilla"/>
+</listbox>
+<html:script>
+function doe() {
+var el=document.getElementsByTagName('*');
+document.getElementsByTagName('listbox')[0].style.display = 'table-cell';
+document.getElementsByTagName('listitem')[0].style.display = 'table-cell';
+window.getComputedStyle(document.getElementsByTagName('listitem')[0], '').getPropertyValue("height");
+document.getElementsByTagName('listitem')[0].style.display = '';
+}
+setTimeout(doe,500);
+</html:script>
+</window>
diff --git a/layout/xul/crashtests/326834-1.html b/layout/xul/crashtests/326834-1.html
new file mode 100644
index 000000000..ca531caa6
--- /dev/null
+++ b/layout/xul/crashtests/326834-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="326834-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/layout/xul/crashtests/326879-1.xul b/layout/xul/crashtests/326879-1.xul
new file mode 100644
index 000000000..84d74c30c
--- /dev/null
+++ b/layout/xul/crashtests/326879-1.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+
+
+<script>
+
+
+function init() {
+
+ var menupopup = document.getElementsByTagName("menupopup")[0];
+ menupopup.ordinal = null;
+};
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+<menulist>
+ <menupopup>
+ <menuitem label="Foo"/>
+ </menupopup>
+</menulist>
+
+
+
+</window>
diff --git a/layout/xul/crashtests/327776-1.xul b/layout/xul/crashtests/327776-1.xul
new file mode 100644
index 000000000..af889493c
--- /dev/null
+++ b/layout/xul/crashtests/327776-1.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script>
+<![CDATA[
+function init()
+{
+ var span = document.getElementsByTagName("span")[0];
+ var boxobj = document.getBoxObjectFor(span);
+ try {
+ boxobj.setPropertyAsSupports(undefined, undefined);
+ } catch(e) {
+ }
+}
+window.addEventListener("load", init, false);
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<span></span>
+
+</body>
+</window>
diff --git a/layout/xul/crashtests/328135-1.xul b/layout/xul/crashtests/328135-1.xul
new file mode 100644
index 000000000..77a467909
--- /dev/null
+++ b/layout/xul/crashtests/328135-1.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+
+
+<script>
+
+function init() {
+ var pop = document.getElementsByTagName("popup")[0];
+ SpecialPowers.wrap(document).getAnonymousNodes(pop)[0];
+ eval.eee = document.documentElement;
+};
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<popup/>
+
+
+<tabbox/>
+
+
+</window>
diff --git a/layout/xul/crashtests/329327-1.xul b/layout/xul/crashtests/329327-1.xul
new file mode 100644
index 000000000..fcfed07c4
--- /dev/null
+++ b/layout/xul/crashtests/329327-1.xul
@@ -0,0 +1,2 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><menulist equalsize="always"><y/> <z width="-444981589286"/> </menulist></window>
diff --git a/layout/xul/crashtests/329407-1.xml b/layout/xul/crashtests/329407-1.xml
new file mode 100644
index 000000000..0d41c0185
--- /dev/null
+++ b/layout/xul/crashtests/329407-1.xml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+
+<body>
+
+ <xul:hbox>
+ <select/>
+ <select/>
+ </xul:hbox>
+
+</body>
+
+</html>
diff --git a/layout/xul/crashtests/329477-1.xhtml b/layout/xul/crashtests/329477-1.xhtml
new file mode 100644
index 000000000..fcbd3da87
--- /dev/null
+++ b/layout/xul/crashtests/329477-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+<![CDATA[
+
+
+function init()
+{
+ var textbox = document.getElementsByTagName("textbox")[0];
+ var hbox = SpecialPowers.wrap(document).getAnonymousNodes(textbox)[0];
+ var menupopup = SpecialPowers.wrap(document).getAnonymousNodes(hbox)[1];
+
+ menupopup.click();
+}
+
+window.addEventListener("load", init, false);
+
+]]>
+</script>
+
+</head>
+
+<body>
+
+
+ <textbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/336962-1.xul b/layout/xul/crashtests/336962-1.xul
new file mode 100644
index 000000000..5ad4ad22b
--- /dev/null
+++ b/layout/xul/crashtests/336962-1.xul
@@ -0,0 +1,17 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function init() {
+ document.getElementById("foopy").style.position = "absolute";
+}
+
+window.addEventListener("load", init, 0);
+
+</script>
+
+
+<box id="foopy" />
+
+
+</window>
diff --git a/layout/xul/crashtests/344228-1.xul b/layout/xul/crashtests/344228-1.xul
new file mode 100644
index 000000000..d6015707b
--- /dev/null
+++ b/layout/xul/crashtests/344228-1.xul
@@ -0,0 +1,27 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+
+function remove(q1) { q1.parentNode.removeChild(q1); }
+
+function boom()
+{
+ var x = document.getElementById("x");
+ var y = document.getElementById("y");
+ remove(x);
+ remove(y);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<tree>
+ <treechildren id="y"/>
+ <richlistbox>
+ <hbox id="x"/>
+ <menulist/>
+ </richlistbox>
+</tree>
+
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/346083-1.xul b/layout/xul/crashtests/346083-1.xul
new file mode 100644
index 000000000..e04d610a4
--- /dev/null
+++ b/layout/xul/crashtests/346083-1.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<script>
+<![CDATA[
+document.getBoxObjectFor(document.getElementsByTagName("body")[0]).setProperty("foo", undefined);
+]]>
+</script>
+
+</body>
+</window>
diff --git a/layout/xul/crashtests/346281-1.xul b/layout/xul/crashtests/346281-1.xul
new file mode 100644
index 000000000..4ef670155
--- /dev/null
+++ b/layout/xul/crashtests/346281-1.xul
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<script>
+<![CDATA[
+var boxy = document.getBoxObjectFor(document.getElementsByTagName("body")[0]);
+boxy.setPropertyAsSupports("zoink", undefined);
+try {
+ boxy.removeProperty(undefined);
+} catch(e) { }
+]]>
+</script>
+
+</body>
+</window>
diff --git a/layout/xul/crashtests/350460.xul b/layout/xul/crashtests/350460.xul
new file mode 100644
index 000000000..b13de6c97
--- /dev/null
+++ b/layout/xul/crashtests/350460.xul
@@ -0,0 +1,8 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Crash [@ DoDeletingFrameSubtree] after reloading a xul page a few times with display: -moz-popup and menuitem">
+ <menuitem style="display: -moz-popup;">
+ <box style="display: -moz-popup;">
+ <box style="display: -moz-popup;"/>
+ </box>
+ </menuitem>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/360642-1.xul b/layout/xul/crashtests/360642-1.xul
new file mode 100644
index 000000000..5e37020a5
--- /dev/null
+++ b/layout/xul/crashtests/360642-1.xul
@@ -0,0 +1,9 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait"
+ onload="setTimeout(function() { var foo = document.getElementById('foo'); foo.parentNode.removeChild(foo); document.documentElement.removeAttribute('class'); }, 30);">
+
+ <listboxbody>
+ <hbox id="foo"/>
+ </listboxbody>
+
+</window>
diff --git a/layout/xul/crashtests/365151.xul b/layout/xul/crashtests/365151.xul
new file mode 100644
index 000000000..074c8d398
--- /dev/null
+++ b/layout/xul/crashtests/365151.xul
@@ -0,0 +1,39 @@
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom()" class="reftest-wait">
+
+
+<script>
+function boom()
+{
+ try {
+ var tree = document.getElementById("tree");
+ var col = tree.treeBoxObject.columns.getFirstColumn();
+ var treecols = document.getElementById("treecols");
+ treecols.parentNode.removeChild(treecols);
+ var x = col.x;
+ } finally {
+ document.documentElement.removeAttribute("class");
+ }
+}
+</script>
+
+
+<tree rows="6" id="tree">
+
+ <treecols id="treecols">
+ <treecol id="firstname" label="First Name"/>
+ </treecols>
+
+ <treechildren id="treechildren">
+ <treeitem>
+ <treerow>
+ <treecell label="Bob"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+
+</tree>
+
+</window>
diff --git a/layout/xul/crashtests/366112-1.xul b/layout/xul/crashtests/366112-1.xul
new file mode 100644
index 000000000..4a03ea2cf
--- /dev/null
+++ b/layout/xul/crashtests/366112-1.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <nativescrollbar />
+
+</window>
diff --git a/layout/xul/crashtests/366203-1.xul b/layout/xul/crashtests/366203-1.xul
new file mode 100644
index 000000000..3e2b96d30
--- /dev/null
+++ b/layout/xul/crashtests/366203-1.xul
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 500);">
+
+<script>
+function boom()
+{
+ tc1 = document.getElementById("tc1");
+ tc1.parentNode.removeChild(tc1);
+}
+</script>
+
+<tree rows="6">
+ <treecols>
+ <treecol id="firstname" label="First Name" primary="true" flex="3"/>
+ <treecol id="lastname" label="Last Name" flex="7"/>
+ </treecols>
+
+ <treechildren id="tc1">
+ <treeitem container="true" open="true">
+ <treerow>
+ <treecell label="Foo"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+
+ <treechildren>
+ <treeitem container="true" open="true">
+ <treerow>
+ <treecell label="Bar"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+
+</window>
+
diff --git a/layout/xul/crashtests/367185-1.xhtml b/layout/xul/crashtests/367185-1.xhtml
new file mode 100644
index 000000000..08fd39fa1
--- /dev/null
+++ b/layout/xul/crashtests/367185-1.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<title>Testcase bug - ASSERTION: shouldn't use unconstrained widths anymore with nested marquees</title>
+</head>
+<body>
+<xul:hbox style="margin: 0 100%;"><span><xul:hbox style="margin: 0 100%;"></xul:hbox></span></xul:hbox>
+</body>
+</html>
diff --git a/layout/xul/crashtests/369942-1.xhtml b/layout/xul/crashtests/369942-1.xhtml
new file mode 100644
index 000000000..a05705843
--- /dev/null
+++ b/layout/xul/crashtests/369942-1.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+<head>
+
+<script>
+function boom()
+{
+ var span = document.getElementById("span");
+ var radio = document.getElementById("radio");
+
+ radio.appendChild(span);
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+
+<style>
+body {
+ text-align: center;
+ font-size: 9px;
+}
+</style>
+
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+<span id="span"><xul:wizard/><div>Industries</div></span>
+
+<xul:radio id="radio"/>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/374102-1.xul b/layout/xul/crashtests/374102-1.xul
new file mode 100644
index 000000000..7e85f0d21
--- /dev/null
+++ b/layout/xul/crashtests/374102-1.xul
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<tabpanels>
+<treechildren style="display: -moz-deck;"/>
+</tabpanels>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/376137-1.html b/layout/xul/crashtests/376137-1.html
new file mode 100644
index 000000000..23b39d900
--- /dev/null
+++ b/layout/xul/crashtests/376137-1.html
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+span { display:block; outline: 10px solid yellow; }
+</style>
+</head>
+
+<body>
+
+<div>
+ <div style="display: -moz-inline-grid">
+ <span>M</span>
+ <span>N</span>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/376137-2.html b/layout/xul/crashtests/376137-2.html
new file mode 100644
index 000000000..160c61ed3
--- /dev/null
+++ b/layout/xul/crashtests/376137-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Bug 376137</title>
+<style>
+p { width: 100%; border: solid 1px;}
+</style>
+
+<div style="display: -moz-inline-grid">
+ <div><p>M</p></div>
+ <div><p>N</p></div>
+</div>
+
diff --git a/layout/xul/crashtests/377592-1.svg b/layout/xul/crashtests/377592-1.svg
new file mode 100644
index 000000000..7371708f2
--- /dev/null
+++ b/layout/xul/crashtests/377592-1.svg
@@ -0,0 +1,27 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+
+<script>
+
+var emptyBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%0A%20%20%20%20%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')";
+
+function boom()
+{
+ var foreignObject = document.getElementById("foreignObject")
+ foreignObject.style.MozBinding = emptyBinding;
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+<foreignObject width="500" height="500" transform="scale(.7,.7)" id="foreignObject" y="300">
+ <xul:menuitem />
+</foreignObject>
+
+
+</svg>
diff --git a/layout/xul/crashtests/378961.html b/layout/xul/crashtests/378961.html
new file mode 100644
index 000000000..b4da857ff
--- /dev/null
+++ b/layout/xul/crashtests/378961.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Testcase bug 378961 - Crash [@ nsSplitterFrameInner::RemoveListener] when dragging splitter and DOMAttrModified event removing window</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C%3Fxml-stylesheet%20href%3D%22chrome%3A//global/skin%22%20type%3D%22text/css%22%3F%3E%0A%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%20orient%3D%22horizontal%22%3E%0A%3Ctextbox/%3E%3Csplitter/%3E%3Cbox/%3E%0A%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Afunction%20doe%28%29%20%7B%0Awindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%0A%7D%0Adocument.addEventListener%28%27DOMAttrModified%27%2C%20doe%2C%20true%29%3B%0A%3C/script%3E%0A%3C/window%3E" style="width: 500px;height:200px;"></iframe>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/381862.html b/layout/xul/crashtests/381862.html
new file mode 100644
index 000000000..e26fa357e
--- /dev/null
+++ b/layout/xul/crashtests/381862.html
@@ -0,0 +1,23 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsBoxFrame::BuildDisplayListForChildren] with tree stuff in iframe toggling display</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%20%20%3Ctree%20style%3D%22display%3A%20block%3B%20position%3A%20absolute%3B%22%3E%0A%20%20%20%20%3Ctree%20style%3D%22display%3A%20table%3B%22%3E%0A%20%20%20%20%20%20%3Ctreeseparator%20style%3D%22display%3A%20block%3B%20position%3A%20absolute%3B%22%3E%0A%20%20%20%20%20%20%20%20%3Ctreechildren%20style%3D%22display%3A%20block%3B%22/%3E%0A%20%20%20%20%20%20%3C/treeseparator%3E%0A%20%20%20%20%20%20%3Ctreechildren%20style%3D%22display%3A%20none%3B%22/%3E%0A%20%20%20%20%3C/tree%3E%0A%20%20%3C/tree%3E%0A%3C/window%3E" id="content"></iframe>
+
+<script>
+function toggleIframe(){
+var x=document.getElementById('content');
+x.style.display = x.style.display == 'none' ? x.style.display = 'block' : x.style.display = 'none';
+setTimeout(toggleIframe,200);
+}
+setTimeout(toggleIframe,500);
+
+function removestyles(i){
+window.frames[0].document.getElementsByTagName('*')[1].removeAttribute('style');
+}
+
+setTimeout(removestyles,500,1);
+/*template*/
+</script>
+</body>
+</html>
diff --git a/layout/xul/crashtests/382746-1.xul b/layout/xul/crashtests/382746-1.xul
new file mode 100644
index 000000000..9bb14f24f
--- /dev/null
+++ b/layout/xul/crashtests/382746-1.xul
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<grid>
+ <rows>
+ <column>
+ <hbox/>
+ <hbox/>
+ </column>
+ <hbox/>
+ </rows>
+</grid>
+
+</window>
diff --git a/layout/xul/crashtests/382899-1.xul b/layout/xul/crashtests/382899-1.xul
new file mode 100644
index 000000000..7dab931f7
--- /dev/null
+++ b/layout/xul/crashtests/382899-1.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<hbox equalsize="always"><grid/>x</hbox>
+
+</window>
diff --git a/layout/xul/crashtests/383236-1.xul b/layout/xul/crashtests/383236-1.xul
new file mode 100644
index 000000000..244df65f1
--- /dev/null
+++ b/layout/xul/crashtests/383236-1.xul
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<listbox><listhead>x</listhead></listbox>
+
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/384037-1.xhtml b/layout/xul/crashtests/384037-1.xhtml
new file mode 100644
index 000000000..04bac671c
--- /dev/null
+++ b/layout/xul/crashtests/384037-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+
+<xul:splitter id="s" collapse="both" state="collapsed" />
+
+</body>
+</html>
+
diff --git a/layout/xul/crashtests/384105-1-inner.xul b/layout/xul/crashtests/384105-1-inner.xul
new file mode 100644
index 000000000..4ea6e0391
--- /dev/null
+++ b/layout/xul/crashtests/384105-1-inner.xul
@@ -0,0 +1,21 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">
+function doe(){
+document.getElementById('a').removeAttribute('style');
+}
+setTimeout(doe,100);
+</script>
+<box id="a" style="position: absolute;">
+ <menuitem sizetopopup="always">
+ <menupopup style="position: absolute;"/>
+ </menuitem>
+
+ <box style="position: fixed;">
+ <tree>
+ <treecol>
+ <treecol/>
+ </treecol>
+ </tree>
+ </box>
+</box>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/384105-1.html b/layout/xul/crashtests/384105-1.html
new file mode 100644
index 000000000..fe468a906
--- /dev/null
+++ b/layout/xul/crashtests/384105-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="384105-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/layout/xul/crashtests/384373-1.xul b/layout/xul/crashtests/384373-1.xul
new file mode 100644
index 000000000..603b53cde
--- /dev/null
+++ b/layout/xul/crashtests/384373-1.xul
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+onerror="var x=document.getElementsByTagName('*');x[Math.floor(Math.random()*x.length)].focus()"
+onblur="event.originalTarget.parentNode.parentNode.removeChild(event.originalTarget.parentNode)">
+<script xmlns="http://www.w3.org/1999/xhtml">setTimeout(function() {window.location.reload()}, 200);</script>
+
+<broadcasterset style="display: block;">
+ <broadcaster style="display: block;"></broadcaster>
+</broadcasterset>
+<preferences></preferences>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/384373-2.xul b/layout/xul/crashtests/384373-2.xul
new file mode 100644
index 000000000..1d56394e3
--- /dev/null
+++ b/layout/xul/crashtests/384373-2.xul
@@ -0,0 +1,4 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onerror="document.getElementsByTagName('*')[1].focus()" onfocus="event.target.parentNode.removeChild(event.target)">
+<broadcaster style="display: block;"/>
+<preferences/>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/384373.html b/layout/xul/crashtests/384373.html
new file mode 100644
index 000000000..c3bc92f16
--- /dev/null
+++ b/layout/xul/crashtests/384373.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 384373</title>
+<script>
+function reload() {
+ this.location.reload();
+}
+// Run the test for 1 second
+setTimeout(function() {
+ document.body.getBoundingClientRect();
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+</script>
+</head>
+<body onload="document.body.getBoundingClientRect()">
+
+<iframe src="384373-1.xul"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,500)" src="384373-2.xul"></iframe>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/384491-1.xhtml b/layout/xul/crashtests/384491-1.xhtml
new file mode 100644
index 000000000..2eb065f8d
--- /dev/null
+++ b/layout/xul/crashtests/384491-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+
+<xul:listboxbody style="overflow: hidden" />
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/384871-1-inner.xul b/layout/xul/crashtests/384871-1-inner.xul
new file mode 100644
index 000000000..62efdb260
--- /dev/null
+++ b/layout/xul/crashtests/384871-1-inner.xul
@@ -0,0 +1,9 @@
+<popup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe(){
+document.documentElement.autoPosition = 'on';
+window.location.reload();
+}
+setTimeout(doe, 300);
+</script>
+</popup> \ No newline at end of file
diff --git a/layout/xul/crashtests/384871-1.html b/layout/xul/crashtests/384871-1.html
new file mode 100644
index 000000000..6bb2a9e07
--- /dev/null
+++ b/layout/xul/crashtests/384871-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="384871-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/layout/xul/crashtests/386642.xul b/layout/xul/crashtests/386642.xul
new file mode 100644
index 000000000..50db21a09
--- /dev/null
+++ b/layout/xul/crashtests/386642.xul
@@ -0,0 +1,31 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Bug 386642 Crash [@ IsCanvasFrame] while opening context menu or changing styles">
+<toolbarbutton type="menu" id="a">
+<menupopup id="b"/>
+</toolbarbutton>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+.one image {
+display: -moz-box;
+}
+image{
+display: none;
+}
+
+</style>
+<script><![CDATA[
+var gg=0;
+function doe() {
+ var a = document.getElementById('a');
+ if (!a.hasAttribute('class')) {
+ a.setAttribute('class', 'one');
+ } else {
+ a.removeAttribute('class');
+ }
+document.getElementById('b').hidePopup();
+}
+
+doe();
+setInterval(doe, 200);
+]]></script>
+</window>
diff --git a/layout/xul/crashtests/387033-1.xhtml b/layout/xul/crashtests/387033-1.xhtml
new file mode 100644
index 000000000..58325b1a7
--- /dev/null
+++ b/layout/xul/crashtests/387033-1.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <xbl:bindings>
+ <xbl:binding id="test" extends="chrome://global/content/bindings/text.xml#text-label">
+ <xbl:implementation>
+ <xbl:property name="accessKey">
+ <xbl:getter>
+ <![CDATA[
+ this.parentNode.parentNode.removeChild(this.parentNode);
+ return "";
+ ]]>
+ </xbl:getter>
+ <xbl:setter>
+ <![CDATA[
+ return val;
+ ]]>
+ </xbl:setter>
+ </xbl:property>
+ </xbl:implementation>
+ </xbl:binding>
+ </xbl:bindings>
+ </head>
+ <body>
+ <xul:hbox>
+ <xul:label value="foobar" style="-moz-binding: url(#test)"/>
+ </xul:hbox>
+ </body>
+</html>
diff --git a/layout/xul/crashtests/387080-1.xul b/layout/xul/crashtests/387080-1.xul
new file mode 100644
index 000000000..4eb9bd784
--- /dev/null
+++ b/layout/xul/crashtests/387080-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <description>
+ <foo height="1793689537164611773" width="20000238421986669650" />
+ </description>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/391974-1-inner.xul b/layout/xul/crashtests/391974-1-inner.xul
new file mode 100644
index 000000000..f13aa2110
--- /dev/null
+++ b/layout/xul/crashtests/391974-1-inner.xul
@@ -0,0 +1,19 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<menuitem>
+<tooltip/>
+<box/>
+</menuitem>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe2() {
+document.getElementsByTagName('menuitem')[0].setAttribute('description', 'tetx');
+}
+
+function doe3() {
+document.getElementsByTagName('menuitem')[0].removeAttribute('description');
+document.getElementsByTagName('tooltip')[0].setAttribute('ordinal', '0');
+}
+setTimeout(doe2,150);
+setTimeout(doe3,200);
+</script>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/391974-1.html b/layout/xul/crashtests/391974-1.html
new file mode 100644
index 000000000..c72a1a73c
--- /dev/null
+++ b/layout/xul/crashtests/391974-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="391974-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/layout/xul/crashtests/394120-1.xhtml b/layout/xul/crashtests/394120-1.xhtml
new file mode 100644
index 000000000..9df447862
--- /dev/null
+++ b/layout/xul/crashtests/394120-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var xultext = document.createElementNS(XUL_NS, "text");
+ var hbox = document.getElementById("hbox")
+ hbox.appendChild(xultext);
+}
+</script>
+</head>
+<body onload="boom();">
+
+<xul:listboxbody><xul:hbox id="hbox" /></xul:listboxbody>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/397293.xhtml b/layout/xul/crashtests/397293.xhtml
new file mode 100644
index 000000000..cfd181921
--- /dev/null
+++ b/layout/xul/crashtests/397293.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="opacity: 0.2;">
+<head>
+<script>
+
+function x()
+{
+ document.documentElement.style.counterReset = "chicken";
+
+ document.body.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(x, 0);">Foo</body>
+
+<xul:listbox/>
+
+</html>
diff --git a/layout/xul/crashtests/397304-1.html b/layout/xul/crashtests/397304-1.html
new file mode 100644
index 000000000..3501f0581
--- /dev/null
+++ b/layout/xul/crashtests/397304-1.html
@@ -0,0 +1 @@
+<html><body><listboxbody style="display: -moz-grid-group;"></listboxbody></body></html> \ No newline at end of file
diff --git a/layout/xul/crashtests/398326-1.xhtml b/layout/xul/crashtests/398326-1.xhtml
new file mode 100644
index 000000000..a265ae4e0
--- /dev/null
+++ b/layout/xul/crashtests/398326-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ var listbox = document.createElementNS(XUL_NS, 'listbox');
+ document.body.appendChild(listbox);
+ var listitem = document.createElementNS(XUL_NS, 'listitem');
+ listbox.appendChild(listitem);
+}
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/layout/xul/crashtests/399013.xul b/layout/xul/crashtests/399013.xul
new file mode 100644
index 000000000..a2349aff8
--- /dev/null
+++ b/layout/xul/crashtests/399013.xul
@@ -0,0 +1,31 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<menulist id="b" style="display: -moz-groupbox;">
+<panel id="c" style=" position: absolute;">
+<popup onunderflow="document.getElementById('c').removeAttribute('style')"/>
+</panel>
+<menupopup id="a" style="display: -moz-stack;">
+<menulist/>
+</menupopup>
+<panel style="display: -moz-deck;" onoverflow="document.getElementById('b').removeAttribute('style')">
+<popup style="display: -moz-deck;"/>
+</panel>
+</menulist>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function doe() {
+document.getElementById('c').removeAttribute('style');
+document.documentElement.boxObject.height;
+document.getElementById('b').removeAttribute('style');
+document.getElementById('a').setAttribute('selected', 'true');
+document.getElementById('a').setAttribute('style', 'position: fixed;');
+document.documentElement.boxObject.height;
+document.getElementById('a').removeAttribute('style');
+}
+
+function doe2() {
+window.location.reload();
+}
+setTimeout(doe2, 200);
+setTimeout(doe,100);
+]]></script>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/400779-1.xhtml b/layout/xul/crashtests/400779-1.xhtml
new file mode 100644
index 000000000..c0f5d493c
--- /dev/null
+++ b/layout/xul/crashtests/400779-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+
+function boom()
+{
+ var menulist = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menulist");
+ document.getElementById("h").appendChild(menulist);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<xul:listboxbody><xul:hbox id="h"/></xul:listboxbody>
+</body>
+</html>
diff --git a/layout/xul/crashtests/402912-1.xhtml b/layout/xul/crashtests/402912-1.xhtml
new file mode 100644
index 000000000..b2cb98dc5
--- /dev/null
+++ b/layout/xul/crashtests/402912-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+<xul:vbox equalsize="always"><xul:hbox flex="1"><span><xul:hbox width="10" height="10"/></span><xul:button /></xul:hbox><xul:hbox maxheight="0"/></xul:vbox>
+</body>
+</html>
diff --git a/layout/xul/crashtests/404192.xhtml b/layout/xul/crashtests/404192.xhtml
new file mode 100644
index 000000000..4ad5af348
--- /dev/null
+++ b/layout/xul/crashtests/404192.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+<xul:titlebar id="a" style="overflow: auto;"/>
+
+<script>
+function doe() {
+document.getElementsByTagName('*')[1].focus();
+document.getElementsByTagName('*')[0].focus();
+document.documentElement.removeAttribute("class");
+}
+setTimeout(doe, 200);
+</script>
+</html>
diff --git a/layout/xul/crashtests/407152.xul b/layout/xul/crashtests/407152.xul
new file mode 100644
index 000000000..0a5adf69e
--- /dev/null
+++ b/layout/xul/crashtests/407152.xul
@@ -0,0 +1,7 @@
+<triple xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: block;">
+<box style="display: block; position: relative; -moz-binding: url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Cimplementation%3E%0A%3Cfield%20name%3D%22_field%22%3Ethis.ownerDocument.getBoxObjectFor%28this.ownerDocument.documentElement%29%3B%3C/field%3E%0A%3C/implementation%3E%0A%3Ccontent%3E%0A%3C/binding%3E%0A%3C/bindings%3E);">
+<box style="position: fixed; -moz-binding: url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Ccontent%3E%0A%3Cchildren%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22/%3E%3C/content%3E%3C/binding%3E%3C/bindings%3E);"/>
+<iframe/>
+</box>
+<scrollbox onoverflow="document.documentElement.removeAttribute('style')"/>
+</triple> \ No newline at end of file
diff --git a/layout/xul/crashtests/408904-1.xul b/layout/xul/crashtests/408904-1.xul
new file mode 100644
index 000000000..59f215c73
--- /dev/null
+++ b/layout/xul/crashtests/408904-1.xul
@@ -0,0 +1 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><grid><rows><label/></rows><columns><column><label/></column></columns></grid></window>
diff --git a/layout/xul/crashtests/412479-1.xhtml b/layout/xul/crashtests/412479-1.xhtml
new file mode 100644
index 000000000..b1086a816
--- /dev/null
+++ b/layout/xul/crashtests/412479-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head></head>
+<body><xul:menubar style="display: table-column; padding: 10px 3000px;"/></body>
+</html>
diff --git a/layout/xul/crashtests/415394-1.xhtml b/layout/xul/crashtests/415394-1.xhtml
new file mode 100644
index 000000000..7dc24dc9f
--- /dev/null
+++ b/layout/xul/crashtests/415394-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.execCommand("justifycenter", false, null);
+
+ var listboxbody = document.getElementById("lbb");
+ listboxbody.height = 9;
+ setTimeout(boom2, 0);
+
+ function boom2()
+ {
+ var td = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ listboxbody.appendChild(td);
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);" contenteditable="true"><xul:listboxbody id="lbb"><xul:hbox/><span><col style="width: 100px;" /></span></xul:listboxbody></body>
+
+</html>
diff --git a/layout/xul/crashtests/417509.xul b/layout/xul/crashtests/417509.xul
new file mode 100644
index 000000000..81703ada3
--- /dev/null
+++ b/layout/xul/crashtests/417509.xul
@@ -0,0 +1,7 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<span id="a" datasources="" xmlns="http://www.w3.org/1999/xhtml"/>
+<script xmlns="http://www.w3.org/1999/xhtml">
+document.documentElement.appendChild(document.getElementById('a'));
+
+</script>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/420424-1.xul b/layout/xul/crashtests/420424-1.xul
new file mode 100644
index 000000000..e60841706
--- /dev/null
+++ b/layout/xul/crashtests/420424-1.xul
@@ -0,0 +1,6 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.getElementById('a').ensureElementIsVisible(null);">
+
+<listbox id="a"/>
+
+</window>
diff --git a/layout/xul/crashtests/430356-1.xhtml b/layout/xul/crashtests/430356-1.xhtml
new file mode 100644
index 000000000..6e5717ae9
--- /dev/null
+++ b/layout/xul/crashtests/430356-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="visibility: collapse;">
+<tabpanels xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="width: -moz-max-content;"></tabpanels>
+</body>
+</html>
diff --git a/layout/xul/crashtests/431738.xhtml b/layout/xul/crashtests/431738.xhtml
new file mode 100644
index 000000000..9ce917a3f
--- /dev/null
+++ b/layout/xul/crashtests/431738.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+<div>
+<span style="font-size: 0pt;"><xul:listboxbody><span style="border: 1px solid red;"/></xul:listboxbody></span>
+</div>
+</body>
+</html>
diff --git a/layout/xul/crashtests/432058-1.xul b/layout/xul/crashtests/432058-1.xul
new file mode 100644
index 000000000..a7f63adf8
--- /dev/null
+++ b/layout/xul/crashtests/432058-1.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+// <![CDATA[
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ var lb = document.getElementById("lb");
+ var firstli = document.getElementById("firstli");
+ lb.appendChild(document.createElementNS(XUL_NS, "hbox"));
+ lb.appendChild(document.createElementNS(XUL_NS, "listitem"));
+ firstli.style.display = "none";
+
+ // Flush layout.
+ document.getBoxObjectFor(document.documentElement).height;
+
+ lb.removeChild(firstli);
+}
+
+// ]]>
+</script>
+
+<listbox id="lb"><listitem id="firstli"/></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/432068-1.xul b/layout/xul/crashtests/432068-1.xul
new file mode 100644
index 000000000..02c3114e6
--- /dev/null
+++ b/layout/xul/crashtests/432068-1.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<hbox style="display: none;">
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="x">
+ <content><listitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/></content>
+ </binding>
+ </bindings>
+</hbox>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").style.MozBinding = "url('#x')";
+
+ // Flush layout.
+ document.documentElement.boxObject.height;
+
+ document.getElementById("listbox").removeChild(document.getElementById("c"));
+}
+
+</script>
+
+<listbox id="listbox"><listitem/><listitem id="b"/><listitem id="c"/></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/432068-2.xul b/layout/xul/crashtests/432068-2.xul
new file mode 100644
index 000000000..c75984bae
--- /dev/null
+++ b/layout/xul/crashtests/432068-2.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var l = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "listitem");
+ l.style.display = "none";
+
+ var c = document.getElementById("c");
+ c.parentNode.insertBefore(l, c);
+
+ document.getElementById("listbox").removeChild(document.getElementById("c"));
+}
+
+</script>
+
+<listbox id="listbox"><listitem/><listitem id="c"/></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/433296-1.xul b/layout/xul/crashtests/433296-1.xul
new file mode 100644
index 000000000..10fd0ec7b
--- /dev/null
+++ b/layout/xul/crashtests/433296-1.xul
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<hbox><listboxbody><listheader/><hbox><iframe/></hbox></listboxbody><tabpanels><tabpanels/></tabpanels></hbox>
+
+</window>
diff --git a/layout/xul/crashtests/433429.xul b/layout/xul/crashtests/433429.xul
new file mode 100644
index 000000000..a52bce68f
--- /dev/null
+++ b/layout/xul/crashtests/433429.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var listbox = document.getElementById("listbox");
+
+ listbox.removeChild(listbox.childNodes[1]);
+ document.documentElement.style.MozBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%3Chbox%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fkeymaster%2Fgatekeeper%2Fthere.is.only.xul%22%2F%3E%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')";
+ document.documentElement.boxObject.height;
+ listbox.removeChild(listbox.childNodes[0]);
+}
+
+</script>
+
+<listbox id="listbox" style="-moz-binding: url(data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%3Clistbox%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fkeymaster%2Fgatekeeper%2Fthere.is.only.xul%22%3E%3Cchildren%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%2F%3E%3C%2Flistbox%3E%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A);"><listitem/><listitem/></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/434458-1.xul b/layout/xul/crashtests/434458-1.xul
new file mode 100644
index 000000000..fbec2a413
--- /dev/null
+++ b/layout/xul/crashtests/434458-1.xul
@@ -0,0 +1,20 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+function boom() {
+ var a = document.getElementById('a');
+ var x = a.popupBoxObject;
+ a.parentNode.removeChild(a);
+ x.enableKeyboardNavigator(true);
+ x.openPopup(null, "after_start", 0, 0, false, false, null);
+ x.openPopupAtScreen(2, 2, false, null);
+ x.showPopup(document.documentElement, a, -1, -1, "popup", "topleft", "topleft");
+ x.hidePopup();
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<menupopup id="a"/>
+
+</window>
diff --git a/layout/xul/crashtests/452185.html b/layout/xul/crashtests/452185.html
new file mode 100644
index 000000000..d4981ffdf
--- /dev/null
+++ b/layout/xul/crashtests/452185.html
@@ -0,0 +1,3 @@
+<html><head></head><body><div style="position: absolute;"> </div>
+<style>div, head {-moz-binding:url(452185.xml#a);</style>
+</body></html>
diff --git a/layout/xul/crashtests/452185.xml b/layout/xul/crashtests/452185.xml
new file mode 100644
index 000000000..655c43a8d
--- /dev/null
+++ b/layout/xul/crashtests/452185.xml
@@ -0,0 +1,5 @@
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:xlink="http://www.w3.org/1999/xlink">
+<binding id="a">
+<content><tbody xmlns="http://www.w3.org/1999/xhtml"><style>*::before { content:"b"; }</style></tbody></content>
+
+</binding></bindings> \ No newline at end of file
diff --git a/layout/xul/crashtests/460900-1.xul b/layout/xul/crashtests/460900-1.xul
new file mode 100644
index 000000000..bff7c5c36
--- /dev/null
+++ b/layout/xul/crashtests/460900-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('label').control='c';">
+<label id="label"><hbox><listboxbody><hbox/><tooltip/></listboxbody><autorepeatbutton/></hbox></label>
+</window>
diff --git a/layout/xul/crashtests/464149-1.xul b/layout/xul/crashtests/464149-1.xul
new file mode 100644
index 000000000..556656f02
--- /dev/null
+++ b/layout/xul/crashtests/464149-1.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window onload="boom();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="m"><content><xul:textbox type="text"><children/></xul:textbox></content></binding>
+</bindings>
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.getElementById("b").style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns='http://www.mozilla.org/xbl'><binding id='foo'><content><hbox xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'><children xmlns='http://www.mozilla.org/xbl'/></hbox></content></binding></bindings>\n") + '")';
+}
+
+]]>
+</script>
+
+<listbox style="float: right;"><listitem/><listitem id="b"/><listitem><hbox style="-moz-binding: url(#m);"/></listitem></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/464407-1.xhtml b/layout/xul/crashtests/464407-1.xhtml
new file mode 100644
index 000000000..83666a6a4
--- /dev/null
+++ b/layout/xul/crashtests/464407-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+</head>
+<body>
+
+<xul:radio style="overflow: auto; height: 72057594037927940pt; display: table-cell;"/>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/467080.xul b/layout/xul/crashtests/467080.xul
new file mode 100644
index 000000000..bc579b0ee
--- /dev/null
+++ b/layout/xul/crashtests/467080.xul
@@ -0,0 +1,24 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" style="-moz-binding:url(#a);" class="reftest-wait">
+
+<content xmlns="http://www.mozilla.org/xbl" ordinal="-1">
+<mathml:median style="display: block;"/>
+</content>
+
+<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function finish() {
+ document.documentElement.removeAttribute("class");
+}
+function doe() {
+document.documentElement.removeAttribute('style');
+setTimeout(finish, 0);
+}
+setTimeout(doe, 100);
+]]></script>
+
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<content><children/></content>
+</binding></bindings>
+</window>
diff --git a/layout/xul/crashtests/467481-1.xul b/layout/xul/crashtests/467481-1.xul
new file mode 100644
index 000000000..56fbd4441
--- /dev/null
+++ b/layout/xul/crashtests/467481-1.xul
@@ -0,0 +1,6 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('a').setAttribute('ordinal', 30);">
+ <listbox>
+ <listitem id="a"/>
+ <listitem><iframe/></listitem>
+ </listbox>
+</window>
diff --git a/layout/xul/crashtests/470063-1.html b/layout/xul/crashtests/470063-1.html
new file mode 100644
index 000000000..11c01b30e
--- /dev/null
+++ b/layout/xul/crashtests/470063-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.removeChild(document.documentElement)
+ document.appendChild(document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox"));
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/xul/crashtests/470272.html b/layout/xul/crashtests/470272.html
new file mode 100644
index 000000000..f23de269b
--- /dev/null
+++ b/layout/xul/crashtests/470272.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script>
+function doe2(i) {
+document.documentElement.offsetHeight;
+document.getElementById('a').setAttribute('style', 'display: -moz-inline-box;');
+document.documentElement.offsetHeight;
+}
+</script>
+</head>
+<body style="float: right; -moz-column-count: 2; height: 20%;" onload="setTimeout(doe2,0);">
+ <div style="display: none;"></div>
+ <ul style="display: -moz-inline-box;"></ul>
+ <span id="a">
+ <ul style="display: -moz-grid; overflow: scroll;"></ul>
+ <span style="display: -moz-inline-box; height: 10px;">
+ <span style="position: absolute;"></span>
+ </span>
+ </span>
+</body>
+</html>
diff --git a/layout/xul/crashtests/472189.xul b/layout/xul/crashtests/472189.xul
new file mode 100644
index 000000000..e276d8fc7
--- /dev/null
+++ b/layout/xul/crashtests/472189.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window id="yourwindow" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script type="text/javascript">
+<![CDATA[
+function onload() {
+ document.addEventListener("DOMAttrModified", function() {}, true);
+ document.getElementById("test").setAttribute("value", "50");
+}
+]]>
+</script>
+<progressmeter id="test"/>
+</window> \ No newline at end of file
diff --git a/layout/xul/crashtests/475133.html b/layout/xul/crashtests/475133.html
new file mode 100644
index 000000000..ea4ee6325
--- /dev/null
+++ b/layout/xul/crashtests/475133.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+function x()
+{
+ document.removeEventListener("DOMAttrModified", x, false);
+ document.removeChild(document.documentElement);
+}
+
+function boom()
+{
+ var p = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "progressmeter");
+ document.addEventListener("DOMAttrModified", x, false);
+ document.documentElement.appendChild(p);
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/xul/crashtests/488210-1.xhtml b/layout/xul/crashtests/488210-1.xhtml
new file mode 100644
index 000000000..9c8e2640c
--- /dev/null
+++ b/layout/xul/crashtests/488210-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var listbox = document.createElementNS(XUL_NS, 'listbox');
+ document.body.appendChild(listbox);
+ listbox.appendChild(document.createElementNS(XUL_NS, 'listitem'));
+ listbox.appendChild(document.createElementNS(XUL_NS, 'listboxbody'));
+ listbox.appendChild(document.createElementNS(XUL_NS, 'hbox'));
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/xul/crashtests/495728-1.xul b/layout/xul/crashtests/495728-1.xul
new file mode 100644
index 000000000..ee8498d05
--- /dev/null
+++ b/layout/xul/crashtests/495728-1.xul
@@ -0,0 +1,239 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window id="list-testcase" title="Testcase"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<script>
+function scrollup() {
+ var list = document.getElementById('list');
+ var firstindex = list.getIndexOfItem(document.getElementById('first'));
+ list.ensureIndexIsVisible(firstindex);
+ setTimeout("document.documentElement.removeAttribute('class')",1);
+}
+
+function scrolldown() {
+ var list = document.getElementById('list');
+ var lastindex = list.getIndexOfItem(document.getElementById('last'));
+ list.ensureIndexIsVisible(lastindex);
+ setTimeout("scrollup()",1);
+}
+
+window.addEventListener("load", scrolldown, false);
+</script>
+
+<listbox id="list">
+<listitem label="Item x" id="first"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x"/>
+<listitem label="Item x" id="last"/>
+</listbox>
+
+</window>
diff --git a/layout/xul/crashtests/508927-1.xul b/layout/xul/crashtests/508927-1.xul
new file mode 100644
index 000000000..98faff4a6
--- /dev/null
+++ b/layout/xul/crashtests/508927-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><xul:listrows xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><xul:listboxbody/><children xmlns="http://www.mozilla.org/xbl"/></xul:listrows></content></binding></bindings>
+<hbox style="-moz-binding: url(#foo);"><listitem/><listitem/></hbox>
+</window>
diff --git a/layout/xul/crashtests/508927-2.xul b/layout/xul/crashtests/508927-2.xul
new file mode 100644
index 000000000..5bf4f9a0c
--- /dev/null
+++ b/layout/xul/crashtests/508927-2.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><xul:listrows xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><xul:listboxbody/><children xmlns="http://www.mozilla.org/xbl"/></xul:listrows></content></binding></bindings>
+<hbox style="-moz-binding: url(#foo);"><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/></hbox>
+</window>
diff --git a/layout/xul/crashtests/514300-1.xul b/layout/xul/crashtests/514300-1.xul
new file mode 100644
index 000000000..d0d655011
--- /dev/null
+++ b/layout/xul/crashtests/514300-1.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('listbox').removeChild(document.getElementById('span'));">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content><listitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><children xmlns="http://www.mozilla.org/xbl"/></listitem></content>
+ </binding>
+</bindings>
+
+<listbox id="listbox" style="-moz-binding: url(#foo)"><span xmlns="http://www.w3.org/1999/xhtml" id="span"/></listbox>
+
+</window>
diff --git a/layout/xul/crashtests/536931-1.xhtml b/layout/xul/crashtests/536931-1.xhtml
new file mode 100644
index 000000000..6f3fc1396
--- /dev/null
+++ b/layout/xul/crashtests/536931-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<listbox id="listbox" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><tab/></listbox>
+<script>window.addEventListener("load", function() { document.documentElement.appendChild(document.getElementById("listbox")); }, false);</script>
+</html>
diff --git a/layout/xul/crashtests/538308-1.xul b/layout/xul/crashtests/538308-1.xul
new file mode 100644
index 000000000..a96f3fa4e
--- /dev/null
+++ b/layout/xul/crashtests/538308-1.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="run()">
+
+ <tree id="tr" flex="1">
+ <treecols>
+ <treecol/>
+ </treecols>
+ <treechildren>
+ <html:optgroup id="group">
+ <html:option id="victim" label="never see this"/>
+ </html:optgroup>
+ </treechildren>
+ </tree>
+
+ <script type="text/javascript"><![CDATA[
+ function run() {
+ group = document.getElementById("group");
+ tc = document.createElement("treechildren");
+ group.appendChild(tc);
+
+ v = document.getElementById("victim");
+ v.parentNode.removeChild(v);
+ v = null;
+
+ tree = document.getElementById("tr");
+ col = tree.columns[0];
+ alert(tree.view.getItemAtIndex(1, col));
+ }
+ ]]></script>
+</window>
diff --git a/layout/xul/crashtests/557174-1.xml b/layout/xul/crashtests/557174-1.xml
new file mode 100644
index 000000000..02850a2db
--- /dev/null
+++ b/layout/xul/crashtests/557174-1.xml
@@ -0,0 +1 @@
+<ther:window xmlns:ther="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" a="" e=""><HTML><ther:statusbar l="" c=""><ther:menulist d=""><ther:menu t="" i="" l=""><mat:h xmlns:mat="http://www.w3.org/1998/Math/MathML" w=""/></ther:menu><ther:menupopup p=""/><ther:menu a="" t="" l=""><ther:menuseparator u="" x=""><xht:html xmlns:xht="http://www.w3.org/1999/xhtml" x=""><xht:body d=""><xht:abbr d=""><xht:abbr p=""><xht:small s=""><xht:a s=""><xht:var e=""><xht:samp e=""><xht:code p=""><xht:b e=""><xht:b d=""><xht:del t=""><xht:h4 r=""><xht:var l=""><xht:i r=""><xht:em r=""><xht:em n=""><xht:map g=""><xht:isindex d=""/></xht:map></xht:em></xht:em></xht:i></xht:var></xht:h4></xht:del></xht:b></xht:b></xht:code></xht:samp></xht:var></xht:a></xht:small></xht:abbr></xht:abbr></xht:body></xht:html></ther:menuseparator></ther:menu></ther:menulist></ther:statusbar></HTML></ther:window> \ No newline at end of file
diff --git a/layout/xul/crashtests/564705-1.xul b/layout/xul/crashtests/564705-1.xul
new file mode 100644
index 000000000..b0f29bef7
--- /dev/null
+++ b/layout/xul/crashtests/564705-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><label value="&#x2026;" accesskey="b"></label></window>
+
diff --git a/layout/xul/crashtests/583957-1.html b/layout/xul/crashtests/583957-1.html
new file mode 100644
index 000000000..85b51bf0c
--- /dev/null
+++ b/layout/xul/crashtests/583957-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ window.addEventListener("DOMSubtreeModified", function(){}, false);
+
+ var m = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
+ document.body.appendChild(m);
+ m.setAttribute("type", "checkbox");
+ m.setAttribute("checked", "true");
+ m.removeAttribute("type");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/xul/crashtests/617089.html b/layout/xul/crashtests/617089.html
new file mode 100644
index 000000000..22e5f6d53
--- /dev/null
+++ b/layout/xul/crashtests/617089.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <div style="display: -moz-inline-box;">
+ <table style="height: 101%;"><tbody><tr><td><div></div></td></tr></tbody></table>
+ <table style="height: 101%;"><tbody><tr><td><div></div></td></tr></tbody></table>
+ </div>
+ </body>
+</html>
diff --git a/layout/xul/crashtests/716503.html b/layout/xul/crashtests/716503.html
new file mode 100644
index 000000000..250ad2ba4
--- /dev/null
+++ b/layout/xul/crashtests/716503.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+div::before {
+ content: "j";
+ display:-moz-inline-box;
+}
+</style>
+<body><div></div></body>
+</html>
diff --git a/layout/xul/crashtests/crashtests.list b/layout/xul/crashtests/crashtests.list
new file mode 100644
index 000000000..4ee6654c6
--- /dev/null
+++ b/layout/xul/crashtests/crashtests.list
@@ -0,0 +1,99 @@
+load 131008-1.xul
+load 137216-1.xul
+load 140218-1.xml
+load 151826-1.xul
+load 168724-1.xul
+load 189814-1.xul
+load 237787-1.xul
+load 265161-1.xul
+load 289410-1.xul
+load 290743.html
+load 291702-1.xul
+load 291702-2.xul
+load 291702-3.xul
+load 294371-1.xul
+load 311457-1.html
+load 321056-1.xhtml
+load 322786-1.xul
+load 325377.xul
+load 326834-1.html
+load 326879-1.xul
+load 327776-1.xul
+load 328135-1.xul
+load 329327-1.xul
+load 329407-1.xml
+load 329477-1.xhtml
+load 336962-1.xul
+load 344228-1.xul
+load 346083-1.xul
+load 346281-1.xul
+load 350460.xul
+load 360642-1.xul
+load 365151.xul
+load 366112-1.xul
+asserts(0-50) load 366203-1.xul # bug 1217984
+asserts(24) asserts-if(Android&&!asyncPan,9) load 367185-1.xhtml # bug 1220345
+load 369942-1.xhtml
+load 374102-1.xul
+load 376137-1.html
+load 376137-2.html
+load 377592-1.svg
+load 378961.html
+load 381862.html
+load 382746-1.xul
+load 382899-1.xul
+load 383236-1.xul
+load 384037-1.xhtml
+load 384105-1.html
+load 384373.html
+load 384491-1.xhtml
+load 384871-1.html
+load 386642.xul
+load 387033-1.xhtml
+load 387080-1.xul
+load 391974-1.html
+load 394120-1.xhtml
+load 397293.xhtml
+load 397304-1.html
+load 398326-1.xhtml
+load 399013.xul
+load 400779-1.xhtml
+load 402912-1.xhtml
+load 404192.xhtml
+load 407152.xul
+load 408904-1.xul
+load 412479-1.xhtml
+asserts(4) asserts-if(gtkWidget&&browserIsRemote,6) load 415394-1.xhtml # Bug 163838, bug 1195474
+load 417509.xul
+load 420424-1.xul
+load 430356-1.xhtml
+load 431738.xhtml
+load 432058-1.xul
+load 432068-1.xul
+load 432068-2.xul
+load 433296-1.xul
+load 433429.xul
+load 434458-1.xul
+load 452185.html
+load 460900-1.xul
+load 464149-1.xul
+asserts-if(winWidget,1) asserts-if(Android,0-1) load 464407-1.xhtml # Bug 450974 on win, Bug 1267054 on Android
+load 467080.xul
+load 467481-1.xul
+load 470063-1.html
+load 470272.html
+load 472189.xul
+load 475133.html
+load 488210-1.xhtml
+load 495728-1.xul
+load 508927-1.xul
+load 508927-2.xul
+load 514300-1.xul
+load 536931-1.xhtml
+asserts(1) load 538308-1.xul
+load 557174-1.xml
+load 564705-1.xul
+load 583957-1.html
+load 617089.html
+load menulist-focused.xhtml
+load 716503.html
diff --git a/layout/xul/crashtests/menulist-focused.xhtml b/layout/xul/crashtests/menulist-focused.xhtml
new file mode 100644
index 000000000..7a09a838d
--- /dev/null
+++ b/layout/xul/crashtests/menulist-focused.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+<xul:menulist focused="true"/>
+</body>
+</html>
diff --git a/layout/xul/grid/crashtests/306911-crash.xul b/layout/xul/grid/crashtests/306911-crash.xul
new file mode 100644
index 000000000..cf55dfdf8
--- /dev/null
+++ b/layout/xul/grid/crashtests/306911-crash.xul
@@ -0,0 +1,4 @@
+<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <listbox id="thelist" flex="1"> <listitem label="Item1" value="item1">
+ <listitem label="Item2" value="item2"/>
+ </listitem> </listbox>
+</window> \ No newline at end of file
diff --git a/layout/xul/grid/crashtests/306911-grid-testcases.xul b/layout/xul/grid/crashtests/306911-grid-testcases.xul
new file mode 100644
index 000000000..bb69f5bcd
--- /dev/null
+++ b/layout/xul/grid/crashtests/306911-grid-testcases.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tabbox>
+ <tabs>
+ <tab label="full grid" />
+ <tab label="grid alone" />
+ <tab label="columns alone" />
+ <tab label="rows alone" />
+ <tab label="column alone" />
+ <tab label="row alone" />
+ <tab label="wacky" />
+ </tabs>
+ <tabpanels>
+ <tabpanel>
+ <grid>
+ <rows style="color: blue">
+ <row>
+ <label value="row 1,1" />
+ <label value="row 1,2" />
+ </row>
+ <row>
+ <label value="row 2,1" />
+ <label value="row 2,2" />
+ </row>
+ </rows>
+ <columns style="color: fuchsia; opacity: 0.7">
+ <column>
+ <label value="column 1,1" />
+ <label value="column 1,2" />
+ </column>
+ <column>
+ <label value="column 2,1" />
+ <label value="column 2,2" />
+ </column>
+ </columns>
+ </grid>
+ </tabpanel>
+ <tabpanel>
+ <grid>
+ <label value="Text inside grid" />
+ </grid>
+ </tabpanel>
+ <tabpanel>
+ <columns>
+ <label value="Text inside columns" />
+ </columns>
+ </tabpanel>
+ <tabpanel>
+ <rows>
+ <label value="Text inside rows" />
+ </rows>
+ </tabpanel>
+ <tabpanel>
+ <column>
+ <label value="Text inside column" />
+ </column>
+ </tabpanel>
+ <tabpanel>
+ <row>
+ <label value="Text inside row" />
+ </row>
+ </tabpanel>
+ <tabpanel>
+ <grid>
+ <label value="Text inside grid one" />
+ <rows style="color: blue">
+ <label value="Text inside rows #1" />
+ <row>
+ <label value="row 1,1" />
+ <label value="row 1,2" />
+ </row>
+ <label value="Text inside rows #2" />
+ <row>
+ <label value="row 2,1" />
+ <label value="row 2,2" />
+ </row>
+ <label value="Text inside rows #3" />
+ </rows>
+ <label value="Text inside grid two" style="opacity: 0.7" />
+ <columns style="color: fuchsia; opacity: 0.7">
+ <label value="Text inside columns #1" />
+ <column>
+ <label value="column 1,1" />
+ <label value="column 1,2" />
+ </column>
+ <label value="Text inside columns #2" />
+ <column>
+ <label value="column 2,1" />
+ <label value="column 2,2" />
+ </column>
+ <label value="Text inside columns #3" />
+ </columns>
+ <label value="Text inside grid three" style="opacity: 0.4" />
+ </grid>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+</window>
diff --git a/layout/xul/grid/crashtests/306911-grid-testcases2.xul b/layout/xul/grid/crashtests/306911-grid-testcases2.xul
new file mode 100644
index 000000000..c6b4e3849
--- /dev/null
+++ b/layout/xul/grid/crashtests/306911-grid-testcases2.xul
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!-- vim:sw=4:ts=4:noet:
+ -->
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tabbox>
+ <tabs>
+ <tab label="no group" />
+ <tab label="wacky orientations" />
+ </tabs>
+ <tabpanels>
+ <tabpanel>
+ <grid>
+ <row>
+ <label value="row 1,1" />
+ <label value="row 1,2" />
+ </row>
+ <row>
+ <label value="row 2,1" />
+ <label value="row 2,2" />
+ </row>
+ <column>
+ <label value="column 1,1" />
+ <label value="column 1,2" />
+ </column>
+ <column>
+ <label value="column 2,1" />
+ <label value="column 2,2" />
+ </column>
+ </grid>
+ </tabpanel>
+ <tabpanel>
+ <grid>
+ <rows style="color: green">
+ <row>
+ <label value="rows+row 1" />
+ <label value="rows+row 2" />
+ </row>
+ <column>
+ <label value="rows+column 1" />
+ <label value="rows+column 2" />
+ </column>
+ <rows style="color: purple">
+ <row>
+ <label value="rows+rows+row 1" />
+ <label value="rows+rows+row 2" />
+ </row>
+ <column>
+ <label value="rows+rows+column 1" />
+ <label value="rows+rows+column 2" />
+ </column>
+ </rows>
+ <columns style="color: blue">
+ <row>
+ <label value="rows+columns+row 1" />
+ <label value="rows+columns+row 2" />
+ </row>
+ <column>
+ <label value="rows+columns+column 1" />
+ <label value="rows+columns+column 2" />
+ </column>
+ </columns>
+ </rows>
+ <columns style="opacity: 0.7; color: lime">
+ <row>
+ <label value="columns+row 1" />
+ <label value="columns+row 2" />
+ </row>
+ <column>
+ <label value="columns+column 1" />
+ <label value="columns+column 2" />
+ </column>
+ <rows style="color: fuchsia">
+ <row>
+ <label value="columns+rows+row 1" />
+ <label value="columns+rows+row 2" />
+ </row>
+ <column>
+ <label value="columns+rows+column 1" />
+ <label value="columns+rows+column 2" />
+ </column>
+ </rows>
+ <columns style="color: aqua">
+ <row>
+ <label value="columns+columns+row 1" />
+ <label value="columns+columns+row 2" />
+ </row>
+ <column>
+ <label value="columns+columns+column 1" />
+ <label value="columns+columns+column 2" />
+ </column>
+ </columns>
+ </columns>
+ </grid>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+</window>
diff --git a/layout/xul/grid/crashtests/311710-1.xul b/layout/xul/grid/crashtests/311710-1.xul
new file mode 100644
index 000000000..403b267e9
--- /dev/null
+++ b/layout/xul/grid/crashtests/311710-1.xul
@@ -0,0 +1,22 @@
+<window title="Testcase bug 311710 - Evil xul testcase, using display:-moz-grid-group causes crash [@ nsGridRow::IsCollapsed]"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<script type="application/x-javascript">
+function clickit() {
+ var button = document.getElementById('button');
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ button.dispatchEvent(evt);
+}
+window.addEventListener('load', clickit, false);
+</script>
+
+ <grid>
+ <rows>
+ <row>
+ <separator/>
+ </row>
+ </rows>
+ </grid>
+<button id="button" onclick="document.getElementsByTagName('row')[0].style.display='-moz-grid-group'" label="Mozilla should not crash, when clicking this button"/>
+</window>
diff --git a/layout/xul/grid/crashtests/312784-1.xul b/layout/xul/grid/crashtests/312784-1.xul
new file mode 100644
index 000000000..ee4054d80
--- /dev/null
+++ b/layout/xul/grid/crashtests/312784-1.xul
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-JavaScript">
+function crash() {
+ document.getElementById("test").style.display = "none";
+}
+
+function clickit() {
+ var button = document.getElementById('button');
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ button.dispatchEvent(evt);
+}
+
+window.onload = clickit;
+
+</script>
+
+ <grid>
+ <columns>
+ <column/>
+ </columns>
+ <rows id="test">
+ <row><button label="placeholder"/></row>
+ </rows>
+ </grid>
+<button id="button" label="Crash me" onclick="crash()"/>
+</window>
diff --git a/layout/xul/grid/crashtests/313173-1-inner.xul b/layout/xul/grid/crashtests/313173-1-inner.xul
new file mode 100644
index 000000000..284d6c1f1
--- /dev/null
+++ b/layout/xul/grid/crashtests/313173-1-inner.xul
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window title="Testcase bug - Crash with evil xul testcase, using -moz-grid/table-caption"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<grid flex="1">
+ <columns>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ </row>
+ </rows>
+</grid>
+
+<html:script>
+function doe(){
+document.getElementsByTagName('columns')[0].style.display='table-caption';
+setTimeout(doe2,20);
+}
+function doe2(){
+document.getElementsByTagName('columns')[0].style.display='-moz-grid';
+}
+
+function clickit() {
+ var button = document.getElementById('button');
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ button.dispatchEvent(evt);
+setTimeout('clickit();', 20);
+}
+window.addEventListener('load', clickit, false);
+
+</html:script>
+ <html:button id="button" onclick="doe()" label="click">Clicking this should not crash Mozilla</html:button>
+</window>
+
diff --git a/layout/xul/grid/crashtests/313173-1.html b/layout/xul/grid/crashtests/313173-1.html
new file mode 100644
index 000000000..8b45339ab
--- /dev/null
+++ b/layout/xul/grid/crashtests/313173-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="313173-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/layout/xul/grid/crashtests/321066-1.xul b/layout/xul/grid/crashtests/321066-1.xul
new file mode 100644
index 000000000..789c2582c
--- /dev/null
+++ b/layout/xul/grid/crashtests/321066-1.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <grid>
+ <rows>
+ <column/>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/crashtests/321073-1.xul b/layout/xul/grid/crashtests/321073-1.xul
new file mode 100644
index 000000000..b92098b62
--- /dev/null
+++ b/layout/xul/grid/crashtests/321073-1.xul
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <listcols>
+ <grid/>
+ <listitem/>
+ </listcols>
+</window> \ No newline at end of file
diff --git a/layout/xul/grid/crashtests/382750-1.xul b/layout/xul/grid/crashtests/382750-1.xul
new file mode 100644
index 000000000..7a9da73ec
--- /dev/null
+++ b/layout/xul/grid/crashtests/382750-1.xul
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<grid><rows><listbox/></rows></grid>
+
+</window>
diff --git a/layout/xul/grid/crashtests/400790-1.xul b/layout/xul/grid/crashtests/400790-1.xul
new file mode 100644
index 000000000..4de709428
--- /dev/null
+++ b/layout/xul/grid/crashtests/400790-1.xul
@@ -0,0 +1,20 @@
+<xul xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script>
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var newListbox = document.createElementNS(XUL_NS, "listbox");
+ document.getElementById("listbox").appendChild(newListbox);
+
+ var newHbox = document.createElementNS(XUL_NS, "hbox");
+ document.getElementById("listitem").appendChild(newHbox);
+}
+
+</script>
+
+<listbox id="listbox"><listitem id="listitem" /></listbox>
+
+</xul>
diff --git a/layout/xul/grid/crashtests/423802-crash.xul b/layout/xul/grid/crashtests/423802-crash.xul
new file mode 100644
index 000000000..0ae4eab8f
--- /dev/null
+++ b/layout/xul/grid/crashtests/423802-crash.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+<window xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<grid>
+ <columns>
+ <column id="col1" flex="1"/>
+ <column id="col2" flex="1"/>
+ <column id="col3" flex="P-2"/>
+ </columns>
+</grid>
+
+</window>
diff --git a/layout/xul/grid/crashtests/crashtests.list b/layout/xul/grid/crashtests/crashtests.list
new file mode 100644
index 000000000..afe8e1002
--- /dev/null
+++ b/layout/xul/grid/crashtests/crashtests.list
@@ -0,0 +1,11 @@
+load 306911-crash.xul
+load 306911-grid-testcases.xul
+load 306911-grid-testcases2.xul
+load 311710-1.xul
+load 312784-1.xul
+load 313173-1.html
+load 321066-1.xul
+load 321073-1.xul
+load 382750-1.xul
+load 400790-1.xul
+load 423802-crash.xul
diff --git a/layout/xul/grid/examples/borderedcolumns.xul b/layout/xul/grid/examples/borderedcolumns.xul
new file mode 100644
index 000000000..15ee06911
--- /dev/null
+++ b/layout/xul/grid/examples/borderedcolumns.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" id="grid">
+ <columns>
+ <column style="border: 10px inset red;"/>
+ <column/>
+ <column style="border: 10px inset red;"/>
+ </columns>
+
+ <rows style="font-size: 20pt;">
+ <row>
+ <text value="Cell 1 "/>
+ <text value="Cell 2 "/>
+ <text value="Cell 3 "/>
+ </row>
+ <row>
+ <text value="Cell 4 "/>
+ <text value="Cell 5 " style="border: 10px inset red;"/>
+ <text value="Cell 6 "/>
+ </row>
+ <row>
+ <text value="Cell 7 "/>
+ <text value="Cell 8 "/>
+ <text value="Cell 9 "/>
+ </row>
+
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/borderedrowscolumns.xul b/layout/xul/grid/examples/borderedrowscolumns.xul
new file mode 100644
index 000000000..94d3d8d99
--- /dev/null
+++ b/layout/xul/grid/examples/borderedrowscolumns.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" id="grid">
+ <columns style="border: 0px solid blue">
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows style="font-size: 40pt; border: 15px inset blue">
+ <row>
+ <text value="Cell(1)"/>
+ <text value="Cell(2)"/>
+ <text value="Cell(3)"/>
+ </row>
+ <rows style="border: 10px inset green">
+ <row>
+ <text value="Cell(1)"/>
+ <text value="Cell(2)"/>
+ <text value="Cell(3)"/>
+ </row>
+ <row>
+ <text value="Cell(4)"/>
+ <text value="Cell(5)"/>
+ <text value="Cell(6)"/>
+ </row>
+ <row>
+ <text value="Cell(7)"/>
+ <text value="Cell(8)"/>
+ <text value="Cell(9)"/>
+ </row>
+
+ </rows>
+ <row>
+ <text value="Cell(7)"/>
+ <text value="Cell(8)"/>
+ <text value="Cell(9)"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/borderedrowscolumns2.xul b/layout/xul/grid/examples/borderedrowscolumns2.xul
new file mode 100644
index 000000000..96b6ca9e5
--- /dev/null
+++ b/layout/xul/grid/examples/borderedrowscolumns2.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="gridsample.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" id="grid">
+ <columns>
+ <column style="border: 10px solid red;"/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows style="font-size: 40pt;">
+ <row style="border: 10px solid red;">
+ <text value="Cell 1 "/>
+ <text value="Cell 2 "/>
+ <text value="Cell 3 "/>
+ </row>
+ <row>
+ <text value="Cell 4 "/>
+ <text value="Cell 5 "/>
+ <text value="Cell 6 "/>
+ </row>
+ <row>
+ <text value="Cell 7 "/>
+ <text value="Cell 8 "/>
+ <text value="Cell 9 "/>
+ </row>
+
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/borderedrowscolumns3.xul b/layout/xul/grid/examples/borderedrowscolumns3.xul
new file mode 100644
index 000000000..30a6fcc1b
--- /dev/null
+++ b/layout/xul/grid/examples/borderedrowscolumns3.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" id="grid">
+ <columns>
+ <column/>
+ <columns style="border: 10px solid red">
+ <column/>
+ </columns>
+ <column/>
+ </columns>
+
+ <rows style="font-size: 24pt">
+ <row>
+ <text value="Cell(1)"/>
+ <text value="Cell(2)"/>
+ <text value="Cell(3)"/>
+ </row>
+ <rows style="border: 10px solid green">
+ <row>
+ <text value="Cell(1)"/>
+ <text value="Cell(2)"/>
+ <text value="Cell(3)"/>
+ </row>
+ <row>
+ <text value="Cell(4)"/>
+ <text value="Cell(5)"/>
+ <text value="Cell(6)"/>
+ </row>
+ <row>
+ <text value="Cell(7)"/>
+ <text value="Cell(8)"/>
+ <text value="Cell(9)"/>
+ </row>
+
+ </rows>
+ <row>
+ <text value="Cell(7)"/>
+ <text value="Cell(8)"/>
+ <text value="Cell(9)"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/bordermargincolumns1.xul b/layout/xul/grid/examples/bordermargincolumns1.xul
new file mode 100644
index 000000000..009f932a8
--- /dev/null
+++ b/layout/xul/grid/examples/bordermargincolumns1.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="gridsample.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" id="grid">
+ <columns>
+ <column style="border: 10px inset red; margin: 10px; "/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows style="font-size: 40pt;">
+ <row style="border: 5px solid green">
+ <text value="Cell 1"/>
+ <text value="Cell 2"/>
+ <text value="Cell 3"/>
+ </row>
+ <row>
+ <text value="Cell 4"/>
+ <text value="Cell 5"/>
+ <text value="Cell 6"/>
+ </row>
+ <row>
+ <text value="Cell 7"/>
+ <text value="Cell 8"/>
+ <text value="Cell 9"/>
+ </row>
+
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/collapsetest.xul b/layout/xul/grid/examples/collapsetest.xul
new file mode 100644
index 000000000..5e1a042f6
--- /dev/null
+++ b/layout/xul/grid/examples/collapsetest.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+ function collapseTag(id)
+ {
+ var row = window.document.getElementById(id);
+ row.setAttribute("collapsed","true");
+ }
+
+ function uncollapseTag(id)
+ {
+ var row = window.document.getElementById(id);
+ row.setAttribute("collapsed","false");
+ }
+
+
+</script>
+
+ <hbox>
+ <grid style="border: 2px solid red;" id="grid">
+ <columns id="columns1">
+ <column id="column1"/>
+ <column id="column2"/>
+ <column id="column3"/>
+ </columns>
+
+ <rows id="rows1" style="font-size: 24pt">
+ <row id="row1">
+ <text value="cell1"/>
+ <text value="cell2"/>
+ <text value="cell3"/>
+ </row>
+ <row id="row2">
+ <text value="cell4"/>
+ <text value="cell5"/>
+ <text value="cell6"/>
+ </row>
+ <row id="row3">
+ <text value="cell7"/>
+ <text value="cell8"/>
+ <text value="cell9"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ <hbox>
+ <button label="collapse row 2" oncommand="collapseTag('row2');"/>
+ <button label="uncollapse row 2" oncommand="uncollapseTag('row2');"/>
+ <button label="collapse column 2" oncommand="collapseTag('column2');"/>
+ <button label="uncollapse column 2" oncommand="uncollapseTag('column2');"/>
+
+ </hbox>
+
+</window>
diff --git a/layout/xul/grid/examples/divcolumngrid.xul b/layout/xul/grid/examples/divcolumngrid.xul
new file mode 100644
index 000000000..2268c302c
--- /dev/null
+++ b/layout/xul/grid/examples/divcolumngrid.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox>
+ <grid style="border: 2px solid red;">
+ <columns>
+ <column/>
+ <description style="border: 10px inset gray">
+ hello
+ </description>
+ <column/>
+ </columns>
+
+ <rows>
+ <row>
+ <text style="font-size: 40px" value="foo1"/>
+ <text style="font-size: 40px" value="foo2"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1" style="background-color: white"/>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/divrowgrid.xul b/layout/xul/grid/examples/divrowgrid.xul
new file mode 100644
index 000000000..657553aab
--- /dev/null
+++ b/layout/xul/grid/examples/divrowgrid.xul
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="width: 100px; border: 2px solid red;">
+ <rows>
+ <row style="font-size: 40px">
+ <text value="foo1"/>
+ <text value="foo2"/>
+ </row>
+ <description>
+ this is some html in the row this should wrap if it is big enough.
+ </description>
+ <row style="font-size: 40px">
+ <text value="foo3"/>
+ <text value="foo4"/>
+ </row>
+
+ </rows>
+ </grid>
+ <spacer flex="1" style="background-color: white"/>
+ </hbox>
+
+
+</window>
diff --git a/layout/xul/grid/examples/dynamicgrid.xul b/layout/xul/grid/examples/dynamicgrid.xul
new file mode 100644
index 000000000..d718df5f9
--- /dev/null
+++ b/layout/xul/grid/examples/dynamicgrid.xul
@@ -0,0 +1,370 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="gridsample.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start()">
+
+<script>
+
+ var selected;
+ var count = 0;
+
+ function isCell(box)
+ {
+ if (box.localName == "row" ||
+ box.localName == "column" ||
+ box.localName == "rows" ||
+ box.localName == "columns" ||
+ box.localName == "grid")
+ return false;
+
+ return true;
+ }
+
+ function start()
+ {
+ selectIt(window.document.getElementById("rows"));
+ }
+
+ function selectIt(box)
+ {
+ if (!box)
+ return;
+
+ var a = box.getAttribute("selected");
+ if (a != "true") {
+ box.setAttribute("selected","true");
+ if (selected)
+ selected.setAttribute("selected","false");
+
+ selected = box;
+ }
+ }
+
+ function addCellSelectionHandle(box)
+ {
+ box.setAttribute("oncommand", "selectIt(this);");
+ }
+
+ function addRowColumnSelectionHandle(box)
+ {
+ box.setAttribute("onclick", "selectIt(this);");
+ }
+
+ function createButton(str)
+ {
+ var b = document.createElement("button");
+ b.setAttribute("label", str+count);
+ count++;
+ addCellSelectionHandle(b);
+ return b;
+ }
+
+ function createRow()
+ {
+ var b = document.createElement("row");
+ b.setAttribute("dynamic","true");
+
+ addRowColumnSelectionHandle(b);
+ return b;
+ }
+
+ function createColumn()
+ {
+ var b = document.createElement("column");
+ b.setAttribute("dynamic","true");
+ addRowColumnSelectionHandle(b);
+ return b;
+ }
+
+ function createText(str)
+ {
+ var text = document.createElement("text");
+ text.setAttribute("value", str+count);
+ count++;
+ text.setAttribute("style", "font-size: 40pt");
+ addCellSelectionHandle(text);
+ return text;
+ }
+
+ function appendElement(element, prepend)
+ {
+ if (!selected)
+ return;
+
+ setUserAttribute(element);
+
+ if (selected.localName == "rows")
+ appendRow(false);
+ else if (selected.localName == "columns")
+ appendColumn(false);
+
+ if (selected.localName == "row" || selected.localName == "column" ) { // is row or column
+ selected.appendChild(element);
+ } else {
+ var parent = selected.parentNode;
+ if (prepend)
+ parent.insertBefore(element, selected);
+ else {
+ var next = selected.nextSibling;
+ if (next)
+ parent.insertBefore(element,next);
+ else
+ parent.appendChild(element);
+ }
+ }
+
+ selectIt(element);
+ }
+
+ function getRows(box)
+ {
+ return window.document.getElementById("rows");
+ }
+
+ function getColumns(box)
+ {
+ return window.document.getElementById("columns");
+ }
+
+ function setUserAttribute(element)
+ {
+ var attributeBox = document.getElementById("attributebox");
+ var valueBox = document.getElementById("valuebox");
+ var attribute = attributeBox.value;
+ var value = valueBox.value;
+ if (attribute != "")
+ element.setAttribute(attribute,value);
+ }
+
+ function appendRowColumn(rowColumn, prepend)
+ {
+ if (!selected)
+ return;
+
+ setUserAttribute(rowColumn);
+
+ var row = rowColumn;
+
+ // first see what we are adding.
+
+ if (isCell(selected)) { // if cell then select row/column
+ selectIt(selected.parentNode);
+ }
+
+ if (selected.localName == "row" || selected.localName == "rows")
+ if (row.localName == "column") {
+ selectIt(getColumns(selected));
+ dump("Selecting the column")
+ dump("Selected="+selected.localName);
+ }
+
+ if (selected.localName == "column" || selected.localName == "columns")
+ if (row.localName == "row")
+ selectIt(getRows(selected));
+
+ if (selected.localName == "rows" || selected.localName == "columns" )
+ { // if rows its easy
+ selected.appendChild(row);
+ } else {
+ var parent = selected.parentNode;
+ if (prepend)
+ parent.insertBefore(row, selected);
+ else {
+ var next = selected.nextSibling;
+ if (next)
+ parent.insertBefore(row,next);
+ else
+ parent.appendChild(row);
+ }
+ }
+
+ selectIt(row);
+ }
+
+ function appendRow(prepend)
+ {
+ var row = createRow();
+ appendRowColumn(row,prepend);
+ }
+
+
+ function appendColumn(prepend)
+ {
+ var column = createColumn();
+ appendRowColumn(column,prepend);
+ }
+
+
+ function selectRows()
+ {
+ var rows = getRows();
+ if (rows.firstChild)
+ selectIt(rows.firstChild);
+ else
+ selectIt(rows);
+ }
+
+
+ function selectColumns()
+ {
+ var columns = getColumns();
+ if (columns.firstChild)
+ selectIt(columns.firstChild);
+ else
+ selectIt(columns);
+ }
+
+ function nextElement()
+ {
+ if (!selected)
+ return;
+
+ selectIt(selected.nextSibling);
+ }
+
+ function previousElement()
+ {
+ if (!selected)
+ return;
+
+ selectIt(selected.previousSibling);
+ }
+
+ function selectRow()
+ {
+ if (!selected)
+ return;
+
+ if (selected.localName == "row")
+ return;
+
+ if (isCell(selected)) {
+ if (selected.parentNode.localName == "row")
+ selectIt(selected.parentNode);
+ }
+ }
+
+ function selectColumn()
+ {
+ if (!selected)
+ return;
+
+ if (selected.localName == "column")
+ return;
+
+ if (isCell(selected)) {
+ if (selected.parentNode.localName == "column")
+ selectIt(selected.parentNode);
+ }
+ }
+
+ function collapseGrid()
+ {
+ var grid = document.getElementById("grid");
+ var collapsed = grid.getAttribute("collapsed");
+
+ if (collapsed == "")
+ grid.setAttribute("collapsed","true");
+ else
+ grid.setAttribute("collapsed","");
+
+ }
+
+ function collapseElement()
+ {
+ if (selected) {
+ var collapsed = selected.getAttribute("collapsed");
+
+ if (collapsed == "")
+ selected.setAttribute("collapsed","true");
+ else
+ selected.setAttribute("collapsed","");
+ }
+ }
+
+</script>
+
+ <hbox flex="1" style="border: 2px inset gray; overflow: auto">
+ <vbox flex="1">
+ <hbox>
+ <grid id="grid" style="border: 2px solid red;">
+ <columns id="columns">
+ </columns>
+
+ <rows start="true" id="rows">
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+
+ <grid style="background-color: blue">
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+
+ <row>
+ <button label="append row" oncommand="appendRow(false);"/>
+ <button label="prepend row" oncommand="appendRow(true);"/>
+
+ <button label="append column" oncommand="appendColumn(false);"/>
+ <button label="prepend column" oncommand="appendColumn(true);"/>
+ </row>
+
+ <row>
+
+ <button label="append button" oncommand="appendElement(createButton('button'),false);"/>
+ <button label="prepend button" oncommand="appendElement(createButton('button'),true);"/>
+
+ <button label="append text" oncommand="appendElement(createText('text'),false);"/>
+ <button label="prepend text" oncommand="appendElement(createText('text'),true);"/>
+
+ </row>
+
+ <row>
+
+ <button label="select rows" oncommand="selectRows()"/>
+ <button label="select columns" oncommand="selectColumns()"/>
+
+ <button label="next" oncommand="nextElement()"/>
+ <button label="previous" oncommand="previousElement()"/>
+
+ </row>
+
+ <hbox align="center">
+ <button label="collapse/uncollapse grid" flex="1" oncommand="collapseGrid()"/>
+ <button label="collapse/uncollapse element" flex="1" oncommand="collapseElement()"/>
+ </hbox>
+
+
+
+ <hbox>
+
+ <text value="attribute"/>
+ <textbox id="attributebox" value="" flex="1"/>
+ <text value="value"/>
+ <textbox id="valuebox" value="" flex="2"/>
+ </hbox>
+
+
+ </rows>
+ </grid>
+
+</window>
diff --git a/layout/xul/grid/examples/flexgroupgrid.xul b/layout/xul/grid/examples/flexgroupgrid.xul
new file mode 100644
index 000000000..f4cd6622c
--- /dev/null
+++ b/layout/xul/grid/examples/flexgroupgrid.xul
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="gridsample.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px inset gray;" flex="1">
+ <columns>
+ <columns style="border: 10px solid red" flex="1">
+ <column flex="2"/>
+ <column flex="1"/>
+ </columns>
+ <column flex="1"/>
+ </columns>
+
+ <rows style="font-size: 20pt">
+ <rows>
+ <row>
+ <text class="yellow" value="CellA"/>
+ <text class="yellow" value="CellAB"/>
+ <text class="yellow" value="CellABC"/>
+ </row>
+ <row>
+ <text class="yellow" value="CellA"/>
+ <text class="yellow" value="CellAB"/>
+ <text class="yellow" value="CellABC"/>
+ </row>
+ </rows>
+ <row>
+ <text class="yellow" value="CellA"/>
+ <text class="yellow" value="CellAB"/>
+ <text class="yellow" value="CellABC"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/javascriptappend.xul b/layout/xul/grid/examples/javascriptappend.xul
new file mode 100644
index 000000000..f2a415cae
--- /dev/null
+++ b/layout/xul/grid/examples/javascriptappend.xul
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script>
+ function start()
+ {
+ var row = document.getElementById("row");
+ var text = document.createElement("text");
+ text.setAttribute("value", "foo");
+ row.appendChild(text);
+ }
+
+ </script>
+
+ <hbox>
+ <grid style="border: 2px solid red;" id="grid">
+ <columns>
+ </columns>
+
+ <rows>
+ <row id="row">
+ <button label="value"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1" style="background-color: white"/>
+ </hbox>
+
+ <button label="insert" oncommand="start()"/>
+
+</window>
diff --git a/layout/xul/grid/examples/jumpygrid.xul b/layout/xul/grid/examples/jumpygrid.xul
new file mode 100644
index 000000000..8bbeb5806
--- /dev/null
+++ b/layout/xul/grid/examples/jumpygrid.xul
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="gridsample.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script>
+ function flip(child)
+ {
+ var jump = child.getAttribute("jumpy");
+ if (jump != "true")
+ child.setAttribute("jumpy","true");
+ else
+ child.setAttribute("jumpy","false");
+ }
+
+ </script>
+ <hbox>
+ <grid style="border: 2px solid yellow;">
+ <columns>
+ </columns>
+
+ <rows>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+ <row>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ <button label="button" class="jumpy"/>
+ </row>
+
+ </rows>
+ </grid>
+ <spacer style="border: 2px solid white;" flex="1"/>
+ </hbox>
+ <spacer style="border: 2px solid white;" flex="1"/>
+
+</window>
diff --git a/layout/xul/grid/examples/nestedrows.xul b/layout/xul/grid/examples/nestedrows.xul
new file mode 100644
index 000000000..700f785b3
--- /dev/null
+++ b/layout/xul/grid/examples/nestedrows.xul
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox flex="1">
+ <grid style="border: 2px solid red" flex="1">
+
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <text value="out1"/>
+ <text value="out2"/>
+ <text value="out3"/>
+ </row>
+
+ <rows flex="1" style="border: 10px inset yellow; font-size: 20pt">
+ <row>
+ <text value="in1"/>
+ <text value="in2"/>
+ <text value="in3"/>
+ </row>
+ <row>
+ <text value="in4"/>
+ <text value="in5"/>
+ <text value="in5"/>
+ </row>
+ </rows>
+
+ </rows>
+
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/examples/rowspan.xul b/layout/xul/grid/examples/rowspan.xul
new file mode 100644
index 000000000..266a32229
--- /dev/null
+++ b/layout/xul/grid/examples/rowspan.xul
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px solid red;">
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row style="font-size: 40px">
+ <text value="foo1"/>
+ <text value="foo2"/>
+ </row>
+ <box width="50" style="border:5px inset grey">
+ <text value="hello there. This spans"/>
+ </box>
+ <row style="font-size: 40px" >
+ <text value="foo1"/>
+ <text value="foo2"/>
+ </row>
+
+ </rows>
+ </grid>
+ <spacer flex="1" style="background-color: white"/>
+ </hbox>
+
+
+</window>
diff --git a/layout/xul/grid/examples/scrollingcolumns.xul b/layout/xul/grid/examples/scrollingcolumns.xul
new file mode 100644
index 000000000..f29909624
--- /dev/null
+++ b/layout/xul/grid/examples/scrollingcolumns.xul
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox flex="1">
+ <grid style="border: 2px solid red" flex="1">
+
+ <rows>
+ <row flex="1"/>
+ <row flex="1"/>
+ <row flex="1"/>
+ </rows>
+ <columns>
+
+ <column>
+ <button label="left"/>
+ <button label="left"/>
+ <button label="left"/>
+ </column>
+
+ <columns flex="1" style="min-width: 1px; overflow: auto; background-color: green">
+ <column>
+ <button label="cell1"/>
+ <button label="cell1"/>
+ <button label="cell1"/>
+ </column>
+ <column>
+ <button label="cell2"/>
+ <button label="cell2"/>
+ <button label="cell2"/>
+ </column>
+ <column>
+ <button label="cell3"/>
+ <button label="cell3"/>
+ <button label="cell3"/>
+ </column>
+ <column>
+ <button label="cell4"/>
+ <button label="cell4"/>
+ <button label="cell4"/>
+ </column>
+ <column>
+ <button label="cell5"/>
+ <button label="cell5"/>
+ <button label="cell5"/>
+ </column>
+ <column>
+ <button label="cell6"/>
+ <button label="cell6"/>
+ <button label="cell6"/>
+ </column>
+ <column>
+ <button label="cell7"/>
+ <button label="cell7"/>
+ <button label="cell7"/>
+ </column>
+ </columns>
+ <column>
+ <button label="right"/>
+ <button label="right"/>
+ <button label="right"/>
+ </column>
+
+ </columns>
+
+ </grid>
+ <spacer width="100"/>
+ </hbox>
+ <spacer height="100"/>
+</window>
diff --git a/layout/xul/grid/examples/scrollingrows.xul b/layout/xul/grid/examples/scrollingrows.xul
new file mode 100644
index 000000000..fd5077cde
--- /dev/null
+++ b/layout/xul/grid/examples/scrollingrows.xul
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox flex="1">
+ <grid style="border: 2px solid red" flex="1">
+
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <button label="left"/>
+ <button label="left"/>
+ <button label="left"/>
+ </row>
+
+ <rows flex="1" style="border: 10px inset gray; overflow: auto; background-color: green">
+ <row>
+ <button label="cell1"/>
+ <button label="cell1"/>
+ <button label="cell1"/>
+ </row>
+ <row>
+ <button label="cell2"/>
+ <button label="cell2"/>
+ <button label="cell2"/>
+ </row>
+ <row>
+ <button label="cell3"/>
+ <button label="cell3"/>
+ <button label="cell3"/>
+ </row>
+ <row>
+ <button label="cell4"/>
+ <button label="cell4"/>
+ <button label="cell4"/>
+ </row>
+ <row>
+ <button label="cell5"/>
+ <button label="cell5"/>
+ <button label="cell5"/>
+ </row>
+ <row>
+ <button label="cell6"/>
+ <button label="cell6"/>
+ <button label="cell6"/>
+ </row>
+ <row>
+ <button label="cell7"/>
+ <button label="cell7"/>
+ <button label="cell7"/>
+ </row>
+ </rows>
+ <row>
+ <button label="right"/>
+ <button label="right"/>
+ <button label="right"/>
+ </row>
+
+ </rows>
+
+ </grid>
+ <spacer width="100"/>
+ </hbox>
+ <spacer height="100"/>
+</window>
diff --git a/layout/xul/grid/examples/splitter.xul b/layout/xul/grid/examples/splitter.xul
new file mode 100644
index 000000000..67946d487
--- /dev/null
+++ b/layout/xul/grid/examples/splitter.xul
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+
+<window orient="vertical" style="border: 2px solid green"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <grid style="border: 2px solid red;">
+ <columns>
+ <column style="min-width: 1px"/>
+ <splitter/>
+ <column style="min-width: 1px"/>
+ </columns>
+
+ <rows>
+ <row>
+ <text style="font-size: 40px" value="foo1"/>
+ <text style="font-size: 40px" value="foo2"/>
+ </row>
+ <label value="this is some text. This is longer"/>
+ <row>
+ <text style="font-size: 40px" value="foo1"/>
+ <text style="font-size: 40px" value="foo2"/>
+ </row>
+
+ </rows>
+ </grid>
+ <spacer flex="1" style="background-color: white"/>
+ </hbox>
+
+
+</window>
diff --git a/layout/xul/grid/moz.build b/layout/xul/grid/moz.build
new file mode 100644
index 000000000..074985aaf
--- /dev/null
+++ b/layout/xul/grid/moz.build
@@ -0,0 +1,43 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL')
+
+EXPORTS += [
+ 'nsGrid.h',
+ 'nsGridCell.h',
+ 'nsGridLayout2.h',
+ 'nsGridRow.h',
+ 'nsGridRowGroupLayout.h',
+ 'nsGridRowLayout.h',
+ 'nsGridRowLeafFrame.h',
+ 'nsGridRowLeafLayout.h',
+ 'nsIGridPart.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsGrid.cpp',
+ 'nsGridCell.cpp',
+ 'nsGridLayout2.cpp',
+ 'nsGridRow.cpp',
+ 'nsGridRowGroupFrame.cpp',
+ 'nsGridRowGroupLayout.cpp',
+ 'nsGridRowLayout.cpp',
+ 'nsGridRowLeafFrame.cpp',
+ 'nsGridRowLeafLayout.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '..',
+ '../../forms',
+ '../../generic',
+ '../../style',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/xul/grid/nsGrid.cpp b/layout/xul/grid/nsGrid.cpp
new file mode 100644
index 000000000..762bbfcd7
--- /dev/null
+++ b/layout/xul/grid/nsGrid.cpp
@@ -0,0 +1,1276 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGrid.h"
+#include "nsGridRowGroupLayout.h"
+#include "nsBox.h"
+#include "nsIScrollableFrame.h"
+#include "nsSprocketLayout.h"
+#include "nsGridLayout2.h"
+#include "nsGridRow.h"
+#include "nsGridCell.h"
+#include "mozilla/ReflowInput.h"
+
+/*
+The grid control expands the idea of boxes from 1 dimension to 2 dimensions.
+It works by allowing the XUL to define a collection of rows and columns and then
+stacking them on top of each other. Here is and example.
+
+Example 1:
+
+<grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row/>
+ <row/>
+ </rows>
+</grid>
+
+example 2:
+
+<grid>
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <text value="hello"/>
+ <text value="there"/>
+ </row>
+ </rows>
+</grid>
+
+example 3:
+
+<grid>
+
+<rows>
+ <row>
+ <text value="hello"/>
+ <text value="there"/>
+ </row>
+ </rows>
+
+ <columns>
+ <column>
+ <text value="Hey I'm in the column and I'm on top!"/>
+ </column>
+ <column/>
+ </columns>
+
+</grid>
+
+Usually the columns are first and the rows are second, so the rows will be drawn on top of the columns.
+You can reverse this by defining the rows first.
+Other tags are then placed in the <row> or <column> tags causing the grid to accommodate everyone.
+It does this by creating 3 things: A cellmap, a row list, and a column list. The cellmap is a 2
+dimensional array of nsGridCells. Each cell contains 2 boxes. One cell from the column list
+and one from the row list. When a cell is asked for its size it returns that smallest size it can
+be to accommodate the 2 cells. Row lists and Column lists use the same data structure: nsGridRow.
+Essentially a row and column are the same except a row goes alone the x axis and a column the y.
+To make things easier and save code everything is written in terms of the x dimension. A flag is
+passed in called "isHorizontal" that can flip the calculations to the y axis.
+
+Usually the number of cells in a row match the number of columns, but not always.
+It is possible to define 5 columns for a grid but have 10 cells in one of the rows.
+In this case 5 extra columns will be added to the column list to handle the situation.
+These are called extraColumns/Rows.
+*/
+
+using namespace mozilla;
+
+nsGrid::nsGrid():mBox(nullptr),
+ mRowsBox(nullptr),
+ mColumnsBox(nullptr),
+ mNeedsRebuild(true),
+ mRowCount(0),
+ mColumnCount(0),
+ mExtraRowCount(0),
+ mExtraColumnCount(0),
+ mMarkingDirty(false)
+{
+ MOZ_COUNT_CTOR(nsGrid);
+}
+
+nsGrid::~nsGrid()
+{
+ FreeMap();
+ MOZ_COUNT_DTOR(nsGrid);
+}
+
+/*
+ * This is called whenever something major happens in the grid. And example
+ * might be when many cells or row are added. It sets a flag signaling that
+ * all the grids caches information should be recalculated.
+ */
+void
+nsGrid::NeedsRebuild(nsBoxLayoutState& aState)
+{
+ if (mNeedsRebuild)
+ return;
+
+ // iterate through columns and rows and dirty them
+ mNeedsRebuild = true;
+
+ // find the new row and column box. They could have
+ // been changed.
+ mRowsBox = nullptr;
+ mColumnsBox = nullptr;
+ FindRowsAndColumns(&mRowsBox, &mColumnsBox);
+
+ // tell all the rows and columns they are dirty
+ DirtyRows(mRowsBox, aState);
+ DirtyRows(mColumnsBox, aState);
+}
+
+
+
+/**
+ * If we are marked for rebuild. Then build everything
+ */
+void
+nsGrid::RebuildIfNeeded()
+{
+ if (!mNeedsRebuild)
+ return;
+
+ mNeedsRebuild = false;
+
+ // find the row and columns frames
+ FindRowsAndColumns(&mRowsBox, &mColumnsBox);
+
+ // count the rows and columns
+ int32_t computedRowCount = 0;
+ int32_t computedColumnCount = 0;
+ int32_t rowCount = 0;
+ int32_t columnCount = 0;
+
+ CountRowsColumns(mRowsBox, rowCount, computedColumnCount);
+ CountRowsColumns(mColumnsBox, columnCount, computedRowCount);
+
+ // computedRowCount are the actual number of rows as determined by the
+ // columns children.
+ // computedColumnCount are the number of columns as determined by the number
+ // of rows children.
+ // We can use this information to see how many extra columns or rows we need.
+ // This can happen if there are are more children in a row that number of columns
+ // defined. Example:
+ //
+ // <columns>
+ // <column/>
+ // </columns>
+ //
+ // <rows>
+ // <row>
+ // <button/><button/>
+ // </row>
+ // </rows>
+ //
+ // computedColumnCount = 2 // for the 2 buttons in the row tag
+ // computedRowCount = 0 // there is nothing in the column tag
+ // mColumnCount = 1 // one column defined
+ // mRowCount = 1 // one row defined
+ //
+ // So in this case we need to make 1 extra column.
+ //
+
+ // Make sure to update mExtraColumnCount no matter what, since it might
+ // happen that we now have as many columns as are defined, and we wouldn't
+ // want to have a positive mExtraColumnCount hanging about in that case!
+ mExtraColumnCount = computedColumnCount - columnCount;
+ if (computedColumnCount > columnCount) {
+ columnCount = computedColumnCount;
+ }
+
+ // Same for rows.
+ mExtraRowCount = computedRowCount - rowCount;
+ if (computedRowCount > rowCount) {
+ rowCount = computedRowCount;
+ }
+
+ // build and poplulate row and columns arrays
+ mRows = BuildRows(mRowsBox, rowCount, true);
+ mColumns = BuildRows(mColumnsBox, columnCount, false);
+
+ // build and populate the cell map
+ mCellMap = BuildCellMap(rowCount, columnCount);
+
+ mRowCount = rowCount;
+ mColumnCount = columnCount;
+
+ // populate the cell map from column and row children
+ PopulateCellMap(mRows.get(), mColumns.get(), mRowCount, mColumnCount, true);
+ PopulateCellMap(mColumns.get(), mRows.get(), mColumnCount, mRowCount, false);
+}
+
+void
+nsGrid::FreeMap()
+{
+ mRows = nullptr;
+ mColumns = nullptr;
+ mCellMap = nullptr;
+ mColumnCount = 0;
+ mRowCount = 0;
+ mExtraColumnCount = 0;
+ mExtraRowCount = 0;
+ mRowsBox = nullptr;
+ mColumnsBox = nullptr;
+}
+
+/**
+ * finds the first <rows> and <columns> tags in the <grid> tag
+ */
+void
+nsGrid::FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns)
+{
+ *aRows = nullptr;
+ *aColumns = nullptr;
+
+ // find the boxes that contain our rows and columns
+ nsIFrame* child = nullptr;
+ // if we have <grid></grid> then mBox will be null (bug 125689)
+ if (mBox)
+ child = nsBox::GetChildXULBox(mBox);
+
+ while(child)
+ {
+ nsIFrame* oldBox = child;
+ nsIScrollableFrame *scrollFrame = do_QueryFrame(child);
+ if (scrollFrame) {
+ nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
+ NS_ASSERTION(scrolledFrame,"Error no scroll frame!!");
+ child = do_QueryFrame(scrolledFrame);
+ }
+
+ nsCOMPtr<nsIGridPart> monument = GetPartFromBox(child);
+ if (monument)
+ {
+ nsGridRowGroupLayout* rowGroup = monument->CastToRowGroupLayout();
+ if (rowGroup) {
+ bool isHorizontal = !nsSprocketLayout::IsXULHorizontal(child);
+ if (isHorizontal)
+ *aRows = child;
+ else
+ *aColumns = child;
+
+ if (*aRows && *aColumns)
+ return;
+ }
+ }
+
+ if (scrollFrame) {
+ child = oldBox;
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+}
+
+/**
+ * Count the number of rows and columns in the given box. aRowCount well become the actual number
+ * rows defined in the xul. aComputedColumnCount will become the number of columns by counting the number
+ * of cells in each row.
+ */
+void
+nsGrid::CountRowsColumns(nsIFrame* aRowBox, int32_t& aRowCount, int32_t& aComputedColumnCount)
+{
+ aRowCount = 0;
+ aComputedColumnCount = 0;
+ // get the rowboxes layout manager. Then ask it to do the work for us
+ if (aRowBox) {
+ nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aRowBox);
+ if (monument)
+ monument->CountRowsColumns(aRowBox, aRowCount, aComputedColumnCount);
+ }
+}
+
+
+/**
+ * Given the number of rows create nsGridRow objects for them and full them out.
+ */
+UniquePtr<nsGridRow[]>
+nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount, bool aIsHorizontal)
+{
+ // if no rows then return null
+ if (aRowCount == 0) {
+ return nullptr;
+ }
+
+ // create the array
+ UniquePtr<nsGridRow[]> row;
+
+ // only create new rows if we have to. Reuse old rows.
+ if (aIsHorizontal)
+ {
+ if (aRowCount > mRowCount) {
+ row = MakeUnique<nsGridRow[]>(aRowCount);
+ } else {
+ for (int32_t i=0; i < mRowCount; i++)
+ mRows[i].Init(nullptr, false);
+
+ row = Move(mRows);
+ }
+ } else {
+ if (aRowCount > mColumnCount) {
+ row = MakeUnique<nsGridRow[]>(aRowCount);
+ } else {
+ for (int32_t i=0; i < mColumnCount; i++)
+ mColumns[i].Init(nullptr, false);
+
+ row = Move(mColumns);
+ }
+ }
+
+ // populate it if we can. If not it will contain only dynamic columns
+ if (aBox)
+ {
+ nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aBox);
+ if (monument) {
+ monument->BuildRows(aBox, row.get());
+ }
+ }
+
+ return row;
+}
+
+
+/**
+ * Given the number of rows and columns. Build a cellmap
+ */
+UniquePtr<nsGridCell[]>
+nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns)
+{
+ int32_t size = aRows*aColumns;
+ int32_t oldsize = mRowCount*mColumnCount;
+ if (size == 0) {
+ return nullptr;
+ }
+
+ if (size > oldsize) {
+ return MakeUnique<nsGridCell[]>(size);
+ }
+
+ // clear out cellmap
+ for (int32_t i=0; i < oldsize; i++) {
+ mCellMap[i].SetBoxInRow(nullptr);
+ mCellMap[i].SetBoxInColumn(nullptr);
+ }
+ return Move(mCellMap);
+}
+
+/**
+ * Run through all the cells in the rows and columns and populate then with 2 cells. One from the row and one
+ * from the column
+ */
+void
+nsGrid::PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal)
+{
+ if (!aRows)
+ return;
+
+ // look through the columns
+ int32_t j = 0;
+
+ for(int32_t i=0; i < aRowCount; i++)
+ {
+ nsIFrame* child = nullptr;
+ nsGridRow* row = &aRows[i];
+
+ // skip bogus rows. They have no cells
+ if (row->mIsBogus)
+ continue;
+
+ child = row->mBox;
+ if (child) {
+ child = nsBox::GetChildXULBox(child);
+
+ j = 0;
+
+ while(child && j < aColumnCount)
+ {
+ // skip bogus column. They have no cells
+ nsGridRow* column = &aColumns[j];
+ if (column->mIsBogus)
+ {
+ j++;
+ continue;
+ }
+
+ if (aIsHorizontal)
+ GetCellAt(j,i)->SetBoxInRow(child);
+ else
+ GetCellAt(i,j)->SetBoxInColumn(child);
+
+ child = nsBox::GetNextXULBox(child);
+
+ j++;
+ }
+ }
+ }
+}
+
+/**
+ * Run through the rows in the given box and mark them dirty so they
+ * will get recalculated and get a layout.
+ */
+void
+nsGrid::DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState)
+{
+ // make sure we prevent others from dirtying things.
+ mMarkingDirty = true;
+
+ // if the box is a grid part have it recursively hand it.
+ if (aRowBox) {
+ nsCOMPtr<nsIGridPart> part = GetPartFromBox(aRowBox);
+ if (part)
+ part->DirtyRows(aRowBox, aState);
+ }
+
+ mMarkingDirty = false;
+}
+
+nsGridRow*
+nsGrid::GetColumnAt(int32_t aIndex, bool aIsHorizontal)
+{
+ return GetRowAt(aIndex, !aIsHorizontal);
+}
+
+nsGridRow*
+nsGrid::GetRowAt(int32_t aIndex, bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ if (aIsHorizontal) {
+ NS_ASSERTION(aIndex < mRowCount && aIndex >= 0, "Index out of range");
+ return &mRows[aIndex];
+ } else {
+ NS_ASSERTION(aIndex < mColumnCount && aIndex >= 0, "Index out of range");
+ return &mColumns[aIndex];
+ }
+}
+
+nsGridCell*
+nsGrid::GetCellAt(int32_t aX, int32_t aY)
+{
+ RebuildIfNeeded();
+
+ NS_ASSERTION(aY < mRowCount && aY >= 0, "Index out of range");
+ NS_ASSERTION(aX < mColumnCount && aX >= 0, "Index out of range");
+ return &mCellMap[aY*mColumnCount+aX];
+}
+
+int32_t
+nsGrid::GetExtraColumnCount(bool aIsHorizontal)
+{
+ return GetExtraRowCount(!aIsHorizontal);
+}
+
+int32_t
+nsGrid::GetExtraRowCount(bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ if (aIsHorizontal)
+ return mExtraRowCount;
+ else
+ return mExtraColumnCount;
+}
+
+
+/**
+ * These methods return the preferred, min, max sizes for a given row index.
+ * aIsHorizontal if aIsHorizontal is true. If you pass false you will get the inverse.
+ * As if you called GetPrefColumnSize(aState, index, aPref)
+ */
+nsSize
+nsGrid::GetPrefRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
+{
+ nsSize size(0,0);
+ if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
+ return size;
+
+ nscoord height = GetPrefRowHeight(aState, aRowIndex, aIsHorizontal);
+ SetLargestSize(size, height, aIsHorizontal);
+
+ return size;
+}
+
+nsSize
+nsGrid::GetMinRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
+{
+ nsSize size(0,0);
+ if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
+ return size;
+
+ nscoord height = GetMinRowHeight(aState, aRowIndex, aIsHorizontal);
+ SetLargestSize(size, height, aIsHorizontal);
+
+ return size;
+}
+
+nsSize
+nsGrid::GetMaxRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
+{
+ nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
+ if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
+ return size;
+
+ nscoord height = GetMaxRowHeight(aState, aRowIndex, aIsHorizontal);
+ SetSmallestSize(size, height, aIsHorizontal);
+
+ return size;
+}
+
+// static
+nsIGridPart*
+nsGrid::GetPartFromBox(nsIFrame* aBox)
+{
+ if (!aBox)
+ return nullptr;
+
+ nsBoxLayout* layout = aBox->GetXULLayoutManager();
+ return layout ? layout->AsGridPart() : nullptr;
+}
+
+nsMargin
+nsGrid::GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal)
+{
+ nsMargin margin(0,0,0,0);
+ // walk the boxes parent chain getting the border/padding/margin of our parent rows
+
+ // first get the layour manager
+ nsIGridPart* part = GetPartFromBox(aBox);
+ if (part)
+ margin = part->GetTotalMargin(aBox, aIsHorizontal);
+
+ return margin;
+}
+
+/**
+ * The first and last rows can be affected by <rows> tags with borders or margin
+ * gets first and last rows and their indexes.
+ * If it fails because there are no rows then:
+ * FirstRow is nullptr
+ * LastRow is nullptr
+ * aFirstIndex = -1
+ * aLastIndex = -1
+ */
+void
+nsGrid::GetFirstAndLastRow(int32_t& aFirstIndex,
+ int32_t& aLastIndex,
+ nsGridRow*& aFirstRow,
+ nsGridRow*& aLastRow,
+ bool aIsHorizontal)
+{
+ aFirstRow = nullptr;
+ aLastRow = nullptr;
+ aFirstIndex = -1;
+ aLastIndex = -1;
+
+ int32_t count = GetRowCount(aIsHorizontal);
+
+ if (count == 0)
+ return;
+
+
+ // We could have collapsed columns either before or after our index.
+ // they should not count. So if we are the 5th row and the first 4 are
+ // collaped we become the first row. Or if we are the 9th row and
+ // 10 up to the last row are collapsed we then become the last.
+
+ // see if we are first
+ int32_t i;
+ for (i=0; i < count; i++)
+ {
+ nsGridRow* row = GetRowAt(i,aIsHorizontal);
+ if (!row->IsXULCollapsed()) {
+ aFirstIndex = i;
+ aFirstRow = row;
+ break;
+ }
+ }
+
+ // see if we are last
+ for (i=count-1; i >= 0; i--)
+ {
+ nsGridRow* row = GetRowAt(i,aIsHorizontal);
+ if (!row->IsXULCollapsed()) {
+ aLastIndex = i;
+ aLastRow = row;
+ break;
+ }
+
+ }
+}
+
+/**
+ * A row can have a top and bottom offset. Usually this is just the top and bottom border/padding.
+ * However if the row is the first or last it could be affected by the fact a column or columns could
+ * have a top or bottom margin.
+ */
+void
+nsGrid::GetRowOffsets(int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal)
+{
+
+ RebuildIfNeeded();
+
+ nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
+
+ if (row->IsOffsetSet())
+ {
+ aTop = row->mTop;
+ aBottom = row->mBottom;
+ return;
+ }
+
+ // first get the rows top and bottom border and padding
+ nsIFrame* box = row->GetBox();
+
+ // add up all the padding
+ nsMargin margin(0,0,0,0);
+ nsMargin border(0,0,0,0);
+ nsMargin padding(0,0,0,0);
+ nsMargin totalBorderPadding(0,0,0,0);
+ nsMargin totalMargin(0,0,0,0);
+
+ // if there is a box and it's not bogus take its
+ // borders padding into account
+ if (box && !row->mIsBogus)
+ {
+ if (!box->IsXULCollapsed())
+ {
+ // get real border and padding. GetXULBorderAndPadding
+ // is redefined on nsGridRowLeafFrame. If we called it here
+ // we would be in finite recurson.
+ box->GetXULBorder(border);
+ box->GetXULPadding(padding);
+
+ totalBorderPadding += border;
+ totalBorderPadding += padding;
+ }
+
+ // if we are the first or last row
+ // take into account <rows> tags around us
+ // that could have borders or margins.
+ // fortunately they only affect the first
+ // and last row inside the <rows> tag
+
+ totalMargin = GetBoxTotalMargin(box, aIsHorizontal);
+ }
+
+ if (aIsHorizontal) {
+ row->mTop = totalBorderPadding.top;
+ row->mBottom = totalBorderPadding.bottom;
+ row->mTopMargin = totalMargin.top;
+ row->mBottomMargin = totalMargin.bottom;
+ } else {
+ row->mTop = totalBorderPadding.left;
+ row->mBottom = totalBorderPadding.right;
+ row->mTopMargin = totalMargin.left;
+ row->mBottomMargin = totalMargin.right;
+ }
+
+ // if we are the first or last row take into account the top and bottom borders
+ // of each columns.
+
+ // If we are the first row then get the largest top border/padding in
+ // our columns. If that's larger than the rows top border/padding use it.
+
+ // If we are the last row then get the largest bottom border/padding in
+ // our columns. If that's larger than the rows bottom border/padding use it.
+ int32_t firstIndex = 0;
+ int32_t lastIndex = 0;
+ nsGridRow* firstRow = nullptr;
+ nsGridRow* lastRow = nullptr;
+ GetFirstAndLastRow(firstIndex, lastIndex, firstRow, lastRow, aIsHorizontal);
+
+ if (aIndex == firstIndex || aIndex == lastIndex) {
+ nscoord maxTop = 0;
+ nscoord maxBottom = 0;
+
+ // run through the columns. Look at each column
+ // pick the largest top border or bottom border
+ int32_t count = GetColumnCount(aIsHorizontal);
+
+ for (int32_t i=0; i < count; i++)
+ {
+ nsMargin totalChildBorderPadding(0,0,0,0);
+
+ nsGridRow* column = GetColumnAt(i,aIsHorizontal);
+ nsIFrame* box = column->GetBox();
+
+ if (box)
+ {
+ // ignore collapsed children
+ if (!box->IsXULCollapsed())
+ {
+ // include the margin of the columns. To the row
+ // at this point border/padding and margins all added
+ // up to more needed space.
+ margin = GetBoxTotalMargin(box, !aIsHorizontal);
+ // get real border and padding. GetXULBorderAndPadding
+ // is redefined on nsGridRowLeafFrame. If we called it here
+ // we would be in finite recurson.
+ box->GetXULBorder(border);
+ box->GetXULPadding(padding);
+ totalChildBorderPadding += border;
+ totalChildBorderPadding += padding;
+ totalChildBorderPadding += margin;
+ }
+
+ nscoord top;
+ nscoord bottom;
+
+ // pick the largest top margin
+ if (aIndex == firstIndex) {
+ if (aIsHorizontal) {
+ top = totalChildBorderPadding.top;
+ } else {
+ top = totalChildBorderPadding.left;
+ }
+ if (top > maxTop)
+ maxTop = top;
+ }
+
+ // pick the largest bottom margin
+ if (aIndex == lastIndex) {
+ if (aIsHorizontal) {
+ bottom = totalChildBorderPadding.bottom;
+ } else {
+ bottom = totalChildBorderPadding.right;
+ }
+ if (bottom > maxBottom)
+ maxBottom = bottom;
+ }
+
+ }
+
+ // If the biggest top border/padding the columns is larger than this rows top border/padding
+ // the use it.
+ if (aIndex == firstIndex) {
+ if (maxTop > (row->mTop + row->mTopMargin))
+ row->mTop = maxTop - row->mTopMargin;
+ }
+
+ // If the biggest bottom border/padding the columns is larger than this rows bottom border/padding
+ // the use it.
+ if (aIndex == lastIndex) {
+ if (maxBottom > (row->mBottom + row->mBottomMargin))
+ row->mBottom = maxBottom - row->mBottomMargin;
+ }
+ }
+ }
+
+ aTop = row->mTop;
+ aBottom = row->mBottom;
+}
+
+/**
+ * These methods return the preferred, min, max coord for a given row index if
+ * aIsHorizontal is true. If you pass false you will get the inverse.
+ * As if you called GetPrefColumnHeight(aState, index, aPref).
+ */
+nscoord
+nsGrid::GetPrefRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
+
+ if (row->IsXULCollapsed())
+ return 0;
+
+ if (row->IsPrefSet())
+ return row->mPref;
+
+ nsIFrame* box = row->mBox;
+
+ // set in CSS?
+ if (box)
+ {
+ bool widthSet, heightSet;
+ nsSize cssSize(-1, -1);
+ nsIFrame::AddXULPrefSize(box, cssSize, widthSet, heightSet);
+
+ row->mPref = GET_HEIGHT(cssSize, aIsHorizontal);
+
+ // yep do nothing.
+ if (row->mPref != -1)
+ return row->mPref;
+ }
+
+ // get the offsets so they are cached.
+ nscoord top;
+ nscoord bottom;
+ GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
+
+ // is the row bogus? If so then just ask it for its size
+ // it should not be affected by cells in the grid.
+ if (row->mIsBogus)
+ {
+ nsSize size(0,0);
+ if (box)
+ {
+ size = box->GetXULPrefSize(aState);
+ nsBox::AddMargin(box, size);
+ nsGridLayout2::AddOffset(box, size);
+ }
+
+ row->mPref = GET_HEIGHT(size, aIsHorizontal);
+ return row->mPref;
+ }
+
+ nsSize size(0,0);
+
+ nsGridCell* child;
+
+ int32_t count = GetColumnCount(aIsHorizontal);
+
+ for (int32_t i=0; i < count; i++)
+ {
+ if (aIsHorizontal)
+ child = GetCellAt(i,aIndex);
+ else
+ child = GetCellAt(aIndex,i);
+
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ nsSize childSize = child->GetXULPrefSize(aState);
+
+ nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
+ }
+ }
+
+ row->mPref = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
+
+ return row->mPref;
+}
+
+nscoord
+nsGrid::GetMinRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
+
+ if (row->IsXULCollapsed())
+ return 0;
+
+ if (row->IsMinSet())
+ return row->mMin;
+
+ nsIFrame* box = row->mBox;
+
+ // set in CSS?
+ if (box) {
+ bool widthSet, heightSet;
+ nsSize cssSize(-1, -1);
+ nsIFrame::AddXULMinSize(aState, box, cssSize, widthSet, heightSet);
+
+ row->mMin = GET_HEIGHT(cssSize, aIsHorizontal);
+
+ // yep do nothing.
+ if (row->mMin != -1)
+ return row->mMin;
+ }
+
+ // get the offsets so they are cached.
+ nscoord top;
+ nscoord bottom;
+ GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
+
+ // is the row bogus? If so then just ask it for its size
+ // it should not be affected by cells in the grid.
+ if (row->mIsBogus)
+ {
+ nsSize size(0,0);
+ if (box) {
+ size = box->GetXULPrefSize(aState);
+ nsBox::AddMargin(box, size);
+ nsGridLayout2::AddOffset(box, size);
+ }
+
+ row->mMin = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
+ return row->mMin;
+ }
+
+ nsSize size(0,0);
+
+ nsGridCell* child;
+
+ int32_t count = GetColumnCount(aIsHorizontal);
+
+ for (int32_t i=0; i < count; i++)
+ {
+ if (aIsHorizontal)
+ child = GetCellAt(i,aIndex);
+ else
+ child = GetCellAt(aIndex,i);
+
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ nsSize childSize = child->GetXULMinSize(aState);
+
+ nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
+ }
+ }
+
+ row->mMin = GET_HEIGHT(size, aIsHorizontal);
+
+ return row->mMin;
+}
+
+nscoord
+nsGrid::GetMaxRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
+
+ if (row->IsXULCollapsed())
+ return 0;
+
+ if (row->IsMaxSet())
+ return row->mMax;
+
+ nsIFrame* box = row->mBox;
+
+ // set in CSS?
+ if (box) {
+ bool widthSet, heightSet;
+ nsSize cssSize(-1, -1);
+ nsIFrame::AddXULMaxSize(box, cssSize, widthSet, heightSet);
+
+ row->mMax = GET_HEIGHT(cssSize, aIsHorizontal);
+
+ // yep do nothing.
+ if (row->mMax != -1)
+ return row->mMax;
+ }
+
+ // get the offsets so they are cached.
+ nscoord top;
+ nscoord bottom;
+ GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
+
+ // is the row bogus? If so then just ask it for its size
+ // it should not be affected by cells in the grid.
+ if (row->mIsBogus)
+ {
+ nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
+ if (box) {
+ size = box->GetXULPrefSize(aState);
+ nsBox::AddMargin(box, size);
+ nsGridLayout2::AddOffset(box, size);
+ }
+
+ row->mMax = GET_HEIGHT(size, aIsHorizontal);
+ return row->mMax;
+ }
+
+ nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
+
+ nsGridCell* child;
+
+ int32_t count = GetColumnCount(aIsHorizontal);
+
+ for (int32_t i=0; i < count; i++)
+ {
+ if (aIsHorizontal)
+ child = GetCellAt(i,aIndex);
+ else
+ child = GetCellAt(aIndex,i);
+
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ nsSize min = child->GetXULMinSize(aState);
+ nsSize childSize = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(aState));
+ nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
+ }
+ }
+
+ row->mMax = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
+
+ return row->mMax;
+}
+
+bool
+nsGrid::IsGrid(nsIFrame* aBox)
+{
+ nsIGridPart* part = GetPartFromBox(aBox);
+ if (!part)
+ return false;
+
+ nsGridLayout2* grid = part->CastToGridLayout();
+
+ if (grid)
+ return true;
+
+ return false;
+}
+
+/**
+ * This get the flexibilty of the row at aIndex. It's not trivial. There are a few
+ * things we need to look at. Specifically we need to see if any <rows> or <columns>
+ * tags are around us. Their flexibilty will affect ours.
+ */
+nscoord
+nsGrid::GetRowFlex(int32_t aIndex, bool aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
+
+ if (row->IsFlexSet())
+ return row->mFlex;
+
+ nsIFrame* box = row->mBox;
+ row->mFlex = 0;
+
+ if (box) {
+
+ // We need our flex but a inflexible row could be around us. If so
+ // neither are we. However if its the row tag just inside the grid it won't
+ // affect us. We need to do this for this case:
+ // <grid>
+ // <rows>
+ // <rows> // this is not flexible. So our children should not be flexible
+ // <row flex="1"/>
+ // <row flex="1"/>
+ // </rows>
+ // <row/>
+ // </rows>
+ // </grid>
+ //
+ // or..
+ //
+ // <grid>
+ // <rows>
+ // <rows> // this is not flexible. So our children should not be flexible
+ // <rows flex="1">
+ // <row flex="1"/>
+ // <row flex="1"/>
+ // </rows>
+ // <row/>
+ // </rows>
+ // </row>
+ // </grid>
+
+
+ // So here is how it looks
+ //
+ // <grid>
+ // <rows> // parentsParent
+ // <rows> // parent
+ // <row flex="1"/>
+ // <row flex="1"/>
+ // </rows>
+ // <row/>
+ // </rows>
+ // </grid>
+
+ // so the answer is simple: 1) Walk our parent chain. 2) If we find
+ // someone who is not flexible and they aren't the rows immediately in
+ // the grid. 3) Then we are not flexible
+
+ box = GetScrollBox(box);
+ nsIFrame* parent = nsBox::GetParentXULBox(box);
+ nsIFrame* parentsParent=nullptr;
+
+ while(parent)
+ {
+ parent = GetScrollBox(parent);
+ parentsParent = nsBox::GetParentXULBox(parent);
+
+ // if our parents parent is not a grid
+ // the get its flex. If its 0 then we are
+ // not flexible.
+ if (parentsParent) {
+ if (!IsGrid(parentsParent)) {
+ nscoord flex = parent->GetXULFlex();
+ nsIFrame::AddXULFlex(parent, flex);
+ if (flex == 0) {
+ row->mFlex = 0;
+ return row->mFlex;
+ }
+ } else
+ break;
+ }
+
+ parent = parentsParent;
+ }
+
+ // get the row flex.
+ row->mFlex = box->GetXULFlex();
+ nsIFrame::AddXULFlex(box, row->mFlex);
+ }
+
+ return row->mFlex;
+}
+
+void
+nsGrid::SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal)
+{
+ if (aIsHorizontal) {
+ if (aSize.height < aHeight)
+ aSize.height = aHeight;
+ } else {
+ if (aSize.width < aHeight)
+ aSize.width = aHeight;
+ }
+}
+
+void
+nsGrid::SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal)
+{
+ if (aIsHorizontal) {
+ if (aSize.height > aHeight)
+ aSize.height = aHeight;
+ } else {
+ if (aSize.width < aHeight)
+ aSize.width = aHeight;
+ }
+}
+
+int32_t
+nsGrid::GetRowCount(int32_t aIsHorizontal)
+{
+ RebuildIfNeeded();
+
+ if (aIsHorizontal)
+ return mRowCount;
+ else
+ return mColumnCount;
+}
+
+int32_t
+nsGrid::GetColumnCount(int32_t aIsHorizontal)
+{
+ return GetRowCount(!aIsHorizontal);
+}
+
+/*
+ * A cell in the given row or columns at the given index has had a child added or removed
+ */
+void
+nsGrid::CellAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
+{
+ // TBD see if the cell will fit in our current row. If it will
+ // just add it in.
+ // but for now rebuild everything.
+ if (mMarkingDirty)
+ return;
+
+ NeedsRebuild(aState);
+}
+
+/**
+ * A row or columns at the given index had been added or removed
+ */
+void
+nsGrid::RowAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
+{
+ // TBD see if we have extra room in the table and just add the new row in
+ // for now rebuild the world
+ if (mMarkingDirty)
+ return;
+
+ NeedsRebuild(aState);
+}
+
+/*
+ * Scrollframes are tranparent. If this is given a scrollframe is will return the
+ * frame inside. If there is no scrollframe it does nothing.
+ */
+nsIFrame*
+nsGrid::GetScrolledBox(nsIFrame* aChild)
+{
+ // first see if it is a scrollframe. If so walk down into it and get the scrolled child
+ nsIScrollableFrame *scrollFrame = do_QueryFrame(aChild);
+ if (scrollFrame) {
+ nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
+ NS_ASSERTION(scrolledFrame,"Error no scroll frame!!");
+ return scrolledFrame;
+ }
+
+ return aChild;
+}
+
+/*
+ * Scrollframes are tranparent. If this is given a child in a scrollframe is will return the
+ * scrollframe ourside it. If there is no scrollframe it does nothing.
+ */
+nsIFrame*
+nsGrid::GetScrollBox(nsIFrame* aChild)
+{
+ if (!aChild)
+ return nullptr;
+
+ // get parent
+ nsIFrame* parent = nsBox::GetParentXULBox(aChild);
+
+ // walk up until we find a scrollframe or a part
+ // if it's a scrollframe return it.
+ // if it's a parent then the child passed does not
+ // have a scroll frame immediately wrapped around it.
+ while (parent) {
+ nsIScrollableFrame *scrollFrame = do_QueryFrame(parent);
+ // scrollframe? Yep return it.
+ if (scrollFrame)
+ return parent;
+
+ nsCOMPtr<nsIGridPart> parentGridRow = GetPartFromBox(parent);
+ // if a part then just return the child
+ if (parentGridRow)
+ break;
+
+ parent = nsBox::GetParentXULBox(parent);
+ }
+
+ return aChild;
+}
+
+
+
+#ifdef DEBUG_grid
+void
+nsGrid::PrintCellMap()
+{
+
+ printf("-----Columns------\n");
+ for (int x=0; x < mColumnCount; x++)
+ {
+
+ nsGridRow* column = GetColumnAt(x);
+ printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax);
+ }
+
+ printf("\n-----Rows------\n");
+ for (x=0; x < mRowCount; x++)
+ {
+ nsGridRow* column = GetRowAt(x);
+ printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax);
+ }
+
+ printf("\n");
+
+}
+#endif
diff --git a/layout/xul/grid/nsGrid.h b/layout/xul/grid/nsGrid.h
new file mode 100644
index 000000000..d8726a946
--- /dev/null
+++ b/layout/xul/grid/nsGrid.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGrid_h___
+#define nsGrid_h___
+
+#include "nsStackLayout.h"
+#include "nsIGridPart.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+
+class nsBoxLayoutState;
+class nsGridCell;
+
+//#define DEBUG_grid 1
+
+/**
+ * The grid data structure, i.e., the grid cellmap.
+ */
+class nsGrid
+{
+public:
+ nsGrid();
+ ~nsGrid();
+
+ nsGridRow* GetColumnAt(int32_t aIndex, bool aIsHorizontal = true);
+ nsGridRow* GetRowAt(int32_t aIndex, bool aIsHorizontal = true);
+ nsGridCell* GetCellAt(int32_t aX, int32_t aY);
+
+ void NeedsRebuild(nsBoxLayoutState& aBoxLayoutState);
+ void RebuildIfNeeded();
+
+ // For all the methods taking an aIsHorizontal parameter:
+ // * When aIsHorizontal is true, the words "rows" and (for
+ // GetColumnCount) "columns" refer to their normal meanings.
+ // * When aIsHorizontal is false, the meanings are flipped.
+ // FIXME: Maybe eliminate GetColumnCount and change aIsHorizontal to
+ // aIsRows? (Calling it horizontal doesn't really make sense because
+ // row groups and columns have vertical orientation, whereas column
+ // groups and rows are horizontal.)
+
+ nsSize GetPrefRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ nsSize GetMinRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ nsSize GetMaxRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ nscoord GetRowFlex(int32_t aRowIndex, bool aIsHorizontal = true);
+
+ nscoord GetPrefRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ nscoord GetMinRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ nscoord GetMaxRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true);
+ void GetRowOffsets(int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal = true);
+
+ void RowAddedOrRemoved(nsBoxLayoutState& aBoxLayoutState, int32_t aIndex, bool aIsHorizontal = true);
+ void CellAddedOrRemoved(nsBoxLayoutState& aBoxLayoutState, int32_t aIndex, bool aIsHorizontal = true);
+ void DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState);
+#ifdef DEBUG_grid
+ void PrintCellMap();
+#endif
+ int32_t GetExtraColumnCount(bool aIsHorizontal = true);
+ int32_t GetExtraRowCount(bool aIsHorizontal = true);
+
+// accessors
+ void SetBox(nsIFrame* aBox) { mBox = aBox; }
+ nsIFrame* GetBox() { return mBox; }
+ nsIFrame* GetRowsBox() { return mRowsBox; }
+ nsIFrame* GetColumnsBox() { return mColumnsBox; }
+ int32_t GetRowCount(int32_t aIsHorizontal = true);
+ int32_t GetColumnCount(int32_t aIsHorizontal = true);
+
+ static nsIFrame* GetScrolledBox(nsIFrame* aChild);
+ static nsIFrame* GetScrollBox(nsIFrame* aChild);
+ static nsIGridPart* GetPartFromBox(nsIFrame* aBox);
+ void GetFirstAndLastRow(int32_t& aFirstIndex,
+ int32_t& aLastIndex,
+ nsGridRow*& aFirstRow,
+ nsGridRow*& aLastRow,
+ bool aIsHorizontal);
+
+private:
+
+ nsMargin GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal = true);
+
+ void FreeMap();
+ void FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns);
+ mozilla::UniquePtr<nsGridRow[]> BuildRows(nsIFrame* aBox, int32_t aSize,
+ bool aIsHorizontal = true);
+ mozilla::UniquePtr<nsGridCell[]> BuildCellMap(int32_t aRows, int32_t aColumns);
+ void PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal = true);
+ void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount);
+ void SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal = true);
+ void SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal = true);
+ bool IsGrid(nsIFrame* aBox);
+
+ // the box that implement the <grid> tag
+ nsIFrame* mBox;
+
+ // an array of row object
+ mozilla::UniquePtr<nsGridRow[]> mRows;
+
+ // an array of columns objects.
+ mozilla::UniquePtr<nsGridRow[]> mColumns;
+
+ // the first in the <grid> that implements the <rows> tag.
+ nsIFrame* mRowsBox;
+
+ // the first in the <grid> that implements the <columns> tag.
+ nsIFrame* mColumnsBox;
+
+ // a flag that is false tells us to rebuild the who grid
+ bool mNeedsRebuild;
+
+ // number of rows and columns as defined by the XUL
+ int32_t mRowCount;
+ int32_t mColumnCount;
+
+ // number of rows and columns that are implied but not
+ // explicitly defined int he XUL
+ int32_t mExtraRowCount;
+ int32_t mExtraColumnCount;
+
+ // x,y array of cells in the rows and columns
+ mozilla::UniquePtr<nsGridCell[]> mCellMap;
+
+ // a flag that when true suppresses all other MarkDirties. This
+ // prevents lots of extra work being done.
+ bool mMarkingDirty;
+};
+
+#endif
+
diff --git a/layout/xul/grid/nsGridCell.cpp b/layout/xul/grid/nsGridCell.cpp
new file mode 100644
index 000000000..c54256297
--- /dev/null
+++ b/layout/xul/grid/nsGridCell.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridCell.h"
+#include "nsFrame.h"
+#include "nsBox.h"
+#include "nsGridLayout2.h"
+
+
+nsGridCell::nsGridCell():mBoxInColumn(nullptr),mBoxInRow(nullptr)
+{
+ MOZ_COUNT_CTOR(nsGridCell);
+}
+
+nsGridCell::~nsGridCell()
+{
+ MOZ_COUNT_DTOR(nsGridCell);
+}
+
+nsSize
+nsGridCell::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ nsSize sum(0,0);
+
+ // take our 2 children and add them up.
+ // we are as wide as the widest child plus its left offset
+ // we are tall as the tallest child plus its top offset
+
+ if (mBoxInColumn) {
+ nsSize pref = mBoxInColumn->GetXULPrefSize(aState);
+
+ nsBox::AddMargin(mBoxInColumn, pref);
+ nsGridLayout2::AddOffset(mBoxInColumn, pref);
+
+ nsBoxLayout::AddLargestSize(sum, pref);
+ }
+
+ if (mBoxInRow) {
+ nsSize pref = mBoxInRow->GetXULPrefSize(aState);
+
+ nsBox::AddMargin(mBoxInRow, pref);
+ nsGridLayout2::AddOffset(mBoxInRow, pref);
+
+ nsBoxLayout::AddLargestSize(sum, pref);
+ }
+
+ return sum;
+}
+
+nsSize
+nsGridCell::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ nsSize sum(0, 0);
+
+ // take our 2 children and add them up.
+ // we are as wide as the widest child plus its left offset
+ // we are tall as the tallest child plus its top offset
+
+ if (mBoxInColumn) {
+ nsSize min = mBoxInColumn->GetXULMinSize(aState);
+
+ nsBox::AddMargin(mBoxInColumn, min);
+ nsGridLayout2::AddOffset(mBoxInColumn, min);
+
+ nsBoxLayout::AddLargestSize(sum, min);
+ }
+
+ if (mBoxInRow) {
+ nsSize min = mBoxInRow->GetXULMinSize(aState);
+
+ nsBox::AddMargin(mBoxInRow, min);
+ nsGridLayout2::AddOffset(mBoxInRow, min);
+
+ nsBoxLayout::AddLargestSize(sum, min);
+ }
+
+ return sum;
+}
+
+nsSize
+nsGridCell::GetXULMaxSize(nsBoxLayoutState& aState)
+{
+ nsSize sum(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+
+ // take our 2 children and add them up.
+ // we are as wide as the smallest child plus its left offset
+ // we are tall as the shortest child plus its top offset
+
+ if (mBoxInColumn) {
+ nsSize max = mBoxInColumn->GetXULMaxSize(aState);
+
+ nsBox::AddMargin(mBoxInColumn, max);
+ nsGridLayout2::AddOffset(mBoxInColumn, max);
+
+ nsBoxLayout::AddSmallestSize(sum, max);
+ }
+
+ if (mBoxInRow) {
+ nsSize max = mBoxInRow->GetXULMaxSize(aState);
+
+ nsBox::AddMargin(mBoxInRow, max);
+ nsGridLayout2::AddOffset(mBoxInRow, max);
+
+ nsBoxLayout::AddSmallestSize(sum, max);
+ }
+
+ return sum;
+}
+
+
+bool
+nsGridCell::IsXULCollapsed()
+{
+ return ((mBoxInColumn && mBoxInColumn->IsXULCollapsed()) ||
+ (mBoxInRow && mBoxInRow->IsXULCollapsed()));
+}
+
+
diff --git a/layout/xul/grid/nsGridCell.h b/layout/xul/grid/nsGridCell.h
new file mode 100644
index 000000000..eca652776
--- /dev/null
+++ b/layout/xul/grid/nsGridCell.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsGridCell_h___
+#define nsGridCell_h___
+
+#include "mozilla/Attributes.h"
+
+class nsBoxLayoutState;
+struct nsSize;
+class nsIFrame;
+
+/*
+ * Grid cell is what makes up the cellmap in the grid. Each GridCell contains
+ * 2 pointers. One to the matching box in the columns and one to the matching box
+ * in the rows. Remember that you can put content in both rows and columns.
+ * When asked for preferred/min/max sizes it works like a stack and takes the
+ * biggest sizes.
+ */
+
+class nsGridCell final
+{
+public:
+ nsGridCell();
+ ~nsGridCell();
+
+ nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState);
+ nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState);
+ nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState);
+ bool IsXULCollapsed();
+
+// accessors
+ nsIFrame* GetBoxInColumn() { return mBoxInColumn; }
+ nsIFrame* GetBoxInRow() { return mBoxInRow; }
+ void SetBoxInRow(nsIFrame* aBox) { mBoxInRow = aBox; }
+ void SetBoxInColumn(nsIFrame* aBox) { mBoxInColumn = aBox; }
+
+private:
+ nsIFrame* mBoxInColumn;
+ nsIFrame* mBoxInRow;
+};
+
+#endif
+
diff --git a/layout/xul/grid/nsGridLayout2.cpp b/layout/xul/grid/nsGridLayout2.cpp
new file mode 100644
index 000000000..75408dce3
--- /dev/null
+++ b/layout/xul/grid/nsGridLayout2.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridLayout2.h"
+#include "nsGridRowGroupLayout.h"
+#include "nsGridRow.h"
+#include "nsBox.h"
+#include "nsIScrollableFrame.h"
+#include "nsSprocketLayout.h"
+#include "mozilla/ReflowInput.h"
+
+nsresult
+NS_NewGridLayout2( nsIPresShell* aPresShell, nsBoxLayout** aNewLayout)
+{
+ *aNewLayout = new nsGridLayout2(aPresShell);
+ NS_IF_ADDREF(*aNewLayout);
+
+ return NS_OK;
+
+}
+
+nsGridLayout2::nsGridLayout2(nsIPresShell* aPresShell):nsStackLayout()
+{
+}
+
+nsGridLayout2::~nsGridLayout2()
+{
+}
+
+// static
+void
+nsGridLayout2::AddOffset(nsIFrame* aChild, nsSize& aSize)
+{
+ nsMargin offset;
+ GetOffset(aChild, offset);
+ aSize.width += offset.left;
+ aSize.height += offset.top;
+}
+
+NS_IMETHODIMP
+nsGridLayout2::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ // XXX This should be set a better way!
+ mGrid.SetBox(aBox);
+ NS_ASSERTION(aBox->GetXULLayoutManager() == this, "setting incorrect box");
+
+ nsresult rv = nsStackLayout::XULLayout(aBox, aBoxLayoutState);
+#ifdef DEBUG_grid
+ mGrid.PrintCellMap();
+#endif
+ return rv;
+}
+
+void
+nsGridLayout2::IntrinsicISizesDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsStackLayout::IntrinsicISizesDirty(aBox, aBoxLayoutState);
+ // XXXldb We really don't need to do all the work that NeedsRebuild
+ // does; we just need to mark intrinsic widths dirty on the
+ // (row/column)(s/-groups).
+ mGrid.NeedsRebuild(aBoxLayoutState);
+}
+
+nsGrid*
+nsGridLayout2::GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor)
+{
+ // XXX This should be set a better way!
+ mGrid.SetBox(aBox);
+ NS_ASSERTION(aBox->GetXULLayoutManager() == this, "setting incorrect box");
+ return &mGrid;
+}
+
+void
+nsGridLayout2::AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal)
+{
+ nscoord& size = GET_WIDTH(aSize, aIsHorizontal);
+
+ if (size != NS_INTRINSICSIZE) {
+ if (aSize2 == NS_INTRINSICSIZE)
+ size = NS_INTRINSICSIZE;
+ else
+ size += aSize2;
+ }
+}
+
+nsSize
+nsGridLayout2::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize minSize = nsStackLayout::GetXULMinSize(aBox, aState);
+
+ // if there are no <rows> tags that will sum up our columns,
+ // sum up our columns here.
+ nsSize total(0,0);
+ nsIFrame* rowsBox = mGrid.GetRowsBox();
+ nsIFrame* columnsBox = mGrid.GetColumnsBox();
+ if (!rowsBox || !columnsBox) {
+ if (!rowsBox) {
+ // max height is the sum of our rows
+ int32_t rows = mGrid.GetRowCount();
+ for (int32_t i=0; i < rows; i++)
+ {
+ nscoord height = mGrid.GetMinRowHeight(aState, i, true);
+ AddWidth(total, height, false); // AddHeight
+ }
+ }
+
+ if (!columnsBox) {
+ // max height is the sum of our rows
+ int32_t columns = mGrid.GetColumnCount();
+ for (int32_t i=0; i < columns; i++)
+ {
+ nscoord width = mGrid.GetMinRowHeight(aState, i, false);
+ AddWidth(total, width, true); // AddWidth
+ }
+ }
+
+ AddMargin(aBox, total);
+ AddOffset(aBox, total);
+ AddLargestSize(minSize, total);
+ }
+
+ return minSize;
+}
+
+nsSize
+nsGridLayout2::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize pref = nsStackLayout::GetXULPrefSize(aBox, aState);
+
+ // if there are no <rows> tags that will sum up our columns,
+ // sum up our columns here.
+ nsSize total(0,0);
+ nsIFrame* rowsBox = mGrid.GetRowsBox();
+ nsIFrame* columnsBox = mGrid.GetColumnsBox();
+ if (!rowsBox || !columnsBox) {
+ if (!rowsBox) {
+ // max height is the sum of our rows
+ int32_t rows = mGrid.GetRowCount();
+ for (int32_t i=0; i < rows; i++)
+ {
+ nscoord height = mGrid.GetPrefRowHeight(aState, i, true);
+ AddWidth(total, height, false); // AddHeight
+ }
+ }
+
+ if (!columnsBox) {
+ // max height is the sum of our rows
+ int32_t columns = mGrid.GetColumnCount();
+ for (int32_t i=0; i < columns; i++)
+ {
+ nscoord width = mGrid.GetPrefRowHeight(aState, i, false);
+ AddWidth(total, width, true); // AddWidth
+ }
+ }
+
+ AddMargin(aBox, total);
+ AddOffset(aBox, total);
+ AddLargestSize(pref, total);
+ }
+
+ return pref;
+}
+
+nsSize
+nsGridLayout2::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize maxSize = nsStackLayout::GetXULMaxSize(aBox, aState);
+
+ // if there are no <rows> tags that will sum up our columns,
+ // sum up our columns here.
+ nsSize total(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ nsIFrame* rowsBox = mGrid.GetRowsBox();
+ nsIFrame* columnsBox = mGrid.GetColumnsBox();
+ if (!rowsBox || !columnsBox) {
+ if (!rowsBox) {
+ total.height = 0;
+ // max height is the sum of our rows
+ int32_t rows = mGrid.GetRowCount();
+ for (int32_t i=0; i < rows; i++)
+ {
+ nscoord height = mGrid.GetMaxRowHeight(aState, i, true);
+ AddWidth(total, height, false); // AddHeight
+ }
+ }
+
+ if (!columnsBox) {
+ total.width = 0;
+ // max height is the sum of our rows
+ int32_t columns = mGrid.GetColumnCount();
+ for (int32_t i=0; i < columns; i++)
+ {
+ nscoord width = mGrid.GetMaxRowHeight(aState, i, false);
+ AddWidth(total, width, true); // AddWidth
+ }
+ }
+
+ AddMargin(aBox, total);
+ AddOffset(aBox, total);
+ AddSmallestSize(maxSize, total);
+ }
+
+ return maxSize;
+}
+
+int32_t
+nsGridLayout2::BuildRows(nsIFrame* aBox, nsGridRow* aRows)
+{
+ if (aBox) {
+ aRows[0].Init(aBox, true);
+ return 1;
+ }
+ return 0;
+}
+
+nsMargin
+nsGridLayout2::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal)
+{
+ nsMargin margin(0,0,0,0);
+ return margin;
+}
+
+void
+nsGridLayout2::ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aPrevBox,
+ const nsFrameList::Slice& aNewChildren)
+{
+ mGrid.NeedsRebuild(aState);
+}
+
+void
+nsGridLayout2::ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState,
+ const nsFrameList::Slice& aNewChildren)
+{
+ mGrid.NeedsRebuild(aState);
+}
+
+void
+nsGridLayout2::ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aChildList)
+{
+ mGrid.NeedsRebuild(aState);
+}
+
+void
+nsGridLayout2::ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aChildList)
+{
+ mGrid.NeedsRebuild(aState);
+}
+
+NS_IMPL_ADDREF_INHERITED(nsGridLayout2, nsStackLayout)
+NS_IMPL_RELEASE_INHERITED(nsGridLayout2, nsStackLayout)
+
+NS_INTERFACE_MAP_BEGIN(nsGridLayout2)
+ NS_INTERFACE_MAP_ENTRY(nsIGridPart)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGridPart)
+NS_INTERFACE_MAP_END_INHERITING(nsStackLayout)
diff --git a/layout/xul/grid/nsGridLayout2.h b/layout/xul/grid/nsGridLayout2.h
new file mode 100644
index 000000000..eb696faf8
--- /dev/null
+++ b/layout/xul/grid/nsGridLayout2.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGridLayout2_h___
+#define nsGridLayout2_h___
+
+#include "mozilla/Attributes.h"
+#include "nsStackLayout.h"
+#include "nsIGridPart.h"
+#include "nsCoord.h"
+#include "nsGrid.h"
+
+class nsGridRowGroupLayout;
+class nsGridRowLayout;
+class nsGridRow;
+class nsBoxLayoutState;
+
+/**
+ * The nsBoxLayout implementation for a grid.
+ */
+class nsGridLayout2 final : public nsStackLayout,
+ public nsIGridPart
+{
+public:
+
+ friend nsresult NS_NewGridLayout2(nsIPresShell* aPresShell, nsBoxLayout** aNewLayout);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void IntrinsicISizesDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual nsGridRowGroupLayout* CastToRowGroupLayout() override { return nullptr; }
+ virtual nsGridLayout2* CastToGridLayout() override { return this; }
+ virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) override;
+ virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) override {
+ NS_NOTREACHED("Should not be called"); return nullptr;
+ }
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override { aRowCount++; }
+ virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override { }
+ virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override;
+ virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override;
+ virtual Type GetType() override { return eGrid; }
+ virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aPrevBox,
+ const nsFrameList::Slice& aNewChildren) override;
+ virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState,
+ const nsFrameList::Slice& aNewChildren) override;
+ virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aChildList) override;
+ virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aChildList) override;
+
+ virtual nsIGridPart* AsGridPart() override { return this; }
+
+ static void AddOffset(nsIFrame* aChild, nsSize& aSize);
+
+protected:
+
+ explicit nsGridLayout2(nsIPresShell* aShell);
+ virtual ~nsGridLayout2();
+ nsGrid mGrid;
+
+private:
+ void AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal);
+
+
+}; // class nsGridLayout2
+
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRow.cpp b/layout/xul/grid/nsGridRow.cpp
new file mode 100644
index 000000000..2a2a64016
--- /dev/null
+++ b/layout/xul/grid/nsGridRow.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridRow.h"
+#include "nsBoxLayoutState.h"
+#include "nsIFrame.h"
+
+nsGridRow::nsGridRow():mIsBogus(false),
+ mBox(nullptr),
+ mFlex(-1),
+ mPref(-1),
+ mMin(-1),
+ mMax(-1),
+ mTop(-1),
+ mBottom(-1),
+ mTopMargin(0),
+ mBottomMargin(0)
+
+{
+ MOZ_COUNT_CTOR(nsGridRow);
+}
+
+void
+nsGridRow::Init(nsIFrame* aBox, bool aIsBogus)
+{
+ mBox = aBox;
+ mIsBogus = aIsBogus;
+ mFlex = -1;
+ mPref = -1;
+ mMin = -1;
+ mMax = -1;
+ mTop = -1;
+ mBottom = -1;
+ mTopMargin = 0;
+ mBottomMargin = 0;
+}
+
+nsGridRow::~nsGridRow()
+{
+ MOZ_COUNT_DTOR(nsGridRow);
+}
+
+bool
+nsGridRow::IsXULCollapsed()
+{
+ return mBox && mBox->IsXULCollapsed();
+}
+
diff --git a/layout/xul/grid/nsGridRow.h b/layout/xul/grid/nsGridRow.h
new file mode 100644
index 000000000..a5bc39158
--- /dev/null
+++ b/layout/xul/grid/nsGridRow.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsGridRow_h___
+#define nsGridRow_h___
+
+#include "nsCoord.h"
+
+class nsIFrame;
+
+/**
+ * The row (or column) data structure in the grid cellmap.
+ */
+class nsGridRow
+{
+public:
+ nsGridRow();
+ ~nsGridRow();
+
+ void Init(nsIFrame* aBox, bool aIsBogus);
+
+// accessors
+ nsIFrame* GetBox() { return mBox; }
+ bool IsPrefSet() { return (mPref != -1); }
+ bool IsMinSet() { return (mMin != -1); }
+ bool IsMaxSet() { return (mMax != -1); }
+ bool IsFlexSet() { return (mFlex != -1); }
+ bool IsOffsetSet() { return (mTop != -1 && mBottom != -1); }
+ bool IsXULCollapsed();
+
+public:
+
+ bool mIsBogus;
+ nsIFrame* mBox;
+ nscoord mFlex;
+ nscoord mPref;
+ nscoord mMin;
+ nscoord mMax;
+ nscoord mTop;
+ nscoord mBottom;
+ nscoord mTopMargin;
+ nscoord mBottomMargin;
+
+};
+
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRowGroupFrame.cpp b/layout/xul/grid/nsGridRowGroupFrame.cpp
new file mode 100644
index 000000000..5b72d3753
--- /dev/null
+++ b/layout/xul/grid/nsGridRowGroupFrame.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridRowGroupFrame.h"
+#include "nsGridRowLeafLayout.h"
+#include "nsGridRow.h"
+#include "nsBoxLayoutState.h"
+#include "nsGridLayout2.h"
+
+already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout();
+
+nsIFrame*
+NS_NewGridRowGroupFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowGroupLayout();
+ return new (aPresShell) nsGridRowGroupFrame(aContext, layout);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGridRowGroupFrame)
+
+
+/**
+ * This is redefined because row groups have a funny property. If they are flexible
+ * then their flex must be equal to the sum of their children's flexes.
+ */
+nscoord
+nsGridRowGroupFrame::GetXULFlex()
+{
+ // if we are flexible out flexibility is determined by our columns.
+ // so first get the our flex. If not 0 then our flex is the sum of
+ // our columns flexes.
+
+ if (!DoesNeedRecalc(mFlex))
+ return mFlex;
+
+ if (nsBoxFrame::GetXULFlex() == 0)
+ return 0;
+
+ // ok we are flexible add up our children
+ nscoord totalFlex = 0;
+ nsIFrame* child = nsBox::GetChildXULBox(this);
+ while (child)
+ {
+ totalFlex += child->GetXULFlex();
+ child = GetNextXULBox(child);
+ }
+
+ mFlex = totalFlex;
+
+ return totalFlex;
+}
+
+
diff --git a/layout/xul/grid/nsGridRowGroupFrame.h b/layout/xul/grid/nsGridRowGroupFrame.h
new file mode 100644
index 000000000..f367e2121
--- /dev/null
+++ b/layout/xul/grid/nsGridRowGroupFrame.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ A frame that can have multiple children. Only one child may be displayed at one time. So the
+ can be flipped though like a deck of cards.
+
+**/
+
+#ifndef nsGridRowGroupFrame_h___
+#define nsGridRowGroupFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+/**
+ * A frame representing a grid row (or column) group, which is usually
+ * an element that is a child of a grid and contains all the rows (or
+ * all the columns). However, multiple levels of groups are allowed, so
+ * the parent or child could instead be another group.
+ */
+class nsGridRowGroupFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("nsGridRowGroup"), aResult);
+ }
+#endif
+
+ nsGridRowGroupFrame(nsStyleContext* aContext,
+ nsBoxLayout* aLayoutManager):
+ nsBoxFrame(aContext, false, aLayoutManager) {}
+
+ virtual nscoord GetXULFlex() override;
+
+}; // class nsGridRowGroupFrame
+
+
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRowGroupLayout.cpp b/layout/xul/grid/nsGridRowGroupLayout.cpp
new file mode 100644
index 000000000..1c600cef5
--- /dev/null
+++ b/layout/xul/grid/nsGridRowGroupLayout.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+
+/*
+ * The nsGridRowGroupLayout implements the <rows> or <columns> tag in a grid.
+ */
+
+#include "nsGridRowGroupLayout.h"
+#include "nsCOMPtr.h"
+#include "nsIScrollableFrame.h"
+#include "nsBox.h"
+#include "nsBoxLayoutState.h"
+#include "nsGridLayout2.h"
+#include "nsGridRow.h"
+#include "mozilla/ReflowInput.h"
+
+already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout()
+{
+ RefPtr<nsBoxLayout> layout = new nsGridRowGroupLayout();
+ return layout.forget();
+}
+
+nsGridRowGroupLayout::nsGridRowGroupLayout():nsGridRowLayout(), mRowCount(0)
+{
+}
+
+nsGridRowGroupLayout::~nsGridRowGroupLayout()
+{
+}
+
+void
+nsGridRowGroupLayout::ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ if (grid)
+ grid->RowAddedOrRemoved(aState, index, isHorizontal);
+}
+
+void
+nsGridRowGroupLayout::AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal)
+{
+ nscoord& size = GET_WIDTH(aSize, aIsHorizontal);
+
+ if (size == NS_INTRINSICSIZE || aSize2 == NS_INTRINSICSIZE)
+ size = NS_INTRINSICSIZE;
+ else
+ size += aSize2;
+}
+
+nsSize
+nsGridRowGroupLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize vpref = nsGridRowLayout::GetXULPrefSize(aBox, aState);
+
+
+ /* It is possible that we could have some extra columns. This is when less columns in XUL were
+ * defined that needed. And example might be a grid with 3 defined columns but a row with 4 cells in
+ * it. We would need an extra column to make the grid work. But because that extra column does not
+ * have a box associated with it we must add its size in manually. Remember we could have extra rows
+ * as well.
+ */
+
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+
+ if (grid)
+ {
+ // make sure we add in extra columns sizes as well
+ bool isHorizontal = IsXULHorizontal(aBox);
+ int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal);
+ int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal);
+ for (int32_t i=0; i < extraColumns; i++)
+ {
+ nscoord pref =
+ grid->GetPrefRowHeight(aState, i+start, !isHorizontal); // GetPrefColumnWidth
+
+ AddWidth(vpref, pref, isHorizontal);
+ }
+ }
+
+ return vpref;
+}
+
+nsSize
+nsGridRowGroupLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize maxSize = nsGridRowLayout::GetXULMaxSize(aBox, aState);
+
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+
+ if (grid)
+ {
+ // make sure we add in extra columns sizes as well
+ bool isHorizontal = IsXULHorizontal(aBox);
+ int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal);
+ int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal);
+ for (int32_t i=0; i < extraColumns; i++)
+ {
+ nscoord max =
+ grid->GetMaxRowHeight(aState, i+start, !isHorizontal); // GetMaxColumnWidth
+
+ AddWidth(maxSize, max, isHorizontal);
+ }
+ }
+
+ return maxSize;
+}
+
+nsSize
+nsGridRowGroupLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize minSize = nsGridRowLayout::GetXULMinSize(aBox, aState);
+
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+
+ if (grid)
+ {
+ // make sure we add in extra columns sizes as well
+ bool isHorizontal = IsXULHorizontal(aBox);
+ int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal);
+ int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal);
+ for (int32_t i=0; i < extraColumns; i++)
+ {
+ nscoord min =
+ grid->GetMinRowHeight(aState, i+start, !isHorizontal); // GetMinColumnWidth
+ AddWidth(minSize, min, isHorizontal);
+ }
+ }
+
+ return minSize;
+}
+
+/*
+ * Run down through our children dirtying them recursively.
+ */
+void
+nsGridRowGroupLayout::DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ if (aBox) {
+ // mark us dirty
+ // XXXldb We probably don't want to walk up the ancestor chain
+ // calling MarkIntrinsicISizesDirty for every row group.
+ aState.PresShell()->FrameNeedsReflow(aBox, nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ while(child) {
+
+ // walk into scrollframes
+ nsIFrame* deepChild = nsGrid::GetScrolledBox(child);
+
+ // walk into other monuments
+ nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild);
+ if (monument)
+ monument->DirtyRows(deepChild, aState);
+
+ child = nsBox::GetNextXULBox(child);
+ }
+ }
+}
+
+
+void
+nsGridRowGroupLayout::CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount)
+{
+ if (aBox) {
+ int32_t startCount = aRowCount;
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ while(child) {
+
+ // first see if it is a scrollframe. If so walk down into it and get the scrolled child
+ nsIFrame* deepChild = nsGrid::GetScrolledBox(child);
+
+ nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild);
+ if (monument) {
+ monument->CountRowsColumns(deepChild, aRowCount, aComputedColumnCount);
+ child = nsBox::GetNextXULBox(child);
+ deepChild = child;
+ continue;
+ }
+
+ child = nsBox::GetNextXULBox(child);
+
+ // if not a monument. Then count it. It will be a bogus row
+ aRowCount++;
+ }
+
+ mRowCount = aRowCount - startCount;
+ }
+}
+
+
+/**
+ * Fill out the given row structure recursively
+ */
+int32_t
+nsGridRowGroupLayout::BuildRows(nsIFrame* aBox, nsGridRow* aRows)
+{
+ int32_t rowCount = 0;
+
+ if (aBox) {
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ while(child) {
+
+ // first see if it is a scrollframe. If so walk down into it and get the scrolled child
+ nsIFrame* deepChild = nsGrid::GetScrolledBox(child);
+
+ nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild);
+ if (monument) {
+ rowCount += monument->BuildRows(deepChild, &aRows[rowCount]);
+ child = nsBox::GetNextXULBox(child);
+ deepChild = child;
+ continue;
+ }
+
+ aRows[rowCount].Init(child, true);
+
+ child = nsBox::GetNextXULBox(child);
+
+ // if not a monument. Then count it. It will be a bogus row
+ rowCount++;
+ }
+ }
+
+ return rowCount;
+}
+
+nsMargin
+nsGridRowGroupLayout::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal)
+{
+ // group have border and padding added to the total margin
+
+ nsMargin margin = nsGridRowLayout::GetTotalMargin(aBox, aIsHorizontal);
+
+ // make sure we have the scrollframe on the outside if it has one.
+ // that's where the border is.
+ aBox = nsGrid::GetScrollBox(aBox);
+
+ // add our border/padding to it
+ nsMargin borderPadding(0,0,0,0);
+ aBox->GetXULBorderAndPadding(borderPadding);
+ margin += borderPadding;
+
+ return margin;
+}
+
+
diff --git a/layout/xul/grid/nsGridRowGroupLayout.h b/layout/xul/grid/nsGridRowGroupLayout.h
new file mode 100644
index 000000000..07f2aa1eb
--- /dev/null
+++ b/layout/xul/grid/nsGridRowGroupLayout.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsGridRowGroupLayout_h___
+#define nsGridRowGroupLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGridRowLayout.h"
+
+/**
+ * The nsBoxLayout implementation for nsGridRowGroupFrame.
+ */
+class nsGridRowGroupLayout : public nsGridRowLayout
+{
+public:
+
+ friend already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout();
+
+ virtual nsGridRowGroupLayout* CastToRowGroupLayout() override { return this; }
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override;
+ virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+ virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override;
+ virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override;
+ virtual int32_t GetRowCount() override { return mRowCount; }
+ virtual Type GetType() override { return eRowGroup; }
+
+protected:
+ nsGridRowGroupLayout();
+ virtual ~nsGridRowGroupLayout();
+
+ virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+ static void AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal);
+
+private:
+ int32_t mRowCount;
+};
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRowLayout.cpp b/layout/xul/grid/nsGridRowLayout.cpp
new file mode 100644
index 000000000..a658e088e
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLayout.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridRowLayout.h"
+#include "nsBoxLayoutState.h"
+#include "nsIScrollableFrame.h"
+#include "nsBox.h"
+#include "nsStackLayout.h"
+#include "nsGrid.h"
+
+nsGridRowLayout::nsGridRowLayout():nsSprocketLayout()
+{
+}
+
+nsGridRowLayout::~nsGridRowLayout()
+{
+}
+
+void
+nsGridRowLayout::ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aPrevBox,
+ const nsFrameList::Slice& aNewChildren)
+{
+ ChildAddedOrRemoved(aBox, aState);
+}
+
+void
+nsGridRowLayout::ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState,
+ const nsFrameList::Slice& aNewChildren)
+{
+ ChildAddedOrRemoved(aBox, aState);
+}
+
+void
+nsGridRowLayout::ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList)
+{
+ ChildAddedOrRemoved(aBox, aState);
+}
+
+void
+nsGridRowLayout::ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList)
+{
+ ChildAddedOrRemoved(aBox, aState);
+}
+
+nsIGridPart*
+nsGridRowLayout::GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox)
+{
+ // go up and find our parent gridRow. Skip and non gridRow
+ // parents.
+ *aParentBox = nullptr;
+
+ // walk up through any scrollboxes
+ aBox = nsGrid::GetScrollBox(aBox);
+
+ // get the parent
+ if (aBox)
+ aBox = nsBox::GetParentXULBox(aBox);
+
+ if (aBox)
+ {
+ nsIGridPart* parentGridRow = nsGrid::GetPartFromBox(aBox);
+ if (parentGridRow && parentGridRow->CanContain(this)) {
+ *aParentBox = aBox;
+ return parentGridRow;
+ }
+ }
+
+ return nullptr;
+}
+
+
+nsGrid*
+nsGridRowLayout::GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor)
+{
+
+ if (aRequestor == nullptr)
+ {
+ nsIFrame* parentBox; // nsIFrame is implemented by nsIFrame and is not refcounted.
+ nsIGridPart* parent = GetParentGridPart(aBox, &parentBox);
+ if (parent)
+ return parent->GetGrid(parentBox, aIndex, this);
+ return nullptr;
+ }
+
+ int32_t index = -1;
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ int32_t count = 0;
+ while(child)
+ {
+ // if there is a scrollframe walk inside it to its child
+ nsIFrame* childBox = nsGrid::GetScrolledBox(child);
+
+ nsBoxLayout* layout = childBox->GetXULLayoutManager();
+ nsIGridPart* gridRow = nsGrid::GetPartFromBox(childBox);
+ if (gridRow)
+ {
+ if (layout == aRequestor) {
+ index = count;
+ break;
+ }
+ count += gridRow->GetRowCount();
+ } else
+ count++;
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ // if we didn't find ourselves then the tree isn't properly formed yet
+ // this could happen during initial construction so lets just
+ // fail.
+ if (index == -1) {
+ *aIndex = -1;
+ return nullptr;
+ }
+
+ (*aIndex) += index;
+
+ nsIFrame* parentBox; // nsIFrame is implemented by nsIFrame and is not refcounted.
+ nsIGridPart* parent = GetParentGridPart(aBox, &parentBox);
+ if (parent)
+ return parent->GetGrid(parentBox, aIndex, this);
+
+ return nullptr;
+}
+
+nsMargin
+nsGridRowLayout::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal)
+{
+ // get our parents margin
+ nsMargin margin(0,0,0,0);
+ nsIFrame* parent = nullptr;
+ nsIGridPart* part = GetParentGridPart(aBox, &parent);
+ if (part && parent) {
+ // if we are the first or last child walk upward and add margins.
+
+ // make sure we check for a scrollbox
+ aBox = nsGrid::GetScrollBox(aBox);
+
+ // see if we have a next to see if we are last
+ nsIFrame* next = nsBox::GetNextXULBox(aBox);
+
+ // get the parent first child to see if we are first
+ nsIFrame* child = nsBox::GetChildXULBox(parent);
+
+ margin = part->GetTotalMargin(parent, aIsHorizontal);
+
+ // if first or last
+ if (child == aBox || next == nullptr) {
+
+ // if it's not the first child remove the top margin
+ // we don't need it.
+ if (child != aBox)
+ {
+ if (aIsHorizontal)
+ margin.top = 0;
+ else
+ margin.left = 0;
+ }
+
+ // if it's not the last child remove the bottom margin
+ // we don't need it.
+ if (next != nullptr)
+ {
+ if (aIsHorizontal)
+ margin.bottom = 0;
+ else
+ margin.right = 0;
+ }
+
+ }
+ }
+
+ // add ours to it.
+ nsMargin ourMargin;
+ aBox->GetXULMargin(ourMargin);
+ margin += ourMargin;
+
+ return margin;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsGridRowLayout, nsBoxLayout)
+NS_IMPL_RELEASE_INHERITED(nsGridRowLayout, nsBoxLayout)
+
+NS_INTERFACE_MAP_BEGIN(nsGridRowLayout)
+ NS_INTERFACE_MAP_ENTRY(nsIGridPart)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGridPart)
+NS_INTERFACE_MAP_END_INHERITING(nsBoxLayout)
diff --git a/layout/xul/grid/nsGridRowLayout.h b/layout/xul/grid/nsGridRowLayout.h
new file mode 100644
index 000000000..22a6f12c9
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLayout.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsGridRowLayout_h___
+#define nsGridRowLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsSprocketLayout.h"
+#include "nsIGridPart.h"
+class nsGridRowGroupLayout;
+class nsGridLayout2;
+class nsBoxLayoutState;
+class nsGrid;
+
+/**
+ * A common base class for nsGridRowLeafLayout (the nsBoxLayout object
+ * for a grid row or column) and nsGridRowGroupLayout (the nsBoxLayout
+ * object for a grid row group or column group).
+ */
+// XXXldb This needs a name that indicates that it's a base class for
+// both row and rows (row-group).
+class nsGridRowLayout : public nsSprocketLayout,
+ public nsIGridPart
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual nsGridRowGroupLayout* CastToRowGroupLayout() override { return nullptr; }
+ virtual nsGridLayout2* CastToGridLayout() override { return nullptr; }
+ virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) override;
+ virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) override;
+ virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aPrevBox,
+ const nsFrameList::Slice& aNewChildren) override;
+ virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState,
+ const nsFrameList::Slice& aNewChildren) override;
+ virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) override;
+ virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) override;
+ virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override;
+
+ virtual nsIGridPart* AsGridPart() override { return this; }
+
+protected:
+ virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState)=0;
+
+ nsGridRowLayout();
+ virtual ~nsGridRowLayout();
+};
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRowLeafFrame.cpp b/layout/xul/grid/nsGridRowLeafFrame.cpp
new file mode 100644
index 000000000..e973877a4
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLeafFrame.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridRowLeafFrame.h"
+#include "nsGridRowLeafLayout.h"
+#include "nsGridRow.h"
+#include "nsBoxLayoutState.h"
+#include "nsGridLayout2.h"
+
+already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout();
+
+nsIFrame*
+NS_NewGridRowLeafFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowLeafLayout();
+ return new (aPresShell) nsGridRowLeafFrame(aContext, false, layout);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGridRowLeafFrame)
+
+/*
+ * Our border and padding could be affected by our columns or rows.
+ * Let's go check it out.
+ */
+nsresult
+nsGridRowLeafFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding)
+{
+ // if our columns have made our padding larger add it in.
+ nsresult rv = nsBoxFrame::GetXULBorderAndPadding(aBorderAndPadding);
+
+ nsIGridPart* part = nsGrid::GetPartFromBox(this);
+ if (!part)
+ return rv;
+
+ int32_t index = 0;
+ nsGrid* grid = part->GetGrid(this, &index);
+
+ if (!grid)
+ return rv;
+
+ bool isHorizontal = IsXULHorizontal();
+
+ int32_t firstIndex = 0;
+ int32_t lastIndex = 0;
+ nsGridRow* firstRow = nullptr;
+ nsGridRow* lastRow = nullptr;
+ grid->GetFirstAndLastRow(firstIndex, lastIndex, firstRow, lastRow, isHorizontal);
+
+ // only the first and last rows can be affected.
+ if (firstRow && firstRow->GetBox() == this) {
+
+ nscoord top = 0;
+ nscoord bottom = 0;
+ grid->GetRowOffsets(firstIndex, top, bottom, isHorizontal);
+
+ if (isHorizontal) {
+ if (top > aBorderAndPadding.top)
+ aBorderAndPadding.top = top;
+ } else {
+ if (top > aBorderAndPadding.left)
+ aBorderAndPadding.left = top;
+ }
+ }
+
+ if (lastRow && lastRow->GetBox() == this) {
+
+ nscoord top = 0;
+ nscoord bottom = 0;
+ grid->GetRowOffsets(lastIndex, top, bottom, isHorizontal);
+
+ if (isHorizontal) {
+ if (bottom > aBorderAndPadding.bottom)
+ aBorderAndPadding.bottom = bottom;
+ } else {
+ if (bottom > aBorderAndPadding.right)
+ aBorderAndPadding.right = bottom;
+ }
+
+ }
+
+ return rv;
+}
+
+
diff --git a/layout/xul/grid/nsGridRowLeafFrame.h b/layout/xul/grid/nsGridRowLeafFrame.h
new file mode 100644
index 000000000..dd4ee6835
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLeafFrame.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ A frame that can have multiple children. Only one child may be displayed at one time. So the
+ can be flipped though like a deck of cards.
+
+**/
+
+#ifndef nsGridRowLeafFrame_h___
+#define nsGridRowLeafFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+/**
+ * A frame representing a grid row (or column). Grid row (and column)
+ * elements are the children of row group (or column group) elements,
+ * and their children are placed one to a cell.
+ */
+// XXXldb This needs a better name that indicates that it's for any grid
+// row.
+class nsGridRowLeafFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewGridRowLeafFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("nsGridRowLeaf"), aResult);
+ }
+#endif
+
+ nsGridRowLeafFrame(nsStyleContext* aContext,
+ bool aIsRoot,
+ nsBoxLayout* aLayoutManager):
+ nsBoxFrame(aContext, aIsRoot, aLayoutManager) {}
+
+ virtual nsresult GetXULBorderAndPadding(nsMargin& aBorderAndPadding) override;
+
+}; // class nsGridRowLeafFrame
+
+
+
+#endif
+
diff --git a/layout/xul/grid/nsGridRowLeafLayout.cpp b/layout/xul/grid/nsGridRowLeafLayout.cpp
new file mode 100644
index 000000000..2a089af0c
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLeafLayout.cpp
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsGridRowLeafLayout.h"
+#include "nsGridRowGroupLayout.h"
+#include "nsGridRow.h"
+#include "nsBoxLayoutState.h"
+#include "nsBox.h"
+#include "nsIScrollableFrame.h"
+#include "nsBoxFrame.h"
+#include "nsGridLayout2.h"
+#include <algorithm>
+
+already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout()
+{
+ RefPtr<nsBoxLayout> layout = new nsGridRowLeafLayout();
+ return layout.forget();
+}
+
+nsGridRowLeafLayout::nsGridRowLeafLayout():nsGridRowLayout()
+{
+}
+
+nsGridRowLeafLayout::~nsGridRowLeafLayout()
+{
+}
+
+nsSize
+nsGridRowLeafLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ // If we are not in a grid. Then we just work like a box. But if we are in a grid
+ // ask the grid for our size.
+ if (!grid) {
+ return nsGridRowLayout::GetXULPrefSize(aBox, aState);
+ }
+ else {
+ return grid->GetPrefRowSize(aState, index, isHorizontal);
+ //AddBorderAndPadding(aBox, pref);
+ }
+}
+
+nsSize
+nsGridRowLeafLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ if (!grid)
+ return nsGridRowLayout::GetXULMinSize(aBox, aState);
+ else {
+ nsSize minSize = grid->GetMinRowSize(aState, index, isHorizontal);
+ AddBorderAndPadding(aBox, minSize);
+ return minSize;
+ }
+}
+
+nsSize
+nsGridRowLeafLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ if (!grid)
+ return nsGridRowLayout::GetXULMaxSize(aBox, aState);
+ else {
+ nsSize maxSize;
+ maxSize = grid->GetMaxRowSize(aState, index, isHorizontal);
+ AddBorderAndPadding(aBox, maxSize);
+ return maxSize;
+ }
+}
+
+/** If a child is added or removed or changes size
+ */
+void
+nsGridRowLeafLayout::ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ if (grid)
+ grid->CellAddedOrRemoved(aState, index, isHorizontal);
+}
+
+void
+nsGridRowLeafLayout::PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aState, nsBoxSize*& aBoxSizes, nscoord& aMinSize, nscoord& aMaxSize, int32_t& aFlexes)
+{
+ int32_t index = 0;
+ nsGrid* grid = GetGrid(aBox, &index);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ // Our base class SprocketLayout is giving us a chance to change the box sizes before layout
+ // If we are a row lets change the sizes to match our columns. If we are a column then do the opposite
+ // and make them match or rows.
+ if (grid) {
+ nsGridRow* column;
+ int32_t count = grid->GetColumnCount(isHorizontal);
+ nsBoxSize* start = nullptr;
+ nsBoxSize* last = nullptr;
+ nsBoxSize* current = nullptr;
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ for (int i=0; i < count; i++)
+ {
+ column = grid->GetColumnAt(i,isHorizontal);
+
+ // make sure the value was computed before we use it.
+ // !isHorizontal is passed in to invert the behavior of these methods.
+ nscoord pref =
+ grid->GetPrefRowHeight(aState, i, !isHorizontal); // GetPrefColumnWidth
+ nscoord min =
+ grid->GetMinRowHeight(aState, i, !isHorizontal); // GetMinColumnWidth
+ nscoord max =
+ grid->GetMaxRowHeight(aState, i, !isHorizontal); // GetMaxColumnWidth
+ nscoord flex = grid->GetRowFlex(i, !isHorizontal); // GetColumnFlex
+ nscoord left = 0;
+ nscoord right = 0;
+ grid->GetRowOffsets(i, left, right, !isHorizontal); // GetColumnOffsets
+ nsIFrame* box = column->GetBox();
+ bool collapsed = false;
+ nscoord topMargin = column->mTopMargin;
+ nscoord bottomMargin = column->mBottomMargin;
+
+ if (box)
+ collapsed = box->IsXULCollapsed();
+
+ pref = pref - (left + right);
+ if (pref < 0)
+ pref = 0;
+
+ // if this is the first or last column. Take into account that
+ // our row could have a border that could affect our left or right
+ // padding from our columns. If the row has padding subtract it.
+ // would should always be able to garentee that our margin is smaller
+ // or equal to our left or right
+ int32_t firstIndex = 0;
+ int32_t lastIndex = 0;
+ nsGridRow* firstRow = nullptr;
+ nsGridRow* lastRow = nullptr;
+ grid->GetFirstAndLastRow(firstIndex, lastIndex, firstRow, lastRow, !isHorizontal);
+
+ if (i == firstIndex || i == lastIndex) {
+ nsMargin offset = GetTotalMargin(aBox, isHorizontal);
+
+ nsMargin border(0,0,0,0);
+ // can't call GetBorderPadding we will get into recursion
+ aBox->GetXULBorder(border);
+ offset += border;
+ aBox->GetXULPadding(border);
+ offset += border;
+
+ // subtract from out left and right
+ if (i == firstIndex)
+ {
+ if (isHorizontal)
+ left -= offset.left;
+ else
+ left -= offset.top;
+ }
+
+ if (i == lastIndex)
+ {
+ if (isHorizontal)
+ right -= offset.right;
+ else
+ right -= offset.bottom;
+ }
+ }
+
+ // initialize the box size here
+ max = std::max(min, max);
+ pref = nsBox::BoundsCheck(min, pref, max);
+
+ current = new (aState) nsBoxSize();
+ current->pref = pref;
+ current->min = min;
+ current->max = max;
+ current->flex = flex;
+ current->bogus = column->mIsBogus;
+ current->left = left + topMargin;
+ current->right = right + bottomMargin;
+ current->collapsed = collapsed;
+
+ if (!start) {
+ start = current;
+ last = start;
+ } else {
+ last->next = current;
+ last = current;
+ }
+
+ if (child && !column->mIsBogus)
+ child = nsBox::GetNextXULBox(child);
+
+ }
+ aBoxSizes = start;
+ }
+
+ nsSprocketLayout::PopulateBoxSizes(aBox, aState, aBoxSizes, aMinSize, aMaxSize, aFlexes);
+}
+
+void
+nsGridRowLeafLayout::ComputeChildSizes(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nscoord& aGivenSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize*& aComputedBoxSizes)
+{
+ // see if we are in a scrollable frame. If we are then there could be scrollbars present
+ // if so we need to subtract them out to make sure our columns line up.
+ if (aBox) {
+ bool isHorizontal = aBox->IsXULHorizontal();
+
+ // go up the parent chain looking for scrollframes
+ nscoord diff = 0;
+ nsIFrame* parentBox;
+ (void)GetParentGridPart(aBox, &parentBox);
+ while (parentBox) {
+ nsIFrame* scrollbox = nsGrid::GetScrollBox(parentBox);
+ nsIScrollableFrame *scrollable = do_QueryFrame(scrollbox);
+ if (scrollable) {
+ // Don't call GetActualScrollbarSizes here because it's not safe
+ // to call that while we're reflowing the contents of the scrollframe,
+ // which we are here.
+ nsMargin scrollbarSizes = scrollable->GetDesiredScrollbarSizes(&aState);
+ uint32_t visible = scrollable->GetScrollbarVisibility();
+
+ if (isHorizontal && (visible & nsIScrollableFrame::VERTICAL)) {
+ diff += scrollbarSizes.left + scrollbarSizes.right;
+ } else if (!isHorizontal && (visible & nsIScrollableFrame::HORIZONTAL)) {
+ diff += scrollbarSizes.top + scrollbarSizes.bottom;
+ }
+ }
+
+ (void)GetParentGridPart(parentBox, &parentBox);
+ }
+
+ if (diff > 0) {
+ aGivenSize += diff;
+
+ nsSprocketLayout::ComputeChildSizes(aBox, aState, aGivenSize, aBoxSizes, aComputedBoxSizes);
+
+ aGivenSize -= diff;
+
+ nsComputedBoxSize* s = aComputedBoxSizes;
+ nsComputedBoxSize* last = aComputedBoxSizes;
+ while(s)
+ {
+ last = s;
+ s = s->next;
+ }
+
+ if (last)
+ last->size -= diff;
+
+ return;
+ }
+ }
+
+ nsSprocketLayout::ComputeChildSizes(aBox, aState, aGivenSize, aBoxSizes, aComputedBoxSizes);
+
+}
+
+NS_IMETHODIMP
+nsGridRowLeafLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ return nsGridRowLayout::XULLayout(aBox, aBoxLayoutState);
+}
+
+void
+nsGridRowLeafLayout::DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ if (aBox) {
+ // mark us dirty
+ // XXXldb We probably don't want to walk up the ancestor chain
+ // calling MarkIntrinsicISizesDirty for every row.
+ aState.PresShell()->FrameNeedsReflow(aBox, nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+void
+nsGridRowLeafLayout::CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount)
+{
+ if (aBox) {
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ // count the children
+ int32_t columnCount = 0;
+ while(child) {
+ child = nsBox::GetNextXULBox(child);
+ columnCount++;
+ }
+
+ // if our count is greater than the current column count
+ if (columnCount > aComputedColumnCount)
+ aComputedColumnCount = columnCount;
+
+ aRowCount++;
+ }
+}
+
+int32_t
+nsGridRowLeafLayout::BuildRows(nsIFrame* aBox, nsGridRow* aRows)
+{
+ if (aBox) {
+ aRows[0].Init(aBox, false);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/layout/xul/grid/nsGridRowLeafLayout.h b/layout/xul/grid/nsGridRowLeafLayout.h
new file mode 100644
index 000000000..b103ab176
--- /dev/null
+++ b/layout/xul/grid/nsGridRowLeafLayout.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsGridRowLeafLayout_h___
+#define nsGridRowLeafLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGridRowLayout.h"
+#include "nsCOMPtr.h"
+
+/**
+ * The nsBoxLayout implementation for nsGridRowLeafFrame.
+ */
+// XXXldb This needs a better name that indicates that it's for any grid
+// row.
+class nsGridRowLeafLayout final : public nsGridRowLayout
+{
+public:
+
+ friend already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout();
+
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override;
+ virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+ virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override;
+ virtual Type GetType() override { return eRowLeaf; }
+
+protected:
+
+ virtual void PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState,
+ nsBoxSize*& aBoxSizes, nscoord& aMinSize,
+ nscoord& aMaxSize, int32_t& aFlexes) override;
+ virtual void ComputeChildSizes(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nscoord& aGivenSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize*& aComputedBoxSizes) override;
+
+
+ nsGridRowLeafLayout();
+ virtual ~nsGridRowLeafLayout();
+ //virtual void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize);
+
+private:
+
+}; // class nsGridRowLeafLayout
+
+#endif
+
diff --git a/layout/xul/grid/nsIGridPart.h b/layout/xul/grid/nsIGridPart.h
new file mode 100644
index 000000000..202429de6
--- /dev/null
+++ b/layout/xul/grid/nsIGridPart.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIGridPart_h___
+#define nsIGridPart_h___
+
+#include "nsISupports.h"
+
+class nsGridRowGroupLayout;
+class nsGrid;
+class nsGridRowLayout;
+class nsGridRow;
+class nsGridLayout2;
+
+// 07373ed7-e947-4a5e-b36c-69f7c195677b
+#define NS_IGRIDPART_IID \
+{ 0x07373ed7, 0xe947, 0x4a5e, \
+ { 0xb3, 0x6c, 0x69, 0xf7, 0xc1, 0x95, 0x67, 0x7b } }
+
+/**
+ * An additional interface implemented by nsBoxLayout implementations
+ * for parts of a grid (excluding cells, which are not special).
+ */
+class nsIGridPart : public nsISupports {
+
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IGRIDPART_IID)
+
+ virtual nsGridRowGroupLayout* CastToRowGroupLayout()=0;
+ virtual nsGridLayout2* CastToGridLayout()=0;
+
+ /**
+ * @param aBox [IN] The other half of the |this| parameter, i.e., the box
+ * whose layout manager is |this|.
+ * @param aIndex [INOUT] For callers not setting aRequestor, the value
+ * pointed to by aIndex is incremented by the index
+ * of the row (aBox) within its row group; if aBox
+ * is not a row/column, it is untouched.
+ * The implementation does this by doing the aIndex
+ * incrementing in the call to the parent row group
+ * when aRequestor is non-null.
+ * @param aRequestor [IN] Non-null if and only if this is a recursive
+ * call from the GetGrid method on a child grid part,
+ * in which case it is a pointer to that grid part.
+ * (This may only be non-null for row groups and
+ * grids.)
+ * @return The grid of which aBox (a row, row group, or grid) is a part.
+ */
+ virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr)=0;
+
+ /**
+ * @param aBox [IN] The other half of the |this| parameter, i.e., the box
+ * whose layout manager is |this|.
+ * @param aParentBox [OUT] The box representing the next level up in
+ * the grid (i.e., row group for a row, grid for a
+ * row group).
+ * @returns The layout manager for aParentBox.
+ */
+ virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) = 0;
+
+ /**
+ * @param aBox [IN] The other half of the |this| parameter, i.e., the box
+ * whose layout manager is |this|.
+ * @param aRowCount [INOUT] Row count
+ * @param aComputedColumnCount [INOUT] Column count
+ */
+ virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount)=0;
+ virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState)=0;
+ virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows)=0;
+ virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal)=0;
+ virtual int32_t GetRowCount() { return 1; }
+
+ /**
+ * Return the level of the grid hierarchy this grid part represents.
+ */
+ enum Type { eGrid, eRowGroup, eRowLeaf };
+ virtual Type GetType()=0;
+
+ /**
+ * Return whether this grid part is an appropriate parent for the argument.
+ */
+ bool CanContain(nsIGridPart* aPossibleChild) {
+ Type thisType = GetType(), childType = aPossibleChild->GetType();
+ return thisType + 1 == childType || (thisType == eRowGroup && childType == eRowGroup);
+ }
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIGridPart, NS_IGRIDPART_IID)
+
+#endif
+
diff --git a/layout/xul/grid/reftests/column-sizing-1-ref.xul b/layout/xul/grid/reftests/column-sizing-1-ref.xul
new file mode 100644
index 000000000..df0113083
--- /dev/null
+++ b/layout/xul/grid/reftests/column-sizing-1-ref.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <hbox>
+ <vbox style="background:aqua">
+ <label value="Left" />
+ </vbox>
+ <vbox style="background:yellow">
+ <textbox value="Right" />
+ </vbox>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/column-sizing-1.xul b/layout/xul/grid/reftests/column-sizing-1.xul
new file mode 100644
index 000000000..2a94569ba
--- /dev/null
+++ b/layout/xul/grid/reftests/column-sizing-1.xul
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <grid>
+ <columns>
+ <column style="background:aqua">
+ <label value="Left" />
+ </column>
+ <column style="background:yellow">
+ <textbox value="Right" />
+ </column>
+ </columns>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-basic-ref.xhtml b/layout/xul/grid/reftests/not-full-basic-ref.xhtml
new file mode 100644
index 000000000..cd233585a
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-basic-ref.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ top: 0px; height: 200px; left: 0px; width: 200px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 200px; bottom: 0px; left: 0px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 200px; height: 100px; left: 100px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 0px; height: 100px; left: 200px; right: 0px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 100px; height: 100px; left: 200px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/not-full-basic.xul b/layout/xul/grid/reftests/not-full-basic.xul
new file mode 100644
index 000000000..5c7fa9123
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-basic.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-grid-pack-align.xul b/layout/xul/grid/reftests/not-full-grid-pack-align.xul
new file mode 100644
index 000000000..3fe6a95cb
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-grid-pack-align.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <!-- align and pack should be no-ops on grid element (not on columns/rows) -->
+ <grid flex="1" align="start" pack="end">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml
new file mode 100644
index 000000000..abef67f87
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ top: 100px; height: 100px; left: 100px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 0px; height: 100px; left: 100px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 200px; height: 100px; left: 100px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 100px; height: 100px; left: 0px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 100px; height: 100px; left: 200px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/not-full-row-group-align.xul b/layout/xul/grid/reftests/not-full-row-group-align.xul
new file mode 100644
index 000000000..0037d9fb8
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-align.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <grid flex="1">
+ <!-- does anybody actually *want* the way columns align="start" behaves here? -->
+ <columns align="start">
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows align="start">
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml
new file mode 100644
index 000000000..b2a92b07b
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ bottom: 0px; height: 100px; right: 0px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ bottom: 0px; height: 100px; left: 0px; right: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ bottom: 100px; height: 100px; left: 0px; width: 300px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 0px; bottom: 100px; right: 0px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 0px; height: 300px; right: 100px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/not-full-row-group-direction.xul b/layout/xul/grid/reftests/not-full-row-group-direction.xul
new file mode 100644
index 000000000..c38db40a5
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-direction.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ rows, columns { -moz-box-direction: reverse; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml
new file mode 100644
index 000000000..9232f6ba4
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ bottom: 200px; height: 100px; right: 200px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ bottom: 200px; height: 100px; left: 0px; right: 300px;" />
+<div style="background: rgb(0, 0, 153);
+ bottom: 100px; height: 100px; left: 0px; width: 300px;" />
+<div style="background: rgb(0, 0, 153);
+ bottom: 200px; height: 100px; right: 0px; width: 200px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 0px; bottom: 300px; right: 200px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 0px; height: 300px; right: 100px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ bottom: 0px; height: 200px; right: 200px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/not-full-row-group-pack.xul b/layout/xul/grid/reftests/not-full-row-group-pack.xul
new file mode 100644
index 000000000..bb8f650ae
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-group-pack.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ rows, columns { -moz-box-pack: end; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-leaf-align.xul b/layout/xul/grid/reftests/not-full-row-leaf-align.xul
new file mode 100644
index 000000000..806514ebd
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-leaf-align.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ row, column { -moz-box-align: start; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-leaf-direction.xul b/layout/xul/grid/reftests/not-full-row-leaf-direction.xul
new file mode 100644
index 000000000..17c3a6585
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-leaf-direction.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ row, column { -moz-box-direction: reverse; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml b/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml
new file mode 100644
index 000000000..30635313a
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ top: 0px; height: 100px; left: 0px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ top: 100px; bottom: 0px; left: 0px; width: 100px;" />
+<div style="background: rgb(0, 255, 0);
+ bottom: 0px; height: 300px; left: 100px; width: 100px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 0px; height: 100px; left: 100px; right: 0px;" />
+<div style="background: rgb(0, 0, 153);
+ top: 100px; height: 100px; right: 0px; width: 300px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/not-full-row-leaf-pack.xul b/layout/xul/grid/reftests/not-full-row-leaf-pack.xul
new file mode 100644
index 000000000..8f353c764
--- /dev/null
+++ b/layout/xul/grid/reftests/not-full-row-leaf-pack.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ row, column { -moz-box-pack: end; }
+ ]]></style>
+ <grid flex="1">
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/reftest-stylo.list b/layout/xul/grid/reftests/reftest-stylo.list
new file mode 100644
index 000000000..eb73955c9
--- /dev/null
+++ b/layout/xul/grid/reftests/reftest-stylo.list
@@ -0,0 +1,38 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+skip-if((B2G&&browserIsRemote)||Mulet) == row-sizing-1.xul row-sizing-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == column-sizing-1.xul column-sizing-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == z-order-1.xul z-order-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == z-order-2.xul z-order-2.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == not-full-basic.xul not-full-basic.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,110000) == not-full-grid-pack-align.xul not-full-grid-pack-align.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,30000) == not-full-row-group-align.xul not-full-row-group-align.xul
+# does anyone want/need this behavior?
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,10000) == not-full-row-group-pack.xul not-full-row-group-pack.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,50000) == not-full-row-group-direction.xul not-full-row-group-direction.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == not-full-row-leaf-align.xul not-full-row-leaf-align.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,50000) == not-full-row-leaf-pack.xul not-full-row-leaf-pack.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,80000) == not-full-row-leaf-direction.xul not-full-row-leaf-direction.xul
+skip-if(B2G||Mulet) random-if(transparentScrollbars) fuzzy-if(OSX==1010,1,565) == scrollable-columns.xul scrollable-columns.xul
+# bug 650597
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == scrollable-rows.xul scrollable-rows.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == sizing-2d.xul sizing-2d.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
diff --git a/layout/xul/grid/reftests/reftest.list b/layout/xul/grid/reftests/reftest.list
new file mode 100644
index 000000000..eb414f906
--- /dev/null
+++ b/layout/xul/grid/reftests/reftest.list
@@ -0,0 +1,18 @@
+== row-sizing-1.xul row-sizing-1-ref.xul
+== column-sizing-1.xul column-sizing-1-ref.xul
+== row-or-column-sizing-1.xul row-or-column-sizing-2.xul
+== row-or-column-sizing-1.xul row-or-column-sizing-3.xul
+== row-or-column-sizing-1.xul row-or-column-sizing-4.xul
+fuzzy-if(skiaContent,1,60000) == z-order-1.xul z-order-1-ref.xul
+fuzzy-if(skiaContent,1,60000) == z-order-2.xul z-order-2-ref.xul
+fuzzy-if(skiaContent,1,60000) == not-full-basic.xul not-full-basic-ref.xhtml
+fuzzy-if(skiaContent,1,110000) == not-full-grid-pack-align.xul not-full-basic-ref.xhtml
+fuzzy-if(skiaContent,1,30000) == not-full-row-group-align.xul not-full-row-group-align-ref.xhtml # does anyone want/need this behavior?
+fuzzy-if(skiaContent,1,10000) == not-full-row-group-pack.xul not-full-row-group-pack-ref.xhtml
+fuzzy-if(skiaContent,1,50000) == not-full-row-group-direction.xul not-full-row-group-direction-ref.xhtml
+fuzzy-if(skiaContent,1,60000) == not-full-row-leaf-align.xul not-full-basic-ref.xhtml
+fuzzy-if(skiaContent,1,50000) == not-full-row-leaf-pack.xul not-full-row-leaf-pack-ref.xhtml
+fuzzy-if(skiaContent,1,80000) == not-full-row-leaf-direction.xul not-full-row-leaf-pack-ref.xhtml
+random-if(transparentScrollbars) fuzzy-if(OSX==1010,1,565) == scrollable-columns.xul scrollable-columns-ref.xhtml # bug 650597
+fails == scrollable-rows.xul scrollable-rows-ref.xhtml
+== sizing-2d.xul sizing-2d-ref.xul
diff --git a/layout/xul/grid/reftests/row-or-column-sizing-1.xul b/layout/xul/grid/reftests/row-or-column-sizing-1.xul
new file mode 100644
index 000000000..6c64eef18
--- /dev/null
+++ b/layout/xul/grid/reftests/row-or-column-sizing-1.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <grid>
+ <columns>
+ <column />
+ <column />
+ </columns>
+ <rows>
+ <row>
+ <hbox />
+ <label value="Upper right" />
+ </row>
+ <row>
+ <textbox value="Lower left" />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/row-or-column-sizing-2.xul b/layout/xul/grid/reftests/row-or-column-sizing-2.xul
new file mode 100644
index 000000000..008f82fd5
--- /dev/null
+++ b/layout/xul/grid/reftests/row-or-column-sizing-2.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <grid>
+ <columns>
+ <column>
+ <hbox />
+ <textbox value="Lower left" />
+ </column>
+ <column>
+ <label value="Upper right" />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row />
+ <row />
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/row-or-column-sizing-3.xul b/layout/xul/grid/reftests/row-or-column-sizing-3.xul
new file mode 100644
index 000000000..1e8e55c29
--- /dev/null
+++ b/layout/xul/grid/reftests/row-or-column-sizing-3.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <grid>
+ <columns>
+ <column>
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <label value="Upper right" />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row>
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <textbox value="Lower left" />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/row-or-column-sizing-4.xul b/layout/xul/grid/reftests/row-or-column-sizing-4.xul
new file mode 100644
index 000000000..5a826fd84
--- /dev/null
+++ b/layout/xul/grid/reftests/row-or-column-sizing-4.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <grid>
+ <columns>
+ <column>
+ <hbox />
+ <textbox value="Lower left" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row>
+ <hbox />
+ <label value="Upper right" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/row-sizing-1-ref.xul b/layout/xul/grid/reftests/row-sizing-1-ref.xul
new file mode 100644
index 000000000..b35719052
--- /dev/null
+++ b/layout/xul/grid/reftests/row-sizing-1-ref.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ orient="horizontal"
+ title="XUL Grid Test">
+ <vbox>
+ <hbox style="background:aqua">
+ <label value="Top" />
+ </hbox>
+ <hbox style="background:yellow">
+ <textbox value="Bottom" />
+ </hbox>
+ </vbox>
+</window>
diff --git a/layout/xul/grid/reftests/row-sizing-1.xul b/layout/xul/grid/reftests/row-sizing-1.xul
new file mode 100644
index 000000000..0455b8da4
--- /dev/null
+++ b/layout/xul/grid/reftests/row-sizing-1.xul
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ orient="horizontal"
+ title="XUL Grid Test">
+ <grid>
+ <rows>
+ <row style="background:aqua">
+ <label value="Top" />
+ </row>
+ <row style="background:yellow">
+ <textbox value="Bottom" />
+ </row>
+ </rows>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/scrollable-columns-ref.xhtml b/layout/xul/grid/reftests/scrollable-columns-ref.xhtml
new file mode 100644
index 000000000..698c5a036
--- /dev/null
+++ b/layout/xul/grid/reftests/scrollable-columns-ref.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ top: 0px; height: 200px; left: 0px; width: 200px;" />
+<div style="background: rgb(0, 255, 0); overflow: auto;
+ top: 200px; height: 100px; left: 0px; width: 200px;">
+ <div style="width: 300px; height: 50px" />
+</div>
+<div style="background: rgb(0, 0, 153);
+ top: 100px; height: 100px; left: 200px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/scrollable-columns.xul b/layout/xul/grid/reftests/scrollable-columns.xul
new file mode 100644
index 000000000..661c4412f
--- /dev/null
+++ b/layout/xul/grid/reftests/scrollable-columns.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ grid { width: 200px; height: 200px; }
+ columns { overflow: auto; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/scrollable-rows-ref.xhtml b/layout/xul/grid/reftests/scrollable-rows-ref.xhtml
new file mode 100644
index 000000000..6b5b95f02
--- /dev/null
+++ b/layout/xul/grid/reftests/scrollable-rows-ref.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>XUL Grid Test</title>
+ <style type="text/css">
+ html { background: black; }
+ html, body { margin: 0; padding: 0; height: 100%; }
+ div { position: absolute; }
+ </style>
+</head>
+<body>
+
+<div style="background: rgb(0, 102, 153);
+ top: 0px; height: 200px; left: 0px; width: 200px;" />
+<div style="background: rgb(0, 0, 153); overflow: auto;
+ top: 0px; height: 200px; left: 200px; width: 100px;">
+ <div style="width: 50px; height: 300px" />
+</div>
+<div style="background: rgb(0, 255, 0);
+ top: 200px; height: 100px; left: 100px; width: 100px;" />
+
+</body>
+</html>
diff --git a/layout/xul/grid/reftests/scrollable-rows.xul b/layout/xul/grid/reftests/scrollable-rows.xul
new file mode 100644
index 000000000..9fa1f82c5
--- /dev/null
+++ b/layout/xul/grid/reftests/scrollable-rows.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ grid { width: 200px; height: 200px; }
+ rows { overflow: auto; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/sizing-2d-ref.xul b/layout/xul/grid/reftests/sizing-2d-ref.xul
new file mode 100644
index 000000000..a3bc4ca73
--- /dev/null
+++ b/layout/xul/grid/reftests/sizing-2d-ref.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="start">
+ <hbox>
+ <box style="background:aqua; width: 50px; height: 100px" />
+ <box style="background:fuchsia; width: 100px; height: 100px" />
+ </hbox>
+ <hbox>
+ <box style="background:yellow; width: 50px; height: 75px" />
+ <box style="background:blue; width: 100px; height: 75px" />
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/sizing-2d.xul b/layout/xul/grid/reftests/sizing-2d.xul
new file mode 100644
index 000000000..7868f9eca
--- /dev/null
+++ b/layout/xul/grid/reftests/sizing-2d.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="start">
+ <grid>
+ <rows>
+ <row>
+ <box style="width: 25px; height: 25px" />
+ <box />
+ </row>
+ <row>
+ <box />
+ <box style="width: 75px; height: 75px" />
+ </row>
+ </rows>
+ <columns>
+ <column>
+ <box style="background: aqua" />
+ <box style="background: yellow; width: 50px; height: 50px" />
+ </column>
+ <column>
+ <box style="background: fuchsia; width: 100px; height: 100px" />
+ <box style="background: blue" />
+ </column>
+ </columns>
+ </grid>
+</window>
diff --git a/layout/xul/grid/reftests/z-order-1-ref.xul b/layout/xul/grid/reftests/z-order-1-ref.xul
new file mode 100644
index 000000000..198c4e6c6
--- /dev/null
+++ b/layout/xul/grid/reftests/z-order-1-ref.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <rows>
+ <row>
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 0, 153)" />
+ </row>
+ <row>
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 0, 153)" />
+ </row>
+ <row>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 0, 0)" />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/z-order-1.xul b/layout/xul/grid/reftests/z-order-1.xul
new file mode 100644
index 000000000..d38ef9f4a
--- /dev/null
+++ b/layout/xul/grid/reftests/z-order-1.xul
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <columns>
+ <column style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ <rows>
+ <row style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/z-order-2-ref.xul b/layout/xul/grid/reftests/z-order-2-ref.xul
new file mode 100644
index 000000000..5b0793d6d
--- /dev/null
+++ b/layout/xul/grid/reftests/z-order-2-ref.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <rows>
+ <row>
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </row>
+ <row>
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 102, 153)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </row>
+ <row>
+ <hbox style="background: rgb(0, 0, 153)" />
+ <hbox style="background: rgb(0, 0, 153)" />
+ <hbox style="background: rgb(0, 0, 0)" />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/grid/reftests/z-order-2.xul b/layout/xul/grid/reftests/z-order-2.xul
new file mode 100644
index 000000000..b2c270d6b
--- /dev/null
+++ b/layout/xul/grid/reftests/z-order-2.xul
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL Grid Test">
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ window { background: black; }
+ hbox { height: 100px; width: 100px; }
+ ]]></style>
+ <hbox>
+ <grid>
+ <rows>
+ <row style="background: rgb(0, 255, 0)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ <row>
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ <hbox style="background: rgb(0, 255, 0)" />
+ </row>
+ <row>
+ <hbox />
+ <hbox />
+ <hbox />
+ </row>
+ </rows>
+ <columns>
+ <column style="background: rgba(0, 0, 255, 0.6)">
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ <column>
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ <hbox style="background: rgba(0, 0, 255, 0.6)" />
+ </column>
+ <column>
+ <hbox />
+ <hbox />
+ <hbox />
+ </column>
+ </columns>
+ </grid>
+ </hbox>
+</window>
diff --git a/layout/xul/moz.build b/layout/xul/moz.build
new file mode 100644
index 000000000..8ed304c9f
--- /dev/null
+++ b/layout/xul/moz.build
@@ -0,0 +1,107 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL')
+
+with Files('*Menu*'):
+ BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: Menus')
+
+if CONFIG['ENABLE_TESTS']:
+ MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+ BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+XPIDL_SOURCES += [
+ 'nsIBoxObject.idl',
+ 'nsIBrowserBoxObject.idl',
+ 'nsIContainerBoxObject.idl',
+ 'nsIListBoxObject.idl',
+ 'nsIMenuBoxObject.idl',
+ 'nsIScrollBoxObject.idl',
+ 'nsISliderListener.idl',
+]
+
+XPIDL_MODULE = 'layout_xul'
+
+EXPORTS += [
+ 'nsBox.h',
+ 'nsIScrollbarMediator.h',
+ 'nsPIBoxObject.h',
+ 'nsPIListBoxObject.h',
+ 'nsXULPopupManager.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'BoxObject.h',
+ 'ContainerBoxObject.h',
+ 'ListBoxObject.h',
+ 'MenuBoxObject.h',
+ 'PopupBoxObject.h',
+ 'ScrollBoxObject.h',
+]
+
+UNIFIED_SOURCES += [
+ 'BoxObject.cpp',
+ 'nsBox.cpp',
+ 'nsBoxFrame.cpp',
+ 'nsBoxLayout.cpp',
+ 'nsBoxLayoutState.cpp',
+ 'nsButtonBoxFrame.cpp',
+ 'nsRepeatService.cpp',
+ 'nsRootBoxFrame.cpp',
+ 'nsScrollbarButtonFrame.cpp',
+ 'nsScrollbarFrame.cpp',
+ 'nsScrollBoxFrame.cpp',
+ 'nsSliderFrame.cpp',
+ 'nsSprocketLayout.cpp',
+ 'nsStackFrame.cpp',
+ 'nsStackLayout.cpp',
+ 'nsXULTooltipListener.cpp',
+]
+
+if CONFIG['MOZ_XUL']:
+ UNIFIED_SOURCES += [
+ 'ContainerBoxObject.cpp',
+ 'ListBoxObject.cpp',
+ 'MenuBoxObject.cpp',
+ 'nsDeckFrame.cpp',
+ 'nsDocElementBoxFrame.cpp',
+ 'nsGroupBoxFrame.cpp',
+ 'nsImageBoxFrame.cpp',
+ 'nsLeafBoxFrame.cpp',
+ 'nsListBoxBodyFrame.cpp',
+ 'nsListBoxLayout.cpp',
+ 'nsListItemFrame.cpp',
+ 'nsMenuBarFrame.cpp',
+ 'nsMenuBarListener.cpp',
+ 'nsMenuFrame.cpp',
+ 'nsMenuPopupFrame.cpp',
+ 'nsPopupSetFrame.cpp',
+ 'nsProgressMeterFrame.cpp',
+ 'nsResizerFrame.cpp',
+ 'nsSplitterFrame.cpp',
+ 'nsTextBoxFrame.cpp',
+ 'nsTitleBarFrame.cpp',
+ 'nsXULLabelFrame.cpp',
+ 'nsXULPopupManager.cpp',
+ 'PopupBoxObject.cpp',
+ 'ScrollBoxObject.cpp',
+ ]
+
+if CONFIG['MOZ_XUL']:
+ DIRS += ['tree', 'grid']
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '../base',
+ '../generic',
+ '../style',
+ '/dom/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/xul/nsBox.cpp b/layout/xul/nsBox.cpp
new file mode 100644
index 000000000..f7ec5fead
--- /dev/null
+++ b/layout/xul/nsBox.cpp
@@ -0,0 +1,981 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBoxLayoutState.h"
+#include "nsBox.h"
+#include "nsBoxFrame.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsContainerFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMMozNamedAttrMap.h"
+#include "nsIDOMAttr.h"
+#include "nsITheme.h"
+#include "nsIServiceManager.h"
+#include "nsBoxLayout.h"
+#include "FrameLayerBuilder.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+#ifdef DEBUG_LAYOUT
+int32_t gIndent = 0;
+#endif
+
+#ifdef DEBUG_LAYOUT
+void
+nsBoxAddIndents()
+{
+ for(int32_t i=0; i < gIndent; i++)
+ {
+ printf(" ");
+ }
+}
+#endif
+
+#ifdef DEBUG_LAYOUT
+void
+nsBox::AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult)
+{
+ aResult.Append(aAttribute);
+ aResult.AppendLiteral("='");
+ aResult.Append(aValue);
+ aResult.AppendLiteral("' ");
+}
+
+void
+nsBox::ListBox(nsAutoString& aResult)
+{
+ nsAutoString name;
+ GetBoxName(name);
+
+ char addr[100];
+ sprintf(addr, "[@%p] ", static_cast<void*>(this));
+
+ aResult.AppendASCII(addr);
+ aResult.Append(name);
+ aResult.Append(' ');
+
+ nsIContent* content = GetContent();
+
+ // add on all the set attributes
+ if (content) {
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
+ nsCOMPtr<nsIDOMMozNamedAttrMap> namedMap;
+
+ node->GetAttributes(getter_AddRefs(namedMap));
+ uint32_t length;
+ namedMap->GetLength(&length);
+
+ nsCOMPtr<nsIDOMAttr> attribute;
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ namedMap->Item(i, getter_AddRefs(attribute));
+ attribute->GetName(name);
+ nsAutoString value;
+ attribute->GetValue(value);
+ AppendAttribute(name, value, aResult);
+ }
+ }
+}
+
+nsresult
+nsBox::XULDumpBox(FILE* aFile)
+{
+ nsAutoString s;
+ ListBox(s);
+ fprintf(aFile, "%s", NS_LossyConvertUTF16toASCII(s).get());
+ return NS_OK;
+}
+
+void
+nsBox::PropagateDebug(nsBoxLayoutState& aState)
+{
+ // propagate debug information
+ if (mState & NS_STATE_DEBUG_WAS_SET) {
+ if (mState & NS_STATE_SET_TO_DEBUG)
+ SetXULDebug(aState, true);
+ else
+ SetXULDebug(aState, false);
+ } else if (mState & NS_STATE_IS_ROOT) {
+ SetXULDebug(aState, gDebug);
+ }
+}
+#endif
+
+#ifdef DEBUG_LAYOUT
+void
+nsBox::GetBoxName(nsAutoString& aName)
+{
+ aName.AssignLiteral("Box");
+}
+#endif
+
+nsresult
+nsBox::BeginXULLayout(nsBoxLayoutState& aState)
+{
+#ifdef DEBUG_LAYOUT
+
+ nsBoxAddIndents();
+ printf("XULLayout: ");
+ XULDumpBox(stdout);
+ printf("\n");
+ gIndent++;
+#endif
+
+ // mark ourselves as dirty so no child under us
+ // can post an incremental layout.
+ // XXXldb Is this still needed?
+ mState |= NS_FRAME_HAS_DIRTY_CHILDREN;
+
+ if (GetStateBits() & NS_FRAME_IS_DIRTY)
+ {
+ // If the parent is dirty, all the children are dirty (ReflowInput
+ // does this too).
+ nsIFrame* box;
+ for (box = GetChildXULBox(this); box; box = GetNextXULBox(box))
+ box->AddStateBits(NS_FRAME_IS_DIRTY);
+ }
+
+ // Another copy-over from ReflowInput.
+ // Since we are in reflow, we don't need to store these properties anymore.
+ FrameProperties props = Properties();
+ props.Delete(UsedBorderProperty());
+ props.Delete(UsedPaddingProperty());
+ props.Delete(UsedMarginProperty());
+
+#ifdef DEBUG_LAYOUT
+ PropagateDebug(aState);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBox::DoXULLayout(nsBoxLayoutState& aState)
+{
+ return NS_OK;
+}
+
+nsresult
+nsBox::EndXULLayout(nsBoxLayoutState& aState)
+{
+
+ #ifdef DEBUG_LAYOUT
+ --gIndent;
+ #endif
+
+ return SyncLayout(aState);
+}
+
+bool nsBox::gGotTheme = false;
+nsITheme* nsBox::gTheme = nullptr;
+
+nsBox::nsBox()
+{
+ MOZ_COUNT_CTOR(nsBox);
+ //mX = 0;
+ //mY = 0;
+ if (!gGotTheme) {
+ gGotTheme = true;
+ CallGetService("@mozilla.org/chrome/chrome-native-theme;1", &gTheme);
+ }
+}
+
+nsBox::~nsBox()
+{
+ // NOTE: This currently doesn't get called for |nsBoxToBlockAdaptor|
+ // objects, so don't rely on putting anything here.
+ MOZ_COUNT_DTOR(nsBox);
+}
+
+/* static */ void
+nsBox::Shutdown()
+{
+ gGotTheme = false;
+ NS_IF_RELEASE(gTheme);
+}
+
+nsresult
+nsBox::XULRelayoutChildAtOrdinal(nsIFrame* aChild)
+{
+ return NS_OK;
+}
+
+nsresult
+nsIFrame::GetXULClientRect(nsRect& aClientRect)
+{
+ aClientRect = mRect;
+ aClientRect.MoveTo(0,0);
+
+ nsMargin borderPadding;
+ GetXULBorderAndPadding(borderPadding);
+
+ aClientRect.Deflate(borderPadding);
+
+ if (aClientRect.width < 0)
+ aClientRect.width = 0;
+
+ if (aClientRect.height < 0)
+ aClientRect.height = 0;
+
+ // NS_ASSERTION(aClientRect.width >=0 && aClientRect.height >= 0, "Content Size < 0");
+
+ return NS_OK;
+}
+
+void
+nsBox::SetXULBounds(nsBoxLayoutState& aState, const nsRect& aRect, bool aRemoveOverflowAreas)
+{
+ NS_BOX_ASSERTION(this, aRect.width >=0 && aRect.height >= 0, "SetXULBounds Size < 0");
+
+ nsRect rect(mRect);
+
+ uint32_t flags = GetXULLayoutFlags();
+
+ uint32_t stateFlags = aState.LayoutFlags();
+
+ flags |= stateFlags;
+
+ if ((flags & NS_FRAME_NO_MOVE_FRAME) == NS_FRAME_NO_MOVE_FRAME)
+ SetSize(aRect.Size());
+ else
+ SetRect(aRect);
+
+ // Nuke the overflow area. The caller is responsible for restoring
+ // it if necessary.
+ if (aRemoveOverflowAreas) {
+ // remove the previously stored overflow area
+ ClearOverflowRects();
+ }
+
+ if (!(flags & NS_FRAME_NO_MOVE_VIEW))
+ {
+ nsContainerFrame::PositionFrameView(this);
+ if ((rect.x != aRect.x) || (rect.y != aRect.y))
+ nsContainerFrame::PositionChildViews(this);
+ }
+
+
+ /*
+ // only if the origin changed
+ if ((rect.x != aRect.x) || (rect.y != aRect.y)) {
+ if (frame->HasView()) {
+ nsContainerFrame::PositionFrameView(presContext, frame,
+ frame->GetView());
+ } else {
+ nsContainerFrame::PositionChildViews(presContext, frame);
+ }
+ }
+ */
+}
+
+nsresult
+nsIFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding)
+{
+ aBorderAndPadding.SizeTo(0, 0, 0, 0);
+ nsresult rv = GetXULBorder(aBorderAndPadding);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMargin padding;
+ rv = GetXULPadding(padding);
+ if (NS_FAILED(rv))
+ return rv;
+
+ aBorderAndPadding += padding;
+
+ return rv;
+}
+
+nsresult
+nsBox::GetXULBorder(nsMargin& aMargin)
+{
+ aMargin.SizeTo(0,0,0,0);
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (disp->mAppearance && gTheme) {
+ // Go to the theme for the border.
+ nsPresContext *context = PresContext();
+ if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) {
+ nsIntMargin margin(0, 0, 0, 0);
+ gTheme->GetWidgetBorder(context->DeviceContext(), this,
+ disp->mAppearance, &margin);
+ aMargin.top = context->DevPixelsToAppUnits(margin.top);
+ aMargin.right = context->DevPixelsToAppUnits(margin.right);
+ aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom);
+ aMargin.left = context->DevPixelsToAppUnits(margin.left);
+ return NS_OK;
+ }
+ }
+
+ aMargin = StyleBorder()->GetComputedBorder();
+
+ return NS_OK;
+}
+
+nsresult
+nsBox::GetXULPadding(nsMargin& aMargin)
+{
+ const nsStyleDisplay *disp = StyleDisplay();
+ if (disp->mAppearance && gTheme) {
+ // Go to the theme for the padding.
+ nsPresContext *context = PresContext();
+ if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) {
+ nsIntMargin margin(0, 0, 0, 0);
+ bool useThemePadding;
+
+ useThemePadding = gTheme->GetWidgetPadding(context->DeviceContext(),
+ this, disp->mAppearance,
+ &margin);
+ if (useThemePadding) {
+ aMargin.top = context->DevPixelsToAppUnits(margin.top);
+ aMargin.right = context->DevPixelsToAppUnits(margin.right);
+ aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom);
+ aMargin.left = context->DevPixelsToAppUnits(margin.left);
+ return NS_OK;
+ }
+ }
+ }
+
+ aMargin.SizeTo(0,0,0,0);
+ StylePadding()->GetPadding(aMargin);
+
+ return NS_OK;
+}
+
+nsresult
+nsBox::GetXULMargin(nsMargin& aMargin)
+{
+ aMargin.SizeTo(0,0,0,0);
+ StyleMargin()->GetMargin(aMargin);
+
+ return NS_OK;
+}
+
+void
+nsBox::SizeNeedsRecalc(nsSize& aSize)
+{
+ aSize.width = -1;
+ aSize.height = -1;
+}
+
+void
+nsBox::CoordNeedsRecalc(nscoord& aFlex)
+{
+ aFlex = -1;
+}
+
+bool
+nsBox::DoesNeedRecalc(const nsSize& aSize)
+{
+ return (aSize.width == -1 || aSize.height == -1);
+}
+
+bool
+nsBox::DoesNeedRecalc(nscoord aCoord)
+{
+ return (aCoord == -1);
+}
+
+nsSize
+nsBox::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context");
+
+ nsSize pref(0,0);
+ DISPLAY_PREF_SIZE(this, pref);
+
+ if (IsXULCollapsed())
+ return pref;
+
+ AddBorderAndPadding(pref);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULPrefSize(this, pref, widthSet, heightSet);
+
+ nsSize minSize = GetXULMinSize(aState);
+ nsSize maxSize = GetXULMaxSize(aState);
+ return BoundsCheck(minSize, pref, maxSize);
+}
+
+nsSize
+nsBox::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context");
+
+ nsSize min(0,0);
+ DISPLAY_MIN_SIZE(this, min);
+
+ if (IsXULCollapsed())
+ return min;
+
+ AddBorderAndPadding(min);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(aState, this, min, widthSet, heightSet);
+ return min;
+}
+
+nsSize
+nsBox::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
+{
+ return nsSize(0, 0);
+}
+
+nsSize
+nsBox::GetXULMaxSize(nsBoxLayoutState& aState)
+{
+ NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context");
+
+ nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ DISPLAY_MAX_SIZE(this, maxSize);
+
+ if (IsXULCollapsed())
+ return maxSize;
+
+ AddBorderAndPadding(maxSize);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMaxSize(this, maxSize, widthSet, heightSet);
+ return maxSize;
+}
+
+nscoord
+nsBox::GetXULFlex()
+{
+ nscoord flex = 0;
+
+ nsIFrame::AddXULFlex(this, flex);
+
+ return flex;
+}
+
+uint32_t
+nsIFrame::GetXULOrdinal()
+{
+ uint32_t ordinal = StyleXUL()->mBoxOrdinal;
+
+ // When present, attribute value overrides CSS.
+ nsIContent* content = GetContent();
+ if (content && content->IsXULElement()) {
+ nsresult error;
+ nsAutoString value;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, value);
+ if (!value.IsEmpty()) {
+ ordinal = value.ToInteger(&error);
+ }
+ }
+
+ return ordinal;
+}
+
+nscoord
+nsBox::GetXULBoxAscent(nsBoxLayoutState& aState)
+{
+ if (IsXULCollapsed())
+ return 0;
+
+ return GetXULPrefSize(aState).height;
+}
+
+bool
+nsBox::IsXULCollapsed()
+{
+ return StyleVisibility()->mVisible == NS_STYLE_VISIBILITY_COLLAPSE;
+}
+
+nsresult
+nsIFrame::XULLayout(nsBoxLayoutState& aState)
+{
+ NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context");
+
+ nsBox *box = static_cast<nsBox*>(this);
+ DISPLAY_LAYOUT(box);
+
+ box->BeginXULLayout(aState);
+
+ box->DoXULLayout(aState);
+
+ box->EndXULLayout(aState);
+
+ return NS_OK;
+}
+
+bool
+nsBox::DoesClipChildren()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+ NS_ASSERTION((display->mOverflowY == NS_STYLE_OVERFLOW_CLIP) ==
+ (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP),
+ "If one overflow is clip, the other should be too");
+ return display->mOverflowX == NS_STYLE_OVERFLOW_CLIP;
+}
+
+nsresult
+nsBox::SyncLayout(nsBoxLayoutState& aState)
+{
+ /*
+ if (IsXULCollapsed()) {
+ CollapseChild(aState, this, true);
+ return NS_OK;
+ }
+ */
+
+
+ if (GetStateBits() & NS_FRAME_IS_DIRTY)
+ XULRedraw(aState);
+
+ RemoveStateBits(NS_FRAME_HAS_DIRTY_CHILDREN | NS_FRAME_IS_DIRTY
+ | NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW);
+
+ nsPresContext* presContext = aState.PresContext();
+
+ uint32_t flags = GetXULLayoutFlags();
+
+ uint32_t stateFlags = aState.LayoutFlags();
+
+ flags |= stateFlags;
+
+ nsRect visualOverflow;
+
+ if (ComputesOwnOverflowArea()) {
+ visualOverflow = GetVisualOverflowRect();
+ }
+ else {
+ nsRect rect(nsPoint(0, 0), GetSize());
+ nsOverflowAreas overflowAreas(rect, rect);
+ if (!DoesClipChildren() && !IsXULCollapsed()) {
+ // See if our child frames caused us to overflow after being laid
+ // out. If so, store the overflow area. This normally can't happen
+ // in XUL, but it can happen with the CSS 'outline' property and
+ // possibly with other exotic stuff (e.g. relatively positioned
+ // frames in HTML inside XUL).
+ nsLayoutUtils::UnionChildOverflow(this, overflowAreas);
+ }
+
+ FinishAndStoreOverflow(overflowAreas, GetSize());
+ visualOverflow = overflowAreas.VisualOverflow();
+ }
+
+ nsView* view = GetView();
+ if (view) {
+ // Make sure the frame's view is properly sized and positioned and has
+ // things like opacity correct
+ nsContainerFrame::SyncFrameViewAfterReflow(presContext, this, view,
+ visualOverflow, flags);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIFrame::XULRedraw(nsBoxLayoutState& aState)
+{
+ if (aState.PaintingDisabled())
+ return NS_OK;
+
+ // nsStackLayout, at least, expects us to repaint descendants even
+ // if a damage rect is provided
+ InvalidateFrameSubtree();
+
+ return NS_OK;
+}
+
+bool
+nsIFrame::AddXULPrefSize(nsIFrame* aBox, nsSize& aSize, bool &aWidthSet, bool &aHeightSet)
+{
+ aWidthSet = false;
+ aHeightSet = false;
+
+ // add in the css min, max, pref
+ const nsStylePosition* position = aBox->StylePosition();
+
+ // see if the width or height was specifically set
+ // XXX Handle eStyleUnit_Enumerated?
+ // (Handling the eStyleUnit_Enumerated types requires
+ // GetXULPrefSize/GetXULMinSize methods that don't consider
+ // (min-/max-/)(width/height) properties.)
+ const nsStyleCoord &width = position->mWidth;
+ if (width.GetUnit() == eStyleUnit_Coord) {
+ aSize.width = width.GetCoordValue();
+ aWidthSet = true;
+ } else if (width.IsCalcUnit()) {
+ if (!width.CalcHasPercent()) {
+ // pass 0 for percentage basis since we know there are no %s
+ aSize.width = nsRuleNode::ComputeComputedCalc(width, 0);
+ if (aSize.width < 0)
+ aSize.width = 0;
+ aWidthSet = true;
+ }
+ }
+
+ const nsStyleCoord &height = position->mHeight;
+ if (height.GetUnit() == eStyleUnit_Coord) {
+ aSize.height = height.GetCoordValue();
+ aHeightSet = true;
+ } else if (height.IsCalcUnit()) {
+ if (!height.CalcHasPercent()) {
+ // pass 0 for percentage basis since we know there are no %s
+ aSize.height = nsRuleNode::ComputeComputedCalc(height, 0);
+ if (aSize.height < 0)
+ aSize.height = 0;
+ aHeightSet = true;
+ }
+ }
+
+ nsIContent* content = aBox->GetContent();
+ // ignore 'height' and 'width' attributes if the actual element is not XUL
+ // For example, we might be magic XUL frames whose primary content is an HTML
+ // <select>
+ if (content && content->IsXULElement()) {
+ nsAutoString value;
+ nsresult error;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+
+ aSize.width =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ aWidthSet = true;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+
+ aSize.height =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ aHeightSet = true;
+ }
+ }
+
+ return (aWidthSet && aHeightSet);
+}
+
+
+bool
+nsIFrame::AddXULMinSize(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize& aSize,
+ bool &aWidthSet, bool &aHeightSet)
+{
+ aWidthSet = false;
+ aHeightSet = false;
+
+ bool canOverride = true;
+
+ // See if a native theme wants to supply a minimum size.
+ const nsStyleDisplay* display = aBox->StyleDisplay();
+ if (display->mAppearance) {
+ nsITheme *theme = aState.PresContext()->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aState.PresContext(), aBox, display->mAppearance)) {
+ LayoutDeviceIntSize size;
+ theme->GetMinimumWidgetSize(aState.PresContext(), aBox,
+ display->mAppearance, &size, &canOverride);
+ if (size.width) {
+ aSize.width = aState.PresContext()->DevPixelsToAppUnits(size.width);
+ aWidthSet = true;
+ }
+ if (size.height) {
+ aSize.height = aState.PresContext()->DevPixelsToAppUnits(size.height);
+ aHeightSet = true;
+ }
+ }
+ }
+
+ // add in the css min, max, pref
+ const nsStylePosition* position = aBox->StylePosition();
+
+ // same for min size. Unfortunately min size is always set to 0. So for now
+ // we will assume 0 (as a coord) means not set.
+ const nsStyleCoord &minWidth = position->mMinWidth;
+ if ((minWidth.GetUnit() == eStyleUnit_Coord &&
+ minWidth.GetCoordValue() != 0) ||
+ (minWidth.IsCalcUnit() && !minWidth.CalcHasPercent())) {
+ nscoord min = nsRuleNode::ComputeCoordPercentCalc(minWidth, 0);
+ if (!aWidthSet || (min > aSize.width && canOverride)) {
+ aSize.width = min;
+ aWidthSet = true;
+ }
+ } else if (minWidth.GetUnit() == eStyleUnit_Percent) {
+ NS_ASSERTION(minWidth.GetPercentValue() == 0.0f,
+ "Non-zero percentage values not currently supported");
+ aSize.width = 0;
+ aWidthSet = true; // FIXME: should we really do this for
+ // nonzero values?
+ }
+ // XXX Handle eStyleUnit_Enumerated?
+ // (Handling the eStyleUnit_Enumerated types requires
+ // GetXULPrefSize/GetXULMinSize methods that don't consider
+ // (min-/max-/)(width/height) properties.
+ // calc() with percentage is treated like '0' (unset)
+
+ const nsStyleCoord &minHeight = position->mMinHeight;
+ if ((minHeight.GetUnit() == eStyleUnit_Coord &&
+ minHeight.GetCoordValue() != 0) ||
+ (minHeight.IsCalcUnit() && !minHeight.CalcHasPercent())) {
+ nscoord min = nsRuleNode::ComputeCoordPercentCalc(minHeight, 0);
+ if (!aHeightSet || (min > aSize.height && canOverride)) {
+ aSize.height = min;
+ aHeightSet = true;
+ }
+ } else if (minHeight.GetUnit() == eStyleUnit_Percent) {
+ NS_ASSERTION(position->mMinHeight.GetPercentValue() == 0.0f,
+ "Non-zero percentage values not currently supported");
+ aSize.height = 0;
+ aHeightSet = true; // FIXME: should we really do this for
+ // nonzero values?
+ }
+ // calc() with percentage is treated like '0' (unset)
+
+ nsIContent* content = aBox->GetContent();
+ if (content && content->IsXULElement()) {
+ nsAutoString value;
+ nsresult error;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::minwidth, value);
+ if (!value.IsEmpty())
+ {
+ value.Trim("%");
+
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ if (val > aSize.width)
+ aSize.width = val;
+ aWidthSet = true;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::minheight, value);
+ if (!value.IsEmpty())
+ {
+ value.Trim("%");
+
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ if (val > aSize.height)
+ aSize.height = val;
+
+ aHeightSet = true;
+ }
+ }
+
+ return (aWidthSet && aHeightSet);
+}
+
+bool
+nsIFrame::AddXULMaxSize(nsIFrame* aBox, nsSize& aSize, bool &aWidthSet, bool &aHeightSet)
+{
+ aWidthSet = false;
+ aHeightSet = false;
+
+ // add in the css min, max, pref
+ const nsStylePosition* position = aBox->StylePosition();
+
+ // and max
+ // see if the width or height was specifically set
+ // XXX Handle eStyleUnit_Enumerated?
+ // (Handling the eStyleUnit_Enumerated types requires
+ // GetXULPrefSize/GetXULMinSize methods that don't consider
+ // (min-/max-/)(width/height) properties.)
+ const nsStyleCoord maxWidth = position->mMaxWidth;
+ if (maxWidth.ConvertsToLength()) {
+ aSize.width = nsRuleNode::ComputeCoordPercentCalc(maxWidth, 0);
+ aWidthSet = true;
+ }
+ // percentages and calc() with percentages are treated like 'none'
+
+ const nsStyleCoord &maxHeight = position->mMaxHeight;
+ if (maxHeight.ConvertsToLength()) {
+ aSize.height = nsRuleNode::ComputeCoordPercentCalc(maxHeight, 0);
+ aHeightSet = true;
+ }
+ // percentages and calc() with percentages are treated like 'none'
+
+ nsIContent* content = aBox->GetContent();
+ if (content && content->IsXULElement()) {
+ nsAutoString value;
+ nsresult error;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::maxwidth, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ aSize.width = val;
+ aWidthSet = true;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::maxheight, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ aSize.height = val;
+
+ aHeightSet = true;
+ }
+ }
+
+ return (aWidthSet || aHeightSet);
+}
+
+bool
+nsIFrame::AddXULFlex(nsIFrame* aBox, nscoord& aFlex)
+{
+ bool flexSet = false;
+
+ // get the flexibility
+ aFlex = aBox->StyleXUL()->mBoxFlex;
+
+ // attribute value overrides CSS
+ nsIContent* content = aBox->GetContent();
+ if (content && content->IsXULElement()) {
+ nsresult error;
+ nsAutoString value;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::flex, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ aFlex = value.ToInteger(&error);
+ flexSet = true;
+ }
+ }
+
+ if (aFlex < 0)
+ aFlex = 0;
+ if (aFlex >= nscoord_MAX)
+ aFlex = nscoord_MAX - 1;
+
+ return flexSet || aFlex > 0;
+}
+
+void
+nsBox::AddBorderAndPadding(nsSize& aSize)
+{
+ AddBorderAndPadding(this, aSize);
+}
+
+void
+nsBox::AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize)
+{
+ nsMargin borderPadding(0,0,0,0);
+ aBox->GetXULBorderAndPadding(borderPadding);
+ AddMargin(aSize, borderPadding);
+}
+
+void
+nsBox::AddMargin(nsIFrame* aChild, nsSize& aSize)
+{
+ nsMargin margin(0,0,0,0);
+ aChild->GetXULMargin(margin);
+ AddMargin(aSize, margin);
+}
+
+void
+nsBox::AddMargin(nsSize& aSize, const nsMargin& aMargin)
+{
+ if (aSize.width != NS_INTRINSICSIZE)
+ aSize.width += aMargin.left + aMargin.right;
+
+ if (aSize.height != NS_INTRINSICSIZE)
+ aSize.height += aMargin.top + aMargin.bottom;
+}
+
+nscoord
+nsBox::BoundsCheck(nscoord aMin, nscoord aPref, nscoord aMax)
+{
+ if (aPref > aMax)
+ aPref = aMax;
+
+ if (aPref < aMin)
+ aPref = aMin;
+
+ return aPref;
+}
+
+nsSize
+nsBox::BoundsCheckMinMax(const nsSize& aMinSize, const nsSize& aMaxSize)
+{
+ return nsSize(std::max(aMaxSize.width, aMinSize.width),
+ std::max(aMaxSize.height, aMinSize.height));
+}
+
+nsSize
+nsBox::BoundsCheck(const nsSize& aMinSize, const nsSize& aPrefSize, const nsSize& aMaxSize)
+{
+ return nsSize(BoundsCheck(aMinSize.width, aPrefSize.width, aMaxSize.width),
+ BoundsCheck(aMinSize.height, aPrefSize.height, aMaxSize.height));
+}
+
+/*static*/ nsIFrame*
+nsBox::GetChildXULBox(const nsIFrame* aFrame)
+{
+ // box layout ends at box-wrapped frames, so don't allow these frames
+ // to report child boxes.
+ return aFrame->IsXULBoxFrame() ? aFrame->PrincipalChildList().FirstChild() : nullptr;
+}
+
+/*static*/ nsIFrame*
+nsBox::GetNextXULBox(const nsIFrame* aFrame)
+{
+ return aFrame->GetParent() &&
+ aFrame->GetParent()->IsXULBoxFrame() ? aFrame->GetNextSibling() : nullptr;
+}
+
+/*static*/ nsIFrame*
+nsBox::GetParentXULBox(const nsIFrame* aFrame)
+{
+ return aFrame->GetParent() &&
+ aFrame->GetParent()->IsXULBoxFrame() ? aFrame->GetParent() : nullptr;
+}
+
+#ifdef DEBUG_LAYOUT
+nsresult
+nsBox::SetXULDebug(nsBoxLayoutState& aState, bool aDebug)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBox::GetDebugBoxAt( const nsPoint& aPoint,
+ nsIFrame** aBox)
+{
+ nsRect thisRect(nsPoint(0,0), GetSize());
+ if (!thisRect.Contains(aPoint))
+ return NS_ERROR_FAILURE;
+
+ nsIFrame* child = nsBox::GetChildXULBox(this);
+ nsIFrame* hit = nullptr;
+
+ *aBox = nullptr;
+ while (nullptr != child) {
+ nsresult rv = child->GetDebugBoxAt(aPoint - child->GetOffsetTo(this), &hit);
+
+ if (NS_SUCCEEDED(rv) && hit) {
+ *aBox = hit;
+ }
+ child = GetNextXULBox(child);
+ }
+
+ // found a child
+ if (*aBox) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+nsresult
+nsBox::GetXULDebug(bool& aDebug)
+{
+ aDebug = false;
+ return NS_OK;
+}
+
+#endif
diff --git a/layout/xul/nsBox.h b/layout/xul/nsBox.h
new file mode 100644
index 000000000..bf019c174
--- /dev/null
+++ b/layout/xul/nsBox.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBox_h___
+#define nsBox_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFrame.h"
+
+class nsITheme;
+
+class nsBox : public nsIFrame {
+
+public:
+
+ friend class nsIFrame;
+
+ static void Shutdown();
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetXULFlex() override;
+ virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual nsSize GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual bool IsXULCollapsed() override;
+
+ virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowAreas = false) override;
+
+ virtual nsresult GetXULBorder(nsMargin& aBorderAndPadding) override;
+ virtual nsresult GetXULPadding(nsMargin& aBorderAndPadding) override;
+ virtual nsresult GetXULMargin(nsMargin& aMargin) override;
+
+ virtual Valignment GetXULVAlign() const override { return vAlign_Top; }
+ virtual Halignment GetXULHAlign() const override { return hAlign_Left; }
+
+ virtual nsresult XULRelayoutChildAtOrdinal(nsIFrame* aChild) override;
+
+#ifdef DEBUG_LAYOUT
+ NS_IMETHOD GetDebugBoxAt(const nsPoint& aPoint, nsIFrame** aBox);
+ virtual nsresult GetXULDebug(bool& aDebug) override;
+ virtual nsresult SetXULDebug(nsBoxLayoutState& aState, bool aDebug) override;
+
+ virtual nsresult XULDumpBox(FILE* out) override;
+ void PropagateDebug(nsBoxLayoutState& aState);
+#endif
+
+ nsBox();
+ virtual ~nsBox();
+
+ /**
+ * Returns true if this box clips its children, e.g., if this box is an sc
+rollbox.
+ */
+ virtual bool DoesClipChildren();
+ virtual bool ComputesOwnOverflowArea() = 0;
+
+ nsresult SyncLayout(nsBoxLayoutState& aBoxLayoutState);
+
+ bool DoesNeedRecalc(const nsSize& aSize);
+ bool DoesNeedRecalc(nscoord aCoord);
+ void SizeNeedsRecalc(nsSize& aSize);
+ void CoordNeedsRecalc(nscoord& aCoord);
+
+ void AddBorderAndPadding(nsSize& aSize);
+
+ static void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize);
+ static void AddMargin(nsIFrame* aChild, nsSize& aSize);
+ static void AddMargin(nsSize& aSize, const nsMargin& aMargin);
+
+ static nsSize BoundsCheckMinMax(const nsSize& aMinSize, const nsSize& aMaxSize);
+ static nsSize BoundsCheck(const nsSize& aMinSize, const nsSize& aPrefSize, const nsSize& aMaxSize);
+ static nscoord BoundsCheck(nscoord aMinSize, nscoord aPrefSize, nscoord aMaxSize);
+
+ static nsIFrame* GetChildXULBox(const nsIFrame* aFrame);
+ static nsIFrame* GetNextXULBox(const nsIFrame* aFrame);
+ static nsIFrame* GetParentXULBox(const nsIFrame* aFrame);
+
+protected:
+
+#ifdef DEBUG_LAYOUT
+ virtual void AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult);
+
+ virtual void ListBox(nsAutoString& aResult);
+#endif
+
+ nsresult BeginXULLayout(nsBoxLayoutState& aState);
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState);
+ nsresult EndXULLayout(nsBoxLayoutState& aState);
+
+#ifdef DEBUG_LAYOUT
+ virtual void GetBoxName(nsAutoString& aName);
+ void PropagateDebug(nsBoxLayoutState& aState);
+#endif
+
+ static bool gGotTheme;
+ static nsITheme* gTheme;
+
+ enum eMouseThrough {
+ unset,
+ never,
+ always
+ };
+
+private:
+
+ //nscoord mX;
+ //nscoord mY;
+};
+
+#ifdef DEBUG_LAYOUT
+#define NS_BOX_ASSERTION(box,expr,str) \
+ if (!(expr)) { \
+ box->XULDumpBox(stdout); \
+ NS_DebugBreak(NSDebugAssertion, str, #expr, __FILE__, __LINE__); \
+ }
+#else
+#define NS_BOX_ASSERTION(box,expr,str) {}
+#endif
+
+#endif
+
diff --git a/layout/xul/nsBoxFrame.cpp b/layout/xul/nsBoxFrame.cpp
new file mode 100644
index 000000000..9ca351d94
--- /dev/null
+++ b/layout/xul/nsBoxFrame.cpp
@@ -0,0 +1,2111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+// How boxes layout
+// ----------------
+// Boxes layout a bit differently than html. html does a bottom up layout. Where boxes do a top down.
+// 1) First thing a box does it goes out and askes each child for its min, max, and preferred sizes.
+// 2) It then adds them up to determine its size.
+// 3) If the box was asked to layout it self intrinically it will layout its children at their preferred size
+// otherwise it will layout the child at the size it was told to. It will squeeze or stretch its children if
+// Necessary.
+//
+// However there is a catch. Some html components like block frames can not determine their preferred size.
+// this is their size if they were laid out intrinsically. So the box will flow the child to determine this can
+// cache the value.
+
+// Boxes and Incremental Reflow
+// ----------------------------
+// Boxes layout out top down by adding up their children's min, max, and preferred sizes. Only problem is if a incremental
+// reflow occurs. The preferred size of a child deep in the hierarchy could change. And this could change
+// any number of syblings around the box. Basically any children in the reflow chain must have their caches cleared
+// so when asked for there current size they can relayout themselves.
+
+#include "nsBoxFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsBoxLayoutState.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/Move.h"
+#include "nsStyleContext.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsHTMLParts.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsIPresShell.h"
+#include "nsCSSRendering.h"
+#include "nsIServiceManager.h"
+#include "nsBoxLayout.h"
+#include "nsSprocketLayout.h"
+#include "nsIScrollableFrame.h"
+#include "nsWidgetsCID.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsContainerFrame.h"
+#include "nsIDOMElement.h"
+#include "nsITheme.h"
+#include "nsTransform2D.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIDOMEvent.h"
+#include "nsDisplayList.h"
+#include "mozilla/Preferences.h"
+#include "nsThemeConstants.h"
+#include "nsLayoutUtils.h"
+#include "nsSliderFrame.h"
+#include <algorithm>
+
+// Needed for Print Preview
+#include "nsIURI.h"
+
+#include "mozilla/TouchEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+//define DEBUG_REDRAW
+
+#define DEBUG_SPRING_SIZE 8
+#define DEBUG_BORDER_SIZE 2
+#define COIL_SIZE 8
+
+//#define TEST_SANITY
+
+#ifdef DEBUG_rods
+//#define DO_NOISY_REFLOW
+#endif
+
+#ifdef DEBUG_LAYOUT
+bool nsBoxFrame::gDebug = false;
+nsIFrame* nsBoxFrame::mDebugChild = nullptr;
+#endif
+
+nsIFrame*
+NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot, nsBoxLayout* aLayoutManager)
+{
+ return new (aPresShell) nsBoxFrame(aContext, aIsRoot, aLayoutManager);
+}
+
+nsIFrame*
+NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBoxFrame)
+
+#ifdef DEBUG
+NS_QUERYFRAME_HEAD(nsBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsBoxFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+#endif
+
+nsBoxFrame::nsBoxFrame(nsStyleContext* aContext,
+ bool aIsRoot,
+ nsBoxLayout* aLayoutManager) :
+ nsContainerFrame(aContext)
+{
+ mState |= NS_STATE_IS_HORIZONTAL;
+ mState |= NS_STATE_AUTO_STRETCH;
+
+ if (aIsRoot)
+ mState |= NS_STATE_IS_ROOT;
+
+ mValign = vAlign_Top;
+ mHalign = hAlign_Left;
+
+ // if no layout manager specified us the static sprocket layout
+ nsCOMPtr<nsBoxLayout> layout = aLayoutManager;
+
+ if (layout == nullptr) {
+ NS_NewSprocketLayout(layout);
+ }
+
+ SetXULLayoutManager(layout);
+}
+
+nsBoxFrame::~nsBoxFrame()
+{
+}
+
+void
+nsBoxFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ if (aListID == kPrincipalList) {
+ // initialize our list of infos.
+ nsBoxLayoutState state(PresContext());
+ CheckBoxOrder();
+ if (mLayoutManager)
+ mLayoutManager->ChildrenSet(this, state, mFrames.FirstChild());
+ }
+}
+
+/* virtual */ void
+nsBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ // The values that CacheAttributes() computes depend on our style,
+ // so we need to recompute them here...
+ CacheAttributes();
+}
+
+/**
+ * Initialize us. This is a good time to get the alignment of the box
+ */
+void
+nsBoxFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ MarkIntrinsicISizesDirty();
+
+ CacheAttributes();
+
+#ifdef DEBUG_LAYOUT
+ // if we are root and this
+ if (mState & NS_STATE_IS_ROOT) {
+ GetDebugPref();
+ }
+#endif
+
+ UpdateMouseThrough();
+
+ // register access key
+ RegUnregAccessKey(true);
+}
+
+void nsBoxFrame::UpdateMouseThrough()
+{
+ if (mContent) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::never, &nsGkAtoms::always, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::mousethrough, strings, eCaseMatters)) {
+ case 0: AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); break;
+ case 1: AddStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); break;
+ case 2: {
+ RemoveStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS);
+ RemoveStateBits(NS_FRAME_MOUSE_THROUGH_NEVER);
+ break;
+ }
+ }
+ }
+}
+
+void
+nsBoxFrame::CacheAttributes()
+{
+ /*
+ printf("Caching: ");
+ XULDumpBox(stdout);
+ printf("\n");
+ */
+
+ mValign = vAlign_Top;
+ mHalign = hAlign_Left;
+
+ bool orient = false;
+ GetInitialOrientation(orient);
+ if (orient)
+ mState |= NS_STATE_IS_HORIZONTAL;
+ else
+ mState &= ~NS_STATE_IS_HORIZONTAL;
+
+ bool normal = true;
+ GetInitialDirection(normal);
+ if (normal)
+ mState |= NS_STATE_IS_DIRECTION_NORMAL;
+ else
+ mState &= ~NS_STATE_IS_DIRECTION_NORMAL;
+
+ GetInitialVAlignment(mValign);
+ GetInitialHAlignment(mHalign);
+
+ bool equalSize = false;
+ GetInitialEqualSize(equalSize);
+ if (equalSize)
+ mState |= NS_STATE_EQUAL_SIZE;
+ else
+ mState &= ~NS_STATE_EQUAL_SIZE;
+
+ bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH);
+ GetInitialAutoStretch(autostretch);
+ if (autostretch)
+ mState |= NS_STATE_AUTO_STRETCH;
+ else
+ mState &= ~NS_STATE_AUTO_STRETCH;
+
+
+#ifdef DEBUG_LAYOUT
+ bool debug = mState & NS_STATE_SET_TO_DEBUG;
+ bool debugSet = GetInitialDebug(debug);
+ if (debugSet) {
+ mState |= NS_STATE_DEBUG_WAS_SET;
+ if (debug)
+ mState |= NS_STATE_SET_TO_DEBUG;
+ else
+ mState &= ~NS_STATE_SET_TO_DEBUG;
+ } else {
+ mState &= ~NS_STATE_DEBUG_WAS_SET;
+ }
+#endif
+}
+
+#ifdef DEBUG_LAYOUT
+bool
+nsBoxFrame::GetInitialDebug(bool& aDebug)
+{
+ if (!GetContent())
+ return false;
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_false, &nsGkAtoms::_true, nullptr};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::debug, strings, eCaseMatters);
+ if (index >= 0) {
+ aDebug = index == 1;
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+bool
+nsBoxFrame::GetInitialHAlignment(nsBoxFrame::Halignment& aHalign)
+{
+ if (!GetContent())
+ return false;
+
+ // XXXdwh Everything inside this if statement is deprecated code.
+ static nsIContent::AttrValuesArray alignStrings[] =
+ {&nsGkAtoms::left, &nsGkAtoms::right, nullptr};
+ static const Halignment alignValues[] = {hAlign_Left, hAlign_Right};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::align,
+ alignStrings, eCaseMatters);
+ if (index >= 0) {
+ aHalign = alignValues[index];
+ return true;
+ }
+
+ // Now that the deprecated stuff is out of the way, we move on to check the appropriate
+ // attribute. For horizontal boxes, we are checking the PACK attribute. For vertical boxes
+ // we are checking the ALIGN attribute.
+ nsIAtom* attrName = IsXULHorizontal() ? nsGkAtoms::pack : nsGkAtoms::align;
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_empty, &nsGkAtoms::start, &nsGkAtoms::center, &nsGkAtoms::end, nullptr};
+ static const Halignment values[] =
+ {hAlign_Left/*not used*/, hAlign_Left, hAlign_Center, hAlign_Right};
+ index = GetContent()->FindAttrValueIn(kNameSpaceID_None, attrName,
+ strings, eCaseMatters);
+
+ if (index == nsIContent::ATTR_VALUE_NO_MATCH) {
+ // The attr was present but had a nonsensical value. Revert to the default.
+ return false;
+ }
+ if (index > 0) {
+ aHalign = values[index];
+ return true;
+ }
+
+ // Now that we've checked for the attribute it's time to check CSS. For
+ // horizontal boxes we're checking PACK. For vertical boxes we are checking
+ // ALIGN.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (IsXULHorizontal()) {
+ switch (boxInfo->mBoxPack) {
+ case StyleBoxPack::Start:
+ aHalign = nsBoxFrame::hAlign_Left;
+ return true;
+ case StyleBoxPack::Center:
+ aHalign = nsBoxFrame::hAlign_Center;
+ return true;
+ case StyleBoxPack::End:
+ aHalign = nsBoxFrame::hAlign_Right;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+ else {
+ switch (boxInfo->mBoxAlign) {
+ case StyleBoxAlign::Start:
+ aHalign = nsBoxFrame::hAlign_Left;
+ return true;
+ case StyleBoxAlign::Center:
+ aHalign = nsBoxFrame::hAlign_Center;
+ return true;
+ case StyleBoxAlign::End:
+ aHalign = nsBoxFrame::hAlign_Right;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsBoxFrame::GetInitialVAlignment(nsBoxFrame::Valignment& aValign)
+{
+ if (!GetContent())
+ return false;
+
+ static nsIContent::AttrValuesArray valignStrings[] =
+ {&nsGkAtoms::top, &nsGkAtoms::baseline, &nsGkAtoms::middle, &nsGkAtoms::bottom, nullptr};
+ static const Valignment valignValues[] =
+ {vAlign_Top, vAlign_BaseLine, vAlign_Middle, vAlign_Bottom};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::valign,
+ valignStrings, eCaseMatters);
+ if (index >= 0) {
+ aValign = valignValues[index];
+ return true;
+ }
+
+ // Now that the deprecated stuff is out of the way, we move on to check the appropriate
+ // attribute. For horizontal boxes, we are checking the ALIGN attribute. For vertical boxes
+ // we are checking the PACK attribute.
+ nsIAtom* attrName = IsXULHorizontal() ? nsGkAtoms::align : nsGkAtoms::pack;
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_empty, &nsGkAtoms::start, &nsGkAtoms::center,
+ &nsGkAtoms::baseline, &nsGkAtoms::end, nullptr};
+ static const Valignment values[] =
+ {vAlign_Top/*not used*/, vAlign_Top, vAlign_Middle, vAlign_BaseLine, vAlign_Bottom};
+ index = GetContent()->FindAttrValueIn(kNameSpaceID_None, attrName,
+ strings, eCaseMatters);
+ if (index == nsIContent::ATTR_VALUE_NO_MATCH) {
+ // The attr was present but had a nonsensical value. Revert to the default.
+ return false;
+ }
+ if (index > 0) {
+ aValign = values[index];
+ return true;
+ }
+
+ // Now that we've checked for the attribute it's time to check CSS. For
+ // horizontal boxes we're checking ALIGN. For vertical boxes we are checking
+ // PACK.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (IsXULHorizontal()) {
+ switch (boxInfo->mBoxAlign) {
+ case StyleBoxAlign::Start:
+ aValign = nsBoxFrame::vAlign_Top;
+ return true;
+ case StyleBoxAlign::Center:
+ aValign = nsBoxFrame::vAlign_Middle;
+ return true;
+ case StyleBoxAlign::Baseline:
+ aValign = nsBoxFrame::vAlign_BaseLine;
+ return true;
+ case StyleBoxAlign::End:
+ aValign = nsBoxFrame::vAlign_Bottom;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+ else {
+ switch (boxInfo->mBoxPack) {
+ case StyleBoxPack::Start:
+ aValign = nsBoxFrame::vAlign_Top;
+ return true;
+ case StyleBoxPack::Center:
+ aValign = nsBoxFrame::vAlign_Middle;
+ return true;
+ case StyleBoxPack::End:
+ aValign = nsBoxFrame::vAlign_Bottom;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+
+ return false;
+}
+
+void
+nsBoxFrame::GetInitialOrientation(bool& aIsHorizontal)
+{
+ // see if we are a vertical or horizontal box.
+ if (!GetContent())
+ return;
+
+ // Check the style system first.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (boxInfo->mBoxOrient == StyleBoxOrient::Horizontal) {
+ aIsHorizontal = true;
+ } else {
+ aIsHorizontal = false;
+ }
+
+ // Now see if we have an attribute. The attribute overrides
+ // the style system value.
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::vertical, &nsGkAtoms::horizontal, nullptr};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::orient,
+ strings, eCaseMatters);
+ if (index >= 0) {
+ aIsHorizontal = index == 1;
+ }
+}
+
+void
+nsBoxFrame::GetInitialDirection(bool& aIsNormal)
+{
+ if (!GetContent())
+ return;
+
+ if (IsXULHorizontal()) {
+ // For horizontal boxes only, we initialize our value based off the CSS 'direction' property.
+ // This means that BiDI users will end up with horizontally inverted chrome.
+ aIsNormal = (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR); // If text runs RTL then so do we.
+ }
+ else
+ aIsNormal = true; // Assume a normal direction in the vertical case.
+
+ // Now check the style system to see if we should invert aIsNormal.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (boxInfo->mBoxDirection == StyleBoxDirection::Reverse) {
+ aIsNormal = !aIsNormal; // Invert our direction.
+ }
+
+ // Now see if we have an attribute. The attribute overrides
+ // the style system value.
+ if (IsXULHorizontal()) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::reverse, &nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir,
+ strings, eCaseMatters);
+ if (index >= 0) {
+ bool values[] = {!aIsNormal, true, false};
+ aIsNormal = values[index];
+ }
+ } else if (GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::reverse, eCaseMatters)) {
+ aIsNormal = !aIsNormal;
+ }
+}
+
+/* Returns true if it was set.
+ */
+bool
+nsBoxFrame::GetInitialEqualSize(bool& aEqualSize)
+{
+ // see if we are a vertical or horizontal box.
+ if (!GetContent())
+ return false;
+
+ if (GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::equalsize,
+ nsGkAtoms::always, eCaseMatters)) {
+ aEqualSize = true;
+ return true;
+ }
+
+ return false;
+}
+
+/* Returns true if it was set.
+ */
+bool
+nsBoxFrame::GetInitialAutoStretch(bool& aStretch)
+{
+ if (!GetContent())
+ return false;
+
+ // Check the align attribute.
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_empty, &nsGkAtoms::stretch, nullptr};
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::align,
+ strings, eCaseMatters);
+ if (index != nsIContent::ATTR_MISSING && index != 0) {
+ aStretch = index == 1;
+ return true;
+ }
+
+ // Check the CSS box-align property.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ aStretch = (boxInfo->mBoxAlign == StyleBoxAlign::Stretch);
+
+ return true;
+}
+
+void
+nsBoxFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus)
+{
+ nsFrameState preserveBits =
+ mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ nsFrame::DidReflow(aPresContext, aReflowInput, aStatus);
+ mState |= preserveBits;
+}
+
+bool
+nsBoxFrame::HonorPrintBackgroundSettings()
+{
+ return (!mContent || !mContent->IsInNativeAnonymousSubtree()) &&
+ nsContainerFrame::HonorPrintBackgroundSettings();
+}
+
+#ifdef DO_NOISY_REFLOW
+static int myCounter = 0;
+static void printSize(char * aDesc, nscoord aSize)
+{
+ printf(" %s: ", aDesc);
+ if (aSize == NS_UNCONSTRAINEDSIZE) {
+ printf("UC");
+ } else {
+ printf("%d", aSize);
+ }
+}
+#endif
+
+/* virtual */ nscoord
+nsBoxFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+ nsSize minSize = GetXULMinSize(state);
+
+ // GetXULMinSize returns border-box width, and we want to return content
+ // width. Since Reflow uses the reflow state's border and padding, we
+ // actually just want to subtract what GetXULMinSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = minSize.width - bp.LeftRight();
+ result = std::max(result, 0);
+
+ return result;
+}
+
+/* virtual */ nscoord
+nsBoxFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+ nsSize prefSize = GetXULPrefSize(state);
+
+ // GetXULPrefSize returns border-box width, and we want to return content
+ // width. Since Reflow uses the reflow state's border and padding, we
+ // actually just want to subtract what GetXULPrefSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = prefSize.width - bp.LeftRight();
+ result = std::max(result, 0);
+
+ return result;
+}
+
+void
+nsBoxFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ // If you make changes to this method, please keep nsLeafBoxFrame::Reflow
+ // in sync, if the changes are applicable there.
+
+ DO_GLOBAL_REFLOW_COUNT("nsBoxFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(aReflowInput.ComputedWidth() >=0 &&
+ aReflowInput.ComputedHeight() >= 0, "Computed Size < 0");
+
+#ifdef DO_NOISY_REFLOW
+ printf("\n-------------Starting BoxFrame Reflow ----------------------------\n");
+ printf("%p ** nsBF::Reflow %d ", this, myCounter++);
+
+ printSize("AW", aReflowInput.AvailableWidth());
+ printSize("AH", aReflowInput.AvailableHeight());
+ printSize("CW", aReflowInput.ComputedWidth());
+ printSize("CH", aReflowInput.ComputedHeight());
+
+ printf(" *\n");
+
+#endif
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ // create the layout state
+ nsBoxLayoutState state(aPresContext, aReflowInput.mRenderingContext,
+ &aReflowInput, aReflowInput.mReflowDepth);
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize computedSize(wm, aReflowInput.ComputedISize(),
+ aReflowInput.ComputedBSize());
+
+ LogicalMargin m = aReflowInput.ComputedLogicalBorderPadding();
+ // GetXULBorderAndPadding(m);
+
+ LogicalSize prefSize(wm);
+
+ // if we are told to layout intrinsic then get our preferred size.
+ NS_ASSERTION(computedSize.ISize(wm) != NS_INTRINSICSIZE,
+ "computed inline size should always be computed");
+ if (computedSize.BSize(wm) == NS_INTRINSICSIZE) {
+ nsSize physicalPrefSize = GetXULPrefSize(state);
+ nsSize minSize = GetXULMinSize(state);
+ nsSize maxSize = GetXULMaxSize(state);
+ // XXXbz isn't GetXULPrefSize supposed to bounds-check for us?
+ physicalPrefSize = BoundsCheck(minSize, physicalPrefSize, maxSize);
+ prefSize = LogicalSize(wm, physicalPrefSize);
+ }
+
+ // get our desiredSize
+ computedSize.ISize(wm) += m.IStart(wm) + m.IEnd(wm);
+
+ if (aReflowInput.ComputedBSize() == NS_INTRINSICSIZE) {
+ computedSize.BSize(wm) = prefSize.BSize(wm);
+ // prefSize is border-box but min/max constraints are content-box.
+ nscoord blockDirBorderPadding =
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
+ nscoord contentBSize = computedSize.BSize(wm) - blockDirBorderPadding;
+ // Note: contentHeight might be negative, but that's OK because min-height
+ // is never negative.
+ computedSize.BSize(wm) = aReflowInput.ApplyMinMaxHeight(contentBSize) +
+ blockDirBorderPadding;
+ } else {
+ computedSize.BSize(wm) += m.BStart(wm) + m.BEnd(wm);
+ }
+
+ nsSize physicalSize = computedSize.GetPhysicalSize(wm);
+ nsRect r(mRect.x, mRect.y, physicalSize.width, physicalSize.height);
+
+ SetXULBounds(state, r);
+
+ // layout our children
+ XULLayout(state);
+
+ // ok our child could have gotten bigger. So lets get its bounds
+
+ // get the ascent
+ LogicalSize boxSize = GetLogicalSize(wm);
+ nscoord ascent = boxSize.BSize(wm);
+
+ // getting the ascent could be a lot of work. Don't get it if
+ // we are the root. The viewport doesn't care about it.
+ if (!(mState & NS_STATE_IS_ROOT)) {
+ ascent = GetXULBoxAscent(state);
+ }
+
+ aDesiredSize.SetSize(wm, boxSize);
+ aDesiredSize.SetBlockStartAscent(ascent);
+
+ aDesiredSize.mOverflowAreas = GetOverflowAreas();
+
+#ifdef DO_NOISY_REFLOW
+ {
+ printf("%p ** nsBF(done) W:%d H:%d ", this, aDesiredSize.Width(), aDesiredSize.Height());
+
+ if (maxElementSize) {
+ printf("MW:%d\n", *maxElementWidth);
+ } else {
+ printf("MW:?\n");
+ }
+
+ }
+#endif
+
+ ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsSize
+nsBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(0,0);
+ DISPLAY_PREF_SIZE(this, size);
+ if (!DoesNeedRecalc(mPrefSize)) {
+ return mPrefSize;
+ }
+
+#ifdef DEBUG_LAYOUT
+ PropagateDebug(aBoxLayoutState);
+#endif
+
+ if (IsXULCollapsed())
+ return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet))
+ {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState);
+ if (!widthSet)
+ size.width = layoutSize.width;
+ if (!heightSet)
+ size.height = layoutSize.height;
+ }
+ else {
+ size = nsBox::GetXULPrefSize(aBoxLayoutState);
+ }
+ }
+
+ nsSize minSize = GetXULMinSize(aBoxLayoutState);
+ nsSize maxSize = GetXULMaxSize(aBoxLayoutState);
+ mPrefSize = BoundsCheck(minSize, size, maxSize);
+
+ return mPrefSize;
+}
+
+nscoord
+nsBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (!DoesNeedRecalc(mAscent))
+ return mAscent;
+
+#ifdef DEBUG_LAYOUT
+ PropagateDebug(aBoxLayoutState);
+#endif
+
+ if (IsXULCollapsed())
+ return 0;
+
+ if (mLayoutManager)
+ mAscent = mLayoutManager->GetAscent(this, aBoxLayoutState);
+ else
+ mAscent = nsBox::GetXULBoxAscent(aBoxLayoutState);
+
+ return mAscent;
+}
+
+nsSize
+nsBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(0,0);
+ DISPLAY_MIN_SIZE(this, size);
+ if (!DoesNeedRecalc(mMinSize)) {
+ return mMinSize;
+ }
+
+#ifdef DEBUG_LAYOUT
+ PropagateDebug(aBoxLayoutState);
+#endif
+
+ if (IsXULCollapsed())
+ return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULMinSize(aBoxLayoutState, this, size, widthSet, heightSet))
+ {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULMinSize(this, aBoxLayoutState);
+ if (!widthSet)
+ size.width = layoutSize.width;
+ if (!heightSet)
+ size.height = layoutSize.height;
+ }
+ else {
+ size = nsBox::GetXULMinSize(aBoxLayoutState);
+ }
+ }
+
+ mMinSize = size;
+
+ return size;
+}
+
+nsSize
+nsBoxFrame::GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ DISPLAY_MAX_SIZE(this, size);
+ if (!DoesNeedRecalc(mMaxSize)) {
+ return mMaxSize;
+ }
+
+#ifdef DEBUG_LAYOUT
+ PropagateDebug(aBoxLayoutState);
+#endif
+
+ if (IsXULCollapsed())
+ return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULMaxSize(this, size, widthSet, heightSet))
+ {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULMaxSize(this, aBoxLayoutState);
+ if (!widthSet)
+ size.width = layoutSize.width;
+ if (!heightSet)
+ size.height = layoutSize.height;
+ }
+ else {
+ size = nsBox::GetXULMaxSize(aBoxLayoutState);
+ }
+ }
+
+ mMaxSize = size;
+
+ return size;
+}
+
+nscoord
+nsBoxFrame::GetXULFlex()
+{
+ if (!DoesNeedRecalc(mFlex))
+ return mFlex;
+
+ mFlex = nsBox::GetXULFlex();
+
+ return mFlex;
+}
+
+/**
+ * If subclassing please subclass this method not layout.
+ * layout will call this method.
+ */
+NS_IMETHODIMP
+nsBoxFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ uint32_t oldFlags = aState.LayoutFlags();
+ aState.SetLayoutFlags(0);
+
+ nsresult rv = NS_OK;
+ if (mLayoutManager) {
+ CoordNeedsRecalc(mAscent);
+ rv = mLayoutManager->XULLayout(this, aState);
+ }
+
+ aState.SetLayoutFlags(oldFlags);
+
+ if (HasAbsolutelyPositionedChildren()) {
+ // Set up a |reflowInput| to pass into ReflowAbsoluteFrames
+ WritingMode wm = GetWritingMode();
+ ReflowInput reflowInput(aState.PresContext(), this,
+ aState.GetRenderingContext(),
+ LogicalSize(wm, GetLogicalSize().ISize(wm),
+ NS_UNCONSTRAINEDSIZE));
+
+ // Set up a |desiredSize| to pass into ReflowAbsoluteFrames
+ ReflowOutput desiredSize(reflowInput);
+ desiredSize.Width() = mRect.width;
+ desiredSize.Height() = mRect.height;
+
+ // get the ascent (cribbed from ::Reflow)
+ nscoord ascent = mRect.height;
+
+ // getting the ascent could be a lot of work. Don't get it if
+ // we are the root. The viewport doesn't care about it.
+ if (!(mState & NS_STATE_IS_ROOT)) {
+ ascent = GetXULBoxAscent(aState);
+ }
+ desiredSize.SetBlockStartAscent(ascent);
+ desiredSize.mOverflowAreas = GetOverflowAreas();
+
+ AddStateBits(NS_FRAME_IN_REFLOW);
+ // Set up a |reflowStatus| to pass into ReflowAbsoluteFrames
+ // (just a dummy value; hopefully that's OK)
+ nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
+ ReflowAbsoluteFrames(aState.PresContext(), desiredSize,
+ reflowInput, reflowStatus);
+ RemoveStateBits(NS_FRAME_IN_REFLOW);
+ }
+
+ return rv;
+}
+
+void
+nsBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // unregister access key
+ RegUnregAccessKey(false);
+
+ // clean up the container box's layout manager and child boxes
+ SetXULLayoutManager(nullptr);
+
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+#ifdef DEBUG_LAYOUT
+nsresult
+nsBoxFrame::SetXULDebug(nsBoxLayoutState& aState, bool aDebug)
+{
+ // see if our state matches the given debug state
+ bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
+ bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
+
+ // if it doesn't then tell each child below us the new debug state
+ if (debugChanged)
+ {
+ if (aDebug) {
+ mState |= NS_STATE_CURRENTLY_IN_DEBUG;
+ } else {
+ mState &= ~NS_STATE_CURRENTLY_IN_DEBUG;
+ }
+
+ SetDebugOnChildList(aState, mFirstChild, aDebug);
+
+ MarkIntrinsicISizesDirty();
+ }
+
+ return NS_OK;
+}
+#endif
+
+/* virtual */ void
+nsBoxFrame::MarkIntrinsicISizesDirty()
+{
+ SizeNeedsRecalc(mPrefSize);
+ SizeNeedsRecalc(mMinSize);
+ SizeNeedsRecalc(mMaxSize);
+ CoordNeedsRecalc(mFlex);
+ CoordNeedsRecalc(mAscent);
+
+ if (mLayoutManager) {
+ nsBoxLayoutState state(PresContext());
+ mLayoutManager->IntrinsicISizesDirty(this, state);
+ }
+
+ // Don't call base class method, since everything it does is within an
+ // IsXULBoxWrapped check.
+}
+
+void
+nsBoxFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids");
+ nsPresContext* presContext = PresContext();
+ nsBoxLayoutState state(presContext);
+
+ // remove the child frame
+ mFrames.RemoveFrame(aOldFrame);
+
+ // notify the layout manager
+ if (mLayoutManager)
+ mLayoutManager->ChildrenRemoved(this, state, aOldFrame);
+
+ // destroy the child frame
+ aOldFrame->Destroy();
+
+ // mark us dirty and generate a reflow command
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsBoxFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+ NS_ASSERTION(!aPrevFrame || mFrames.ContainsFrame(aPrevFrame),
+ "inserting after sibling frame not in our child list");
+ NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids");
+ nsBoxLayoutState state(PresContext());
+
+ // insert the child frames
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, aPrevFrame, aFrameList);
+
+ // notify the layout manager
+ if (mLayoutManager)
+ mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
+
+ // Make sure to check box order _after_ notifying the layout
+ // manager; otherwise the slice we give the layout manager will
+ // just be bogus. If the layout manager cares about the order, we
+ // just lose.
+ CheckBoxOrder();
+
+#ifdef DEBUG_LAYOUT
+ // if we are in debug make sure our children are in debug as well.
+ if (mState & NS_STATE_CURRENTLY_IN_DEBUG)
+ SetDebugOnChildList(state, mFrames.FirstChild(), true);
+#endif
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+
+void
+nsBoxFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids");
+ nsBoxLayoutState state(PresContext());
+
+ // append the new frames
+ const nsFrameList::Slice& newFrames = mFrames.AppendFrames(this, aFrameList);
+
+ // notify the layout manager
+ if (mLayoutManager)
+ mLayoutManager->ChildrenAppended(this, state, newFrames);
+
+ // Make sure to check box order _after_ notifying the layout
+ // manager; otherwise the slice we give the layout manager will
+ // just be bogus. If the layout manager cares about the order, we
+ // just lose.
+ CheckBoxOrder();
+
+#ifdef DEBUG_LAYOUT
+ // if we are in debug make sure our children are in debug as well.
+ if (mState & NS_STATE_CURRENTLY_IN_DEBUG)
+ SetDebugOnChildList(state, mFrames.FirstChild(), true);
+#endif
+
+ // XXXbz why is this NS_FRAME_FIRST_REFLOW check here?
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+}
+
+/* virtual */ nsContainerFrame*
+nsBoxFrame::GetContentInsertionFrame()
+{
+ if (GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK)
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ return nsContainerFrame::GetContentInsertionFrame();
+}
+
+nsresult
+nsBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ // Ignore 'width', 'height', 'screenX', 'screenY' and 'sizemode' on a
+ // <window>.
+ if (mContent->IsAnyOfXULElements(nsGkAtoms::window,
+ nsGkAtoms::page,
+ nsGkAtoms::dialog,
+ nsGkAtoms::wizard) &&
+ (nsGkAtoms::width == aAttribute ||
+ nsGkAtoms::height == aAttribute ||
+ nsGkAtoms::screenX == aAttribute ||
+ nsGkAtoms::screenY == aAttribute ||
+ nsGkAtoms::sizemode == aAttribute)) {
+ return rv;
+ }
+
+ if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::align ||
+ aAttribute == nsGkAtoms::valign ||
+ aAttribute == nsGkAtoms::left ||
+ aAttribute == nsGkAtoms::top ||
+ aAttribute == nsGkAtoms::right ||
+ aAttribute == nsGkAtoms::bottom ||
+ aAttribute == nsGkAtoms::start ||
+ aAttribute == nsGkAtoms::end ||
+ aAttribute == nsGkAtoms::minwidth ||
+ aAttribute == nsGkAtoms::maxwidth ||
+ aAttribute == nsGkAtoms::minheight ||
+ aAttribute == nsGkAtoms::maxheight ||
+ aAttribute == nsGkAtoms::flex ||
+ aAttribute == nsGkAtoms::orient ||
+ aAttribute == nsGkAtoms::pack ||
+ aAttribute == nsGkAtoms::dir ||
+ aAttribute == nsGkAtoms::mousethrough ||
+ aAttribute == nsGkAtoms::equalsize) {
+
+ if (aAttribute == nsGkAtoms::align ||
+ aAttribute == nsGkAtoms::valign ||
+ aAttribute == nsGkAtoms::orient ||
+ aAttribute == nsGkAtoms::pack ||
+#ifdef DEBUG_LAYOUT
+ aAttribute == nsGkAtoms::debug ||
+#endif
+ aAttribute == nsGkAtoms::dir) {
+
+ mValign = nsBoxFrame::vAlign_Top;
+ mHalign = nsBoxFrame::hAlign_Left;
+
+ bool orient = true;
+ GetInitialOrientation(orient);
+ if (orient)
+ mState |= NS_STATE_IS_HORIZONTAL;
+ else
+ mState &= ~NS_STATE_IS_HORIZONTAL;
+
+ bool normal = true;
+ GetInitialDirection(normal);
+ if (normal)
+ mState |= NS_STATE_IS_DIRECTION_NORMAL;
+ else
+ mState &= ~NS_STATE_IS_DIRECTION_NORMAL;
+
+ GetInitialVAlignment(mValign);
+ GetInitialHAlignment(mHalign);
+
+ bool equalSize = false;
+ GetInitialEqualSize(equalSize);
+ if (equalSize)
+ mState |= NS_STATE_EQUAL_SIZE;
+ else
+ mState &= ~NS_STATE_EQUAL_SIZE;
+
+#ifdef DEBUG_LAYOUT
+ bool debug = mState & NS_STATE_SET_TO_DEBUG;
+ bool debugSet = GetInitialDebug(debug);
+ if (debugSet) {
+ mState |= NS_STATE_DEBUG_WAS_SET;
+
+ if (debug)
+ mState |= NS_STATE_SET_TO_DEBUG;
+ else
+ mState &= ~NS_STATE_SET_TO_DEBUG;
+ } else {
+ mState &= ~NS_STATE_DEBUG_WAS_SET;
+ }
+#endif
+
+ bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH);
+ GetInitialAutoStretch(autostretch);
+ if (autostretch)
+ mState |= NS_STATE_AUTO_STRETCH;
+ else
+ mState &= ~NS_STATE_AUTO_STRETCH;
+ }
+ else if (aAttribute == nsGkAtoms::left ||
+ aAttribute == nsGkAtoms::top ||
+ aAttribute == nsGkAtoms::right ||
+ aAttribute == nsGkAtoms::bottom ||
+ aAttribute == nsGkAtoms::start ||
+ aAttribute == nsGkAtoms::end) {
+ mState &= ~NS_STATE_STACK_NOT_POSITIONED;
+ }
+ else if (aAttribute == nsGkAtoms::mousethrough) {
+ UpdateMouseThrough();
+ }
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ else if (aAttribute == nsGkAtoms::ordinal) {
+ nsIFrame* parent = GetParentXULBox(this);
+ // If our parent is not a box, there's not much we can do... but in that
+ // case our ordinal doesn't matter anyway, so that's ok.
+ // Also don't bother with popup frames since they are kept on the
+ // kPopupList and XULRelayoutChildAtOrdinal() only handles
+ // principal children.
+ if (parent && !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ StyleDisplay()->mDisplay != mozilla::StyleDisplay::Popup) {
+ parent->XULRelayoutChildAtOrdinal(this);
+ // XXXldb Should this instead be a tree change on the child or parent?
+ PresContext()->PresShell()->
+ FrameNeedsReflow(parent, nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ // If the accesskey changed, register for the new value
+ // The old value has been unregistered in nsXULElement::SetAttr
+ else if (aAttribute == nsGkAtoms::accesskey) {
+ RegUnregAccessKey(true);
+ }
+ else if (aAttribute == nsGkAtoms::rows &&
+ mContent->IsXULElement(nsGkAtoms::tree)) {
+ // Reflow ourselves and all our children if "rows" changes, since
+ // nsTreeBodyFrame's layout reads this from its parent (this frame).
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+
+ return rv;
+}
+
+#ifdef DEBUG_LAYOUT
+void
+nsBoxFrame::GetDebugPref()
+{
+ gDebug = Preferences::GetBool("xul.debug.box");
+}
+
+class nsDisplayXULDebug : public nsDisplayItem {
+public:
+ nsDisplayXULDebug(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULDebug);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULDebug() {
+ MOZ_COUNT_DTOR(nsDisplayXULDebug);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, nsRect aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
+ nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2);
+ static_cast<nsBoxFrame*>(mFrame)->
+ DisplayDebugInfoFor(this, rectCenter - ToReferenceFrame());
+ aOutFrames->AppendElement(this);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder
+ nsRenderingContext* aCtx);
+ NS_DISPLAY_DECL_NAME("XULDebug", TYPE_XUL_DEBUG)
+};
+
+void
+nsDisplayXULDebug::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ static_cast<nsBoxFrame*>(mFrame)->
+ PaintXULDebugOverlay(*aCtx->GetDrawTarget(), ToReferenceFrame());
+}
+
+static void
+PaintXULDebugBackground(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect, nsPoint aPt)
+{
+ static_cast<nsBoxFrame*>(aFrame)->PaintXULDebugBackground(aDrawTarget, aPt);
+}
+#endif
+
+void
+nsBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ bool forceLayer = false;
+
+ if (GetContent()->IsXULElement()) {
+ // forcelayer is only supported on XUL elements with box layout
+ if (GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) {
+ forceLayer = true;
+ }
+ // Check for frames that are marked as a part of the region used
+ // in calculating glass margins on Windows.
+ const nsStyleDisplay* styles = StyleDisplay();
+ if (styles && styles->mAppearance == NS_THEME_WIN_EXCLUDE_GLASS) {
+ aBuilder->AddWindowExcludeGlassRegion(
+ nsRect(aBuilder->ToReferenceFrame(this), GetSize()));
+ }
+ }
+
+ nsDisplayListCollection tempLists;
+ const nsDisplayListSet& destination = forceLayer ? tempLists : aLists;
+
+ DisplayBorderBackgroundOutline(aBuilder, destination);
+
+#ifdef DEBUG_LAYOUT
+ if (mState & NS_STATE_CURRENTLY_IN_DEBUG) {
+ destination.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, PaintXULDebugBackground,
+ "XULDebugBackground"));
+ destination.Outlines()->AppendNewToTop(new (aBuilder)
+ nsDisplayXULDebug(aBuilder, this));
+ }
+#endif
+
+ BuildDisplayListForChildren(aBuilder, aDirtyRect, destination);
+
+ // see if we have to draw a selection frame around this container
+ DisplaySelectionOverlay(aBuilder, destination.Content());
+
+ if (forceLayer) {
+ // This is a bit of a hack. Collect up all descendant display items
+ // and merge them into a single Content() list. This can cause us
+ // to violate CSS stacking order, but forceLayer is a magic
+ // XUL-only extension anyway.
+ nsDisplayList masterList;
+ masterList.AppendToTop(tempLists.BorderBackground());
+ masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
+ masterList.AppendToTop(tempLists.Floats());
+ masterList.AppendToTop(tempLists.Content());
+ masterList.AppendToTop(tempLists.PositionedDescendants());
+ masterList.AppendToTop(tempLists.Outlines());
+
+ // Wrap the list to make it its own layer
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayOwnLayer(aBuilder, this, &masterList));
+ }
+}
+
+void
+nsBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsIFrame* kid = mFrames.FirstChild();
+ // Put each child's background onto the BlockBorderBackgrounds list
+ // to emulate the existing two-layer XUL painting scheme.
+ nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
+ // The children should be in the right order
+ while (kid) {
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, set);
+ kid = kid->GetNextSibling();
+ }
+}
+
+// REVIEW: PaintChildren did a few things none of which are a big deal
+// anymore:
+// * Paint some debugging rects for this frame.
+// This is done by nsDisplayXULDebugBackground, which goes in the
+// BorderBackground() layer so it isn't clipped by OVERFLOW_CLIP.
+// * Apply OVERFLOW_CLIP to the children.
+// This is now in nsFrame::BuildDisplayListForStackingContext/Child.
+// * Actually paint the children.
+// Moved to BuildDisplayList.
+// * Paint per-kid debug information.
+// This is done by nsDisplayXULDebug, which is in the Outlines()
+// layer so it goes on top. This means it is not clipped by OVERFLOW_CLIP,
+// whereas it did used to respect OVERFLOW_CLIP, but too bad.
+#ifdef DEBUG_LAYOUT
+void
+nsBoxFrame::PaintXULDebugBackground(DrawTarget* aDrawTarget, nsPoint aPt)
+{
+ nsMargin border;
+ GetXULBorder(border);
+
+ nsMargin debugBorder;
+ nsMargin debugMargin;
+ nsMargin debugPadding;
+
+ bool isHorizontal = IsXULHorizontal();
+
+ GetDebugBorder(debugBorder);
+ PixelMarginToTwips(debugBorder);
+
+ GetDebugMargin(debugMargin);
+ PixelMarginToTwips(debugMargin);
+
+ GetDebugPadding(debugPadding);
+ PixelMarginToTwips(debugPadding);
+
+ nsRect inner(mRect);
+ inner.MoveTo(aPt);
+ inner.Deflate(debugMargin);
+ inner.Deflate(border);
+ //nsRect borderRect(inner);
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+
+ ColorPattern color(ToDeviceColor(isHorizontal ? Color(0.f, 0.f, 1.f, 1.f) :
+ Color(1.f, 0.f, 0.f, 1.f)));
+
+ //left
+ nsRect r(inner);
+ r.width = debugBorder.left;
+ aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color);
+
+ // top
+ r = inner;
+ r.height = debugBorder.top;
+ aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color);
+
+ //right
+ r = inner;
+ r.x = r.x + r.width - debugBorder.right;
+ r.width = debugBorder.right;
+ aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color);
+
+ //bottom
+ r = inner;
+ r.y = r.y + r.height - debugBorder.bottom;
+ r.height = debugBorder.bottom;
+ aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color);
+
+ // If we have dirty children or we are dirty place a green border around us.
+ if (NS_SUBTREE_DIRTY(this)) {
+ nsRect dirty(inner);
+ ColorPattern green(ToDeviceColor(Color(0.f, 1.f, 0.f, 1.f)));
+ aDrawTarget->StrokeRect(NSRectToRect(dirty, appUnitsPerDevPixel), green);
+ }
+}
+
+void
+nsBoxFrame::PaintXULDebugOverlay(DrawTarget& aDrawTarget, nsPoint aPt)
+{
+ nsMargin border;
+ GetXULBorder(border);
+
+ nsMargin debugMargin;
+ GetDebugMargin(debugMargin);
+ PixelMarginToTwips(debugMargin);
+
+ nsRect inner(mRect);
+ inner.MoveTo(aPt);
+ inner.Deflate(debugMargin);
+ inner.Deflate(border);
+
+ nscoord onePixel = GetPresContext()->IntScaledPixelsToTwips(1);
+
+ kid = nsBox::GetChildXULBox(this);
+ while (nullptr != kid) {
+ bool isHorizontal = IsXULHorizontal();
+
+ nscoord x, y, borderSize, spacerSize;
+
+ nsRect cr(kid->mRect);
+ nsMargin margin;
+ kid->GetXULMargin(margin);
+ cr.Inflate(margin);
+
+ if (isHorizontal)
+ {
+ cr.y = inner.y;
+ x = cr.x;
+ y = cr.y + onePixel;
+ spacerSize = debugBorder.top - onePixel*4;
+ } else {
+ cr.x = inner.x;
+ x = cr.y;
+ y = cr.x + onePixel;
+ spacerSize = debugBorder.left - onePixel*4;
+ }
+
+ nscoord flex = kid->GetXULFlex();
+
+ if (!kid->IsXULCollapsed()) {
+ if (isHorizontal)
+ borderSize = cr.width;
+ else
+ borderSize = cr.height;
+
+ DrawSpacer(GetPresContext(), aDrawTarget, isHorizontal, flex, x, y, borderSize, spacerSize);
+ }
+
+ kid = GetNextXULBox(kid);
+ }
+}
+#endif
+
+#ifdef DEBUG_LAYOUT
+void
+nsBoxFrame::GetBoxName(nsAutoString& aName)
+{
+ GetFrameName(aName);
+}
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("Box"), aResult);
+}
+#endif
+
+nsIAtom*
+nsBoxFrame::GetType() const
+{
+ return nsGkAtoms::boxFrame;
+}
+
+#ifdef DEBUG_LAYOUT
+nsresult
+nsBoxFrame::GetXULDebug(bool& aDebug)
+{
+ aDebug = (mState & NS_STATE_CURRENTLY_IN_DEBUG);
+ return NS_OK;
+}
+#endif
+
+// REVIEW: nsBoxFrame::GetFrameForPoint is a problem because of 'mousethrough'
+// attribute support. Here's how it works:
+// * For each child frame F, we determine the target frame T(F) by recursively
+// invoking GetFrameForPoint on the child
+// * Let F' be the last child frame such that T(F') doesn't have mousethrough.
+// If F' exists, return T(F')
+// * Otherwise let F'' be the first child frame such that T(F'') is non-null.
+// If F'' exists, return T(F'')
+// * Otherwise return this frame, if this frame contains the point
+// * Otherwise return null
+// It's not clear how this should work for more complex z-ordering situations.
+// The basic principle seems to be that if a frame F has a descendant
+// 'mousethrough' frame that includes the target position, then F
+// will not receive events (unless it overrides GetFrameForPoint).
+// A 'mousethrough' frame will only receive an event if, after applying that rule,
+// all eligible frames are 'mousethrough'; the bottom-most inner-most 'mousethrough'
+// frame is then chosen (the first eligible frame reached in a
+// traversal of the frame tree --- pre/post is irrelevant since ancestors
+// of the mousethrough frames can't be eligible).
+// IMHO this is very bogus and adds a great deal of complexity for something
+// that is very rarely used. So I'm redefining 'mousethrough' to the following:
+// a frame with mousethrough is transparent to mouse events. This is compatible
+// with the way 'mousethrough' is used in Seamonkey's navigator.xul and
+// Firefox's browser.xul. The only other place it's used is in the 'expander'
+// XBL binding, which in our tree is only used by Thunderbird SMIME Advanced
+// Preferences, and I can't figure out what that does, so I'll have to test it.
+// If it's broken I'll probably just change the binding to use it more sensibly.
+// This new behaviour is implemented in nsDisplayList::HitTest.
+// REVIEW: This debug-box stuff is annoying. I'm just going to put debug boxes
+// in the outline layer and avoid GetDebugBoxAt.
+
+// REVIEW: GetCursor had debug-only event dumping code. I have replaced it
+// with instrumentation in nsDisplayXULDebug.
+
+#ifdef DEBUG_LAYOUT
+void
+nsBoxFrame::DrawLine(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2)
+{
+ nsPoint p1(x1, y1);
+ nsPoint p2(x2, y2);
+ if (!aHorizontal) {
+ Swap(p1.x, p1.y);
+ Swap(p2.x, p2.y);
+ }
+ ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
+ StrokeLineWithSnapping(p1, p2, PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget, color);
+}
+
+void
+nsBoxFrame::FillRect(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x, nscoord y, nscoord width, nscoord height)
+{
+ Rect rect = NSRectToSnappedRect(aHorizontal ? nsRect(x, y, width, height) :
+ nsRect(y, x, height, width),
+ PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget);
+ ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
+ aDrawTarget.FillRect(rect, white);
+}
+
+void
+nsBoxFrame::DrawSpacer(nsPresContext* aPresContext, DrawTarget& aDrawTarget,
+ bool aHorizontal, int32_t flex, nscoord x, nscoord y,
+ nscoord size, nscoord spacerSize)
+{
+ nscoord onePixel = aPresContext->IntScaledPixelsToTwips(1);
+
+ // if we do draw the coils
+ int distance = 0;
+ int center = 0;
+ int offset = 0;
+ int coilSize = COIL_SIZE*onePixel;
+ int halfSpacer = spacerSize/2;
+
+ distance = size;
+ center = y + halfSpacer;
+ offset = x;
+
+ int coils = distance/coilSize;
+
+ int halfCoilSize = coilSize/2;
+
+ if (flex == 0) {
+ DrawLine(aDrawTarget, aHorizontal, x,y + spacerSize/2, x + size, y + spacerSize/2);
+ } else {
+ for (int i=0; i < coils; i++)
+ {
+ DrawLine(aDrawTarget, aHorizontal, offset, center+halfSpacer, offset+halfCoilSize, center-halfSpacer);
+ DrawLine(aDrawTarget, aHorizontal, offset+halfCoilSize, center-halfSpacer, offset+coilSize, center+halfSpacer);
+
+ offset += coilSize;
+ }
+ }
+
+ FillRect(aDrawTarget, aHorizontal, x + size - spacerSize/2, y, spacerSize/2, spacerSize);
+ FillRect(aDrawTarget, aHorizontal, x, y, spacerSize/2, spacerSize);
+}
+
+void
+nsBoxFrame::GetDebugBorder(nsMargin& aInset)
+{
+ aInset.SizeTo(2,2,2,2);
+
+ if (IsXULHorizontal())
+ aInset.top = 10;
+ else
+ aInset.left = 10;
+}
+
+void
+nsBoxFrame::GetDebugMargin(nsMargin& aInset)
+{
+ aInset.SizeTo(2,2,2,2);
+}
+
+void
+nsBoxFrame::GetDebugPadding(nsMargin& aPadding)
+{
+ aPadding.SizeTo(2,2,2,2);
+}
+
+void
+nsBoxFrame::PixelMarginToTwips(nsMargin& aMarginPixels)
+{
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ aMarginPixels.left *= onePixel;
+ aMarginPixels.right *= onePixel;
+ aMarginPixels.top *= onePixel;
+ aMarginPixels.bottom *= onePixel;
+}
+
+void
+nsBoxFrame::GetValue(nsPresContext* aPresContext, const nsSize& a, const nsSize& b, char* ch)
+{
+ float p2t = aPresContext->ScaledPixelsToTwips();
+
+ char width[100];
+ char height[100];
+
+ if (a.width == NS_INTRINSICSIZE)
+ sprintf(width,"%s","INF");
+ else
+ sprintf(width,"%d", nscoord(a.width/*/p2t*/));
+
+ if (a.height == NS_INTRINSICSIZE)
+ sprintf(height,"%s","INF");
+ else
+ sprintf(height,"%d", nscoord(a.height/*/p2t*/));
+
+
+ sprintf(ch, "(%s%s, %s%s)", width, (b.width != NS_INTRINSICSIZE ? "[SET]" : ""),
+ height, (b.height != NS_INTRINSICSIZE ? "[SET]" : ""));
+
+}
+
+void
+nsBoxFrame::GetValue(nsPresContext* aPresContext, int32_t a, int32_t b, char* ch)
+{
+ if (a == NS_INTRINSICSIZE)
+ sprintf(ch, "%d[SET]", b);
+ else
+ sprintf(ch, "%d", a);
+}
+
+nsresult
+nsBoxFrame::DisplayDebugInfoFor(nsIFrame* aBox,
+ nsPoint& aPoint)
+{
+ nsBoxLayoutState state(GetPresContext());
+
+ nscoord x = aPoint.x;
+ nscoord y = aPoint.y;
+
+ // get the area inside our border but not our debug margins.
+ nsRect insideBorder(aBox->mRect);
+ insideBorder.MoveTo(0,0):
+ nsMargin border(0,0,0,0);
+ aBox->GetXULBorderAndPadding(border);
+ insideBorder.Deflate(border);
+
+ bool isHorizontal = IsXULHorizontal();
+
+ if (!insideBorder.Contains(nsPoint(x,y)))
+ return NS_ERROR_FAILURE;
+
+ //printf("%%%%%% inside box %%%%%%%\n");
+
+ int count = 0;
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ nsMargin m;
+ nsMargin m2;
+ GetDebugBorder(m);
+ PixelMarginToTwips(m);
+
+ GetDebugMargin(m2);
+ PixelMarginToTwips(m2);
+
+ m += m2;
+
+ if ((isHorizontal && y < insideBorder.y + m.top) ||
+ (!isHorizontal && x < insideBorder.x + m.left)) {
+ //printf("**** inside debug border *******\n");
+ while (child)
+ {
+ const nsRect& r = child->mRect;
+
+ // if we are not in the child. But in the spacer above the child.
+ if ((isHorizontal && x >= r.x && x < r.x + r.width) ||
+ (!isHorizontal && y >= r.y && y < r.y + r.height)) {
+ aCursor = NS_STYLE_CURSOR_POINTER;
+ // found it but we already showed it.
+ if (mDebugChild == child)
+ return NS_OK;
+
+ if (aBox->GetContent()) {
+ printf("---------------\n");
+ XULDumpBox(stdout);
+ printf("\n");
+ }
+
+ if (child->GetContent()) {
+ printf("child #%d: ", count);
+ child->XULDumpBox(stdout);
+ printf("\n");
+ }
+
+ mDebugChild = child;
+
+ nsSize prefSizeCSS(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ nsSize minSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ nsSize maxSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+ nscoord flexCSS = NS_INTRINSICSIZE;
+
+ bool widthSet, heightSet;
+ nsIFrame::AddXULPrefSize(child, prefSizeCSS, widthSet, heightSet);
+ nsIFrame::AddXULMinSize (state, child, minSizeCSS, widthSet, heightSet);
+ nsIFrame::AddXULMaxSize (child, maxSizeCSS, widthSet, heightSet);
+ nsIFrame::AddXULFlex (child, flexCSS);
+
+ nsSize prefSize = child->GetXULPrefSize(state);
+ nsSize minSize = child->GetXULMinSize(state);
+ nsSize maxSize = child->GetXULMaxSize(state);
+ nscoord flexSize = child->GetXULFlex();
+ nscoord ascentSize = child->GetXULBoxAscent(state);
+
+ char min[100];
+ char pref[100];
+ char max[100];
+ char calc[100];
+ char flex[100];
+ char ascent[100];
+
+ nsSize actualSize;
+ GetFrameSizeWithMargin(child, actualSize);
+ nsSize actualSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+
+ GetValue(aPresContext, minSize, minSizeCSS, min);
+ GetValue(aPresContext, prefSize, prefSizeCSS, pref);
+ GetValue(aPresContext, maxSize, maxSizeCSS, max);
+ GetValue(aPresContext, actualSize, actualSizeCSS, calc);
+ GetValue(aPresContext, flexSize, flexCSS, flex);
+ GetValue(aPresContext, ascentSize, NS_INTRINSICSIZE, ascent);
+
+
+ printf("min%s, pref%s, max%s, actual%s, flex=%s, ascent=%s\n\n",
+ min,
+ pref,
+ max,
+ calc,
+ flex,
+ ascent
+ );
+
+ return NS_OK;
+ }
+
+ child = GetNextXULBox(child);
+ count++;
+ }
+ } else {
+ }
+
+ mDebugChild = nullptr;
+
+ return NS_OK;
+}
+
+void
+nsBoxFrame::SetDebugOnChildList(nsBoxLayoutState& aState, nsIFrame* aChild, bool aDebug)
+{
+ nsIFrame* child = nsBox::GetChildXULBox(this);
+ while (child)
+ {
+ child->SetXULDebug(aState, aDebug);
+ child = GetNextXULBox(child);
+ }
+}
+
+nsresult
+nsBoxFrame::GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize)
+{
+ nsRect rect(aBox->GetRect());
+ nsMargin margin(0,0,0,0);
+ aBox->GetXULMargin(margin);
+ rect.Inflate(margin);
+ aSize.width = rect.width;
+ aSize.height = rect.height;
+ return NS_OK;
+}
+#endif
+
+// If you make changes to this function, check its counterparts
+// in nsTextBoxFrame and nsXULLabelFrame
+void
+nsBoxFrame::RegUnregAccessKey(bool aDoReg)
+{
+ MOZ_ASSERT(mContent);
+
+ // only support accesskeys for the following elements
+ if (!mContent->IsAnyOfXULElements(nsGkAtoms::button,
+ nsGkAtoms::toolbarbutton,
+ nsGkAtoms::checkbox,
+ nsGkAtoms::textbox,
+ nsGkAtoms::tab,
+ nsGkAtoms::radio)) {
+ return;
+ }
+
+ nsAutoString accessKey;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+
+ if (accessKey.IsEmpty())
+ return;
+
+ // With a valid PresContext we can get the ESM
+ // and register the access key
+ EventStateManager* esm = PresContext()->EventStateManager();
+
+ uint32_t key = accessKey.First();
+ if (aDoReg)
+ esm->RegisterAccessKey(mContent, key);
+ else
+ esm->UnregisterAccessKey(mContent, key);
+}
+
+bool
+nsBoxFrame::SupportsOrdinalsInChildren()
+{
+ return true;
+}
+
+// Helper less-than-or-equal function, used in CheckBoxOrder() as a
+// template-parameter for the sorting functions.
+bool
+IsBoxOrdinalLEQ(nsIFrame* aFrame1,
+ nsIFrame* aFrame2)
+{
+ // If we've got a placeholder frame, use its out-of-flow frame's ordinal val.
+ nsIFrame* aRealFrame1 = nsPlaceholderFrame::GetRealFrameFor(aFrame1);
+ nsIFrame* aRealFrame2 = nsPlaceholderFrame::GetRealFrameFor(aFrame2);
+ return aRealFrame1->GetXULOrdinal() <= aRealFrame2->GetXULOrdinal();
+}
+
+void
+nsBoxFrame::CheckBoxOrder()
+{
+ if (SupportsOrdinalsInChildren() &&
+ !nsIFrame::IsFrameListSorted<IsBoxOrdinalLEQ>(mFrames)) {
+ nsIFrame::SortFrameList<IsBoxOrdinalLEQ>(mFrames);
+ }
+}
+
+nsresult
+nsBoxFrame::LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox, const nsRect& aRect)
+{
+ // get the current rect
+ nsRect oldRect(aBox->GetRect());
+ aBox->SetXULBounds(aState, aRect);
+
+ bool layout = NS_SUBTREE_DIRTY(aBox);
+
+ if (layout || (oldRect.width != aRect.width || oldRect.height != aRect.height)) {
+ return aBox->XULLayout(aState);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBoxFrame::XULRelayoutChildAtOrdinal(nsIFrame* aChild)
+{
+ if (!SupportsOrdinalsInChildren())
+ return NS_OK;
+
+ uint32_t ord = aChild->GetXULOrdinal();
+
+ nsIFrame* child = mFrames.FirstChild();
+ nsIFrame* newPrevSib = nullptr;
+
+ while (child) {
+ if (ord < child->GetXULOrdinal()) {
+ break;
+ }
+
+ if (child != aChild) {
+ newPrevSib = child;
+ }
+
+ child = GetNextXULBox(child);
+ }
+
+ if (aChild->GetPrevSibling() == newPrevSib) {
+ // This box is not moving.
+ return NS_OK;
+ }
+
+ // Take |aChild| out of its old position in the child list.
+ mFrames.RemoveFrame(aChild);
+
+ // Insert it after |newPrevSib| or at the start if it's null.
+ mFrames.InsertFrame(nullptr, newPrevSib, aChild);
+
+ return NS_OK;
+}
+
+/**
+ * This wrapper class lets us redirect mouse hits from descendant frames
+ * of a menu to the menu itself, if they didn't specify 'allowevents'.
+ *
+ * The wrapper simply turns a hit on a descendant element
+ * into a hit on the menu itself, unless there is an element between the target
+ * and the menu with the "allowevents" attribute.
+ *
+ * This is used by nsMenuFrame and nsTreeColFrame.
+ *
+ * Note that turning a hit on a descendant element into nullptr, so events
+ * could fall through to the menu background, might be an appealing simplification
+ * but it would mean slightly strange behaviour in some cases, because grabber
+ * wrappers can be created for many individual lists and items, so the exact
+ * fallthrough behaviour would be complex. E.g. an element with "allowevents"
+ * on top of the Content() list could receive the event even if it was covered
+ * by a PositionedDescenants() element without "allowevents". It is best to
+ * never convert a non-null hit into null.
+ */
+// REVIEW: This is roughly of what nsMenuFrame::GetFrameForPoint used to do.
+// I've made 'allowevents' affect child elements because that seems the only
+// reasonable thing to do.
+class nsDisplayXULEventRedirector : public nsDisplayWrapList {
+public:
+ nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayItem* aItem,
+ nsIFrame* aTargetFrame)
+ : nsDisplayWrapList(aBuilder, aFrame, aItem), mTargetFrame(aTargetFrame) {}
+ nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ nsIFrame* aTargetFrame)
+ : nsDisplayWrapList(aBuilder, aFrame, aList), mTargetFrame(aTargetFrame) {}
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ NS_DISPLAY_DECL_NAME("XULEventRedirector", TYPE_XUL_EVENT_REDIRECTOR)
+private:
+ nsIFrame* mTargetFrame;
+};
+
+void nsDisplayXULEventRedirector::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsTArray<nsIFrame*> outFrames;
+ mList.HitTest(aBuilder, aRect, aState, &outFrames);
+
+ bool topMostAdded = false;
+ uint32_t localLength = outFrames.Length();
+
+ for (uint32_t i = 0; i < localLength; i++) {
+
+ for (nsIContent* content = outFrames.ElementAt(i)->GetContent();
+ content && content != mTargetFrame->GetContent();
+ content = content->GetParent()) {
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents,
+ nsGkAtoms::_true, eCaseMatters)) {
+ // Events are allowed on 'frame', so let it go.
+ aOutFrames->AppendElement(outFrames.ElementAt(i));
+ topMostAdded = true;
+ }
+ }
+
+ // If there was no hit on the topmost frame or its ancestors,
+ // add the target frame itself as the first candidate (see bug 562554).
+ if (!topMostAdded) {
+ topMostAdded = true;
+ aOutFrames->AppendElement(mTargetFrame);
+ }
+ }
+}
+
+class nsXULEventRedirectorWrapper : public nsDisplayWrapper
+{
+public:
+ explicit nsXULEventRedirectorWrapper(nsIFrame* aTargetFrame)
+ : mTargetFrame(aTargetFrame) {}
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList) override {
+ return new (aBuilder)
+ nsDisplayXULEventRedirector(aBuilder, aFrame, aList, mTargetFrame);
+ }
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) override {
+ return new (aBuilder)
+ nsDisplayXULEventRedirector(aBuilder, aItem->Frame(), aItem,
+ mTargetFrame);
+ }
+private:
+ nsIFrame* mTargetFrame;
+};
+
+void
+nsBoxFrame::WrapListsInRedirector(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aIn,
+ const nsDisplayListSet& aOut)
+{
+ nsXULEventRedirectorWrapper wrapper(this);
+ wrapper.WrapLists(aBuilder, this, aIn, aOut);
+}
+
+bool
+nsBoxFrame::GetEventPoint(WidgetGUIEvent* aEvent, nsPoint &aPoint) {
+ LayoutDeviceIntPoint refPoint;
+ bool res = GetEventPoint(aEvent, refPoint);
+ aPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, refPoint, this);
+ return res;
+}
+
+bool
+nsBoxFrame::GetEventPoint(WidgetGUIEvent* aEvent, LayoutDeviceIntPoint& aPoint) {
+ NS_ENSURE_TRUE(aEvent, false);
+
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ if (touchEvent) {
+ // return false if there is more than one touch on the page, or if
+ // we can't find a touch point
+ if (touchEvent->mTouches.Length() != 1) {
+ return false;
+ }
+
+ dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
+ if (!touch) {
+ return false;
+ }
+ aPoint = touch->mRefPoint;
+ } else {
+ aPoint = aEvent->mRefPoint;
+ }
+ return true;
+}
diff --git a/layout/xul/nsBoxFrame.h b/layout/xul/nsBoxFrame.h
new file mode 100644
index 000000000..ad405222f
--- /dev/null
+++ b/layout/xul/nsBoxFrame.h
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ nsBoxFrame is a frame that can lay its children out either vertically or horizontally.
+ It lays them out according to a min max or preferred size.
+
+**/
+
+#ifndef nsBoxFrame_h___
+#define nsBoxFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsContainerFrame.h"
+#include "nsBoxLayout.h"
+
+class nsBoxLayoutState;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ bool aIsRoot,
+ nsBoxLayout* aLayoutManager);
+nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+class nsBoxFrame : public nsContainerFrame
+{
+protected:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+#ifdef DEBUG
+ NS_DECL_QUERYFRAME_TARGET(nsBoxFrame)
+ NS_DECL_QUERYFRAME
+#endif
+
+ friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ bool aIsRoot,
+ nsBoxLayout* aLayoutManager);
+ friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // gets the rect inside our border and debug border. If you wish to paint inside a box
+ // call this method to get the rect so you don't draw on the debug border or outer border.
+
+ virtual void SetXULLayoutManager(nsBoxLayout* aLayout) override { mLayoutManager = aLayout; }
+ virtual nsBoxLayout* GetXULLayoutManager() override { return mLayoutManager; }
+
+ virtual nsresult XULRelayoutChildAtOrdinal(nsIFrame* aChild) override;
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetXULFlex() override;
+ virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override;
+#ifdef DEBUG_LAYOUT
+ virtual nsresult SetXULDebug(nsBoxLayoutState& aBoxLayoutState, bool aDebug) override;
+ virtual nsresult GetXULDebug(bool& aDebug) override;
+#endif
+ virtual Valignment GetXULVAlign() const override { return mValign; }
+ virtual Halignment GetXULHAlign() const override { return mHalign; }
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual bool ComputesOwnOverflowArea() override { return false; }
+
+ // ----- child and sibling operations ---
+
+ // ----- public methods -------
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void MarkIntrinsicISizesDirty() override;
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override;
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // record that children that are ignorable whitespace should be excluded
+ // (When content was loaded via the XUL content sink, it's already
+ // been excluded, but we need this for when the XUL namespace is used
+ // in other MIME types or when the XUL CSS display types are used with
+ // non-XUL elements.)
+
+ // This is bogus, but it's what we've always done.
+ // (Given that we're replaced, we need to say we're a replaced element
+ // that contains a block so ReflowInput doesn't tell us to be
+ // NS_INTRINSICSIZE wide.)
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock | eXULBox |
+ nsIFrame::eExcludesIgnorableWhitespace));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus) override;
+
+ virtual bool HonorPrintBackgroundSettings() override;
+
+ virtual ~nsBoxFrame();
+
+ explicit nsBoxFrame(nsStyleContext* aContext, bool aIsRoot = false, nsBoxLayout* aLayoutManager = nullptr);
+
+ // virtual so nsStackFrame, nsButtonBoxFrame, nsSliderFrame and nsMenuFrame
+ // can override it
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists);
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_LAYOUT
+ virtual void SetDebugOnChildList(nsBoxLayoutState& aState, nsIFrame* aChild, bool aDebug);
+ nsresult DisplayDebugInfoFor(nsIFrame* aBox,
+ nsPoint& aPoint);
+#endif
+
+ static nsresult LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox, const nsRect& aRect);
+
+ /**
+ * Utility method to redirect events on descendants to this frame.
+ * Supports 'allowevents' attribute on descendant elements to allow those
+ * elements and their descendants to receive events.
+ */
+ void WrapListsInRedirector(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aIn,
+ const nsDisplayListSet& aOut);
+
+ /**
+ * This defaults to true, but some box frames (nsListBoxBodyFrame for
+ * example) don't support ordinals in their children.
+ */
+ virtual bool SupportsOrdinalsInChildren();
+
+protected:
+#ifdef DEBUG_LAYOUT
+ virtual void GetBoxName(nsAutoString& aName) override;
+ void PaintXULDebugBackground(nsRenderingContext& aRenderingContext,
+ nsPoint aPt);
+ void PaintXULDebugOverlay(DrawTarget& aRenderingContext,
+ nsPoint aPt);
+#endif
+
+ virtual bool GetInitialEqualSize(bool& aEqualSize);
+ virtual void GetInitialOrientation(bool& aIsHorizontal);
+ virtual void GetInitialDirection(bool& aIsNormal);
+ virtual bool GetInitialHAlignment(Halignment& aHalign);
+ virtual bool GetInitialVAlignment(Valignment& aValign);
+ virtual bool GetInitialAutoStretch(bool& aStretch);
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ nsSize mPrefSize;
+ nsSize mMinSize;
+ nsSize mMaxSize;
+ nscoord mFlex;
+ nscoord mAscent;
+
+ nsCOMPtr<nsBoxLayout> mLayoutManager;
+
+ // Get the point associated with this event. Returns true if a single valid
+ // point was found. Otherwise false.
+ bool GetEventPoint(mozilla::WidgetGUIEvent* aEvent, nsPoint& aPoint);
+ // Gets the event coordinates relative to the widget offset associated with
+ // this frame. Return true if a single valid point was found.
+ bool GetEventPoint(mozilla::WidgetGUIEvent* aEvent,
+ mozilla::LayoutDeviceIntPoint& aPoint);
+
+protected:
+ void RegUnregAccessKey(bool aDoReg);
+
+ void CheckBoxOrder();
+
+private:
+
+#ifdef DEBUG_LAYOUT
+ nsresult SetXULDebug(nsPresContext* aPresContext, bool aDebug);
+ bool GetInitialDebug(bool& aDebug);
+ void GetDebugPref();
+
+ void GetDebugBorder(nsMargin& aInset);
+ void GetDebugPadding(nsMargin& aInset);
+ void GetDebugMargin(nsMargin& aInset);
+
+ nsresult GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize);
+
+ void PixelMarginToTwips(nsMargin& aMarginPixels);
+
+ void GetValue(nsPresContext* aPresContext, const nsSize& a, const nsSize& b, char* value);
+ void GetValue(nsPresContext* aPresContext, int32_t a, int32_t b, char* value);
+ void DrawSpacer(nsPresContext* aPresContext, DrawTarget& aDrawTarget, bool aHorizontal, int32_t flex, nscoord x, nscoord y, nscoord size, nscoord spacerSize);
+ void DrawLine(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2);
+ void FillRect(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x, nscoord y, nscoord width, nscoord height);
+#endif
+ virtual void UpdateMouseThrough();
+
+ void CacheAttributes();
+
+ // instance variables.
+ Halignment mHalign;
+ Valignment mValign;
+
+#ifdef DEBUG_LAYOUT
+ static bool gDebug;
+ static nsIFrame* mDebugChild;
+#endif
+
+}; // class nsBoxFrame
+
+#endif
+
diff --git a/layout/xul/nsBoxLayout.cpp b/layout/xul/nsBoxLayout.cpp
new file mode 100644
index 000000000..60db32a7a
--- /dev/null
+++ b/layout/xul/nsBoxLayout.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsBox.h"
+#include "nsCOMPtr.h"
+#include "nsContainerFrame.h"
+#include "nsBoxLayout.h"
+
+void
+nsBoxLayout::AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize)
+{
+ nsBox::AddBorderAndPadding(aBox, aSize);
+}
+
+void
+nsBoxLayout::AddMargin(nsIFrame* aBox, nsSize& aSize)
+{
+ nsBox::AddMargin(aBox, aSize);
+}
+
+void
+nsBoxLayout::AddMargin(nsSize& aSize, const nsMargin& aMargin)
+{
+ nsBox::AddMargin(aSize, aMargin);
+}
+
+nsSize
+nsBoxLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize pref (0, 0);
+ AddBorderAndPadding(aBox, pref);
+
+ return pref;
+}
+
+nsSize
+nsBoxLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize minSize (0,0);
+ AddBorderAndPadding(aBox, minSize);
+ return minSize;
+}
+
+nsSize
+nsBoxLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ //AddBorderAndPadding () never changes maxSize (NS_INTRINSICSIZE)
+ //AddBorderAndPadding(aBox, maxSize);
+ return nsSize (NS_INTRINSICSIZE,NS_INTRINSICSIZE);
+}
+
+
+nscoord
+nsBoxLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ return 0;
+}
+
+NS_IMETHODIMP
+nsBoxLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ return NS_OK;
+}
+
+void
+nsBoxLayout::AddLargestSize(nsSize& aSize, const nsSize& aSize2)
+{
+ if (aSize2.width > aSize.width)
+ aSize.width = aSize2.width;
+
+ if (aSize2.height > aSize.height)
+ aSize.height = aSize2.height;
+}
+
+void
+nsBoxLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSize2)
+{
+ if (aSize2.width < aSize.width)
+ aSize.width = aSize2.width;
+
+ if (aSize2.height < aSize.height)
+ aSize.height = aSize2.height;
+}
+
+NS_IMPL_ISUPPORTS(nsBoxLayout, nsBoxLayout)
diff --git a/layout/xul/nsBoxLayout.h b/layout/xul/nsBoxLayout.h
new file mode 100644
index 000000000..cb4d77170
--- /dev/null
+++ b/layout/xul/nsBoxLayout.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBoxLayout_h___
+#define nsBoxLayout_h___
+
+#include "nsISupports.h"
+#include "nsCoord.h"
+#include "nsFrameList.h"
+
+class nsIFrame;
+class nsBoxLayoutState;
+struct nsSize;
+struct nsMargin;
+
+#define NS_BOX_LAYOUT_IID \
+{ 0x09d522a7, 0x304c, 0x4137, \
+ { 0xaf, 0xc9, 0xe0, 0x80, 0x2e, 0x89, 0xb7, 0xe8 } }
+
+class nsIGridPart;
+
+class nsBoxLayout : public nsISupports {
+
+protected:
+ virtual ~nsBoxLayout() {}
+
+public:
+
+ nsBoxLayout() {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_BOX_LAYOUT_IID)
+
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState);
+
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState);
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState);
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState);
+ virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState);
+ virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState,
+ nsIFrame* aPrevBox,
+ const nsFrameList::Slice& aNewChildren) {}
+ virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState,
+ const nsFrameList::Slice& aNewChildren) {}
+ virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) {}
+ virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) {}
+ virtual void IntrinsicISizesDirty(nsIFrame* aBox, nsBoxLayoutState& aState) {}
+
+ virtual void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize);
+ virtual void AddMargin(nsIFrame* aChild, nsSize& aSize);
+ virtual void AddMargin(nsSize& aSize, const nsMargin& aMargin);
+
+ virtual nsIGridPart* AsGridPart() { return nullptr; }
+
+ static void AddLargestSize(nsSize& aSize, const nsSize& aToAdd);
+ static void AddSmallestSize(nsSize& aSize, const nsSize& aToAdd);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsBoxLayout, NS_BOX_LAYOUT_IID)
+
+#endif
+
diff --git a/layout/xul/nsBoxLayoutState.cpp b/layout/xul/nsBoxLayoutState.cpp
new file mode 100644
index 000000000..e1219534e
--- /dev/null
+++ b/layout/xul/nsBoxLayoutState.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsBoxLayoutState.h"
+
+nsBoxLayoutState::nsBoxLayoutState(nsPresContext* aPresContext,
+ nsRenderingContext* aRenderingContext,
+ const ReflowInput* aOuterReflowInput,
+ uint16_t aReflowDepth)
+ : mPresContext(aPresContext)
+ , mRenderingContext(aRenderingContext)
+ , mOuterReflowInput(aOuterReflowInput)
+ , mLayoutFlags(0)
+ , mReflowDepth(aReflowDepth)
+ , mPaintingDisabled(false)
+{
+ NS_ASSERTION(mPresContext, "PresContext must be non-null");
+}
+
+nsBoxLayoutState::nsBoxLayoutState(const nsBoxLayoutState& aState)
+ : mPresContext(aState.mPresContext)
+ , mRenderingContext(aState.mRenderingContext)
+ , mOuterReflowInput(aState.mOuterReflowInput)
+ , mLayoutFlags(aState.mLayoutFlags)
+ , mReflowDepth(aState.mReflowDepth + 1)
+ , mPaintingDisabled(aState.mPaintingDisabled)
+{
+ NS_ASSERTION(mPresContext, "PresContext must be non-null");
+}
diff --git a/layout/xul/nsBoxLayoutState.h b/layout/xul/nsBoxLayoutState.h
new file mode 100644
index 000000000..b857ad9c0
--- /dev/null
+++ b/layout/xul/nsBoxLayoutState.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Author:
+ Eric D Vaughan
+
+**/
+
+#ifndef nsBoxLayoutState_h___
+#define nsBoxLayoutState_h___
+
+#include "nsCOMPtr.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+
+class nsRenderingContext;
+namespace mozilla {
+struct ReflowInput;
+} // namespace mozilla
+
+
+class MOZ_STACK_CLASS nsBoxLayoutState
+{
+ using ReflowInput = mozilla::ReflowInput;
+
+public:
+ explicit nsBoxLayoutState(nsPresContext* aPresContext,
+ nsRenderingContext* aRenderingContext = nullptr,
+ // see OuterReflowInput() below
+ const ReflowInput* aOuterReflowInput = nullptr,
+ uint16_t aReflowDepth = 0);
+ nsBoxLayoutState(const nsBoxLayoutState& aState);
+
+ nsPresContext* PresContext() const { return mPresContext; }
+ nsIPresShell* PresShell() const { return mPresContext->PresShell(); }
+
+ uint32_t LayoutFlags() const { return mLayoutFlags; }
+ void SetLayoutFlags(uint32_t aFlags) { mLayoutFlags = aFlags; }
+
+ // if true no one under us will paint during reflow.
+ void SetPaintingDisabled(bool aDisable) { mPaintingDisabled = aDisable; }
+ bool PaintingDisabled() const { return mPaintingDisabled; }
+
+ // The rendering context may be null for specialized uses of
+ // nsBoxLayoutState and should be null-checked before it is used.
+ // However, passing a null rendering context to the constructor when
+ // doing box layout or intrinsic size calculation will cause bugs.
+ nsRenderingContext* GetRenderingContext() const { return mRenderingContext; }
+
+ struct AutoReflowDepth {
+ explicit AutoReflowDepth(nsBoxLayoutState& aState)
+ : mState(aState) { ++mState.mReflowDepth; }
+ ~AutoReflowDepth() { --mState.mReflowDepth; }
+ nsBoxLayoutState& mState;
+ };
+
+ // The HTML reflow state that lives outside the box-block boundary.
+ // May not be set reliably yet.
+ const ReflowInput* OuterReflowInput() { return mOuterReflowInput; }
+
+ uint16_t GetReflowDepth() { return mReflowDepth; }
+
+private:
+ RefPtr<nsPresContext> mPresContext;
+ nsRenderingContext *mRenderingContext;
+ const ReflowInput *mOuterReflowInput;
+ uint32_t mLayoutFlags;
+ uint16_t mReflowDepth;
+ bool mPaintingDisabled;
+};
+
+#endif
+
diff --git a/layout/xul/nsButtonBoxFrame.cpp b/layout/xul/nsButtonBoxFrame.cpp
new file mode 100644
index 000000000..45d934516
--- /dev/null
+++ b/layout/xul/nsButtonBoxFrame.cpp
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsButtonBoxFrame.h"
+#include "nsIContent.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsIDOMElement.h"
+#include "nsDisplayList.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+
+
+NS_IMPL_ISUPPORTS(nsButtonBoxFrame::nsButtonBoxListener, nsIDOMEventListener)
+
+nsresult
+nsButtonBoxFrame::nsButtonBoxListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ if (!mButtonBoxFrame) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("blur")) {
+ mButtonBoxFrame->Blurred();
+ return NS_OK;
+ }
+
+ NS_ABORT();
+
+ return NS_OK;
+}
+
+//
+// NS_NewXULButtonFrame
+//
+// Creates a new Button frame and returns it
+//
+nsIFrame*
+NS_NewButtonBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsButtonBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame)
+
+nsButtonBoxFrame::nsButtonBoxFrame(nsStyleContext* aContext) :
+ nsBoxFrame(aContext, false),
+ mButtonBoxListener(nullptr),
+ mIsHandlingKeyEvent(false)
+{
+ UpdateMouseThrough();
+}
+
+void
+nsButtonBoxFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mButtonBoxListener = new nsButtonBoxListener(this);
+
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false);
+}
+
+void
+nsButtonBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false);
+
+ mButtonBoxListener->mButtonBoxFrame = nullptr;
+ mButtonBoxListener = nullptr;
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsButtonBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // override, since we don't want children to get events
+ if (aBuilder->IsForEventDelivery())
+ return;
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+nsresult
+nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ switch (aEvent->mMessage) {
+ case eKeyDown: {
+ WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (NS_VK_SPACE == keyEvent->mKeyCode) {
+ EventStateManager* esm = aPresContext->EventStateManager();
+ // :hover:active state
+ esm->SetContentState(mContent, NS_EVENT_STATE_HOVER);
+ esm->SetContentState(mContent, NS_EVENT_STATE_ACTIVE);
+ mIsHandlingKeyEvent = true;
+ }
+ break;
+ }
+
+// On mac, Return fires the default button, not the focused one.
+#ifndef XP_MACOSX
+ case eKeyPress: {
+ WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (NS_VK_RETURN == keyEvent->mKeyCode) {
+ nsCOMPtr<nsIDOMXULButtonElement> buttonEl(do_QueryInterface(mContent));
+ if (buttonEl) {
+ MouseClicked(aEvent);
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+ }
+#endif
+
+ case eKeyUp: {
+ WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (NS_VK_SPACE == keyEvent->mKeyCode) {
+ mIsHandlingKeyEvent = false;
+ // only activate on keyup if we're already in the :hover:active state
+ NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
+ EventStates buttonState = mContent->AsElement()->State();
+ if (buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER)) {
+ // return to normal state
+ EventStateManager* esm = aPresContext->EventStateManager();
+ esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
+ esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+ MouseClicked(aEvent);
+ }
+ }
+ break;
+ }
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ MouseClicked(mouseEvent);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+void
+nsButtonBoxFrame::Blurred()
+{
+ NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
+ EventStates buttonState = mContent->AsElement()->State();
+ if (mIsHandlingKeyEvent &&
+ buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER)) {
+ // return to normal state
+ EventStateManager* esm = PresContext()->EventStateManager();
+ esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
+ esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+ }
+ mIsHandlingKeyEvent = false;
+}
+
+void
+nsButtonBoxFrame::DoMouseClick(WidgetGUIEvent* aEvent, bool aTrustEvent)
+{
+ // Don't execute if we're disabled.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ return;
+
+ // Execute the oncommand event handler.
+ bool isShift = false;
+ bool isControl = false;
+ bool isAlt = false;
+ bool isMeta = false;
+ if(aEvent) {
+ WidgetInputEvent* inputEvent = aEvent->AsInputEvent();
+ isShift = inputEvent->IsShift();
+ isControl = inputEvent->IsControl();
+ isAlt = inputEvent->IsAlt();
+ isMeta = inputEvent->IsMeta();
+ }
+
+ // Have the content handle the event, propagating it according to normal DOM rules.
+ nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
+ if (shell) {
+ nsContentUtils::DispatchXULCommand(mContent,
+ aEvent ?
+ aEvent->IsTrusted() : aTrustEvent,
+ nullptr, shell,
+ isControl, isAlt, isShift, isMeta);
+ }
+}
diff --git a/layout/xul/nsButtonBoxFrame.h b/layout/xul/nsButtonBoxFrame.h
new file mode 100644
index 000000000..e9bfd99a5
--- /dev/null
+++ b/layout/xul/nsButtonBoxFrame.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsButtonBoxFrame_h___
+#define nsButtonBoxFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMEventListener.h"
+#include "nsBoxFrame.h"
+
+class nsButtonBoxFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewButtonBoxFrame(nsIPresShell* aPresShell);
+
+ explicit nsButtonBoxFrame(nsStyleContext* aContext);
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void MouseClicked(mozilla::WidgetGUIEvent* aEvent)
+ { DoMouseClick(aEvent, false); }
+
+ void Blurred();
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("ButtonBoxFrame"), aResult);
+ }
+#endif
+
+ /**
+ * Our implementation of MouseClicked.
+ * @param aTrustEvent if true and aEvent as null, then assume the event was trusted
+ */
+ void DoMouseClick(mozilla::WidgetGUIEvent* aEvent, bool aTrustEvent);
+ void UpdateMouseThrough() override { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); }
+
+private:
+ class nsButtonBoxListener final : public nsIDOMEventListener
+ {
+ public:
+ explicit nsButtonBoxListener(nsButtonBoxFrame* aButtonBoxFrame) :
+ mButtonBoxFrame(aButtonBoxFrame)
+ { }
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ friend class nsButtonBoxFrame;
+ virtual ~nsButtonBoxListener() { }
+ nsButtonBoxFrame* mButtonBoxFrame;
+ };
+
+ RefPtr<nsButtonBoxListener> mButtonBoxListener;
+ bool mIsHandlingKeyEvent;
+}; // class nsButtonBoxFrame
+
+#endif /* nsButtonBoxFrame_h___ */
diff --git a/layout/xul/nsDeckFrame.cpp b/layout/xul/nsDeckFrame.cpp
new file mode 100644
index 000000000..b0c0296b2
--- /dev/null
+++ b/layout/xul/nsDeckFrame.cpp
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsDeckFrame.h"
+#include "nsStyleContext.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLParts.h"
+#include "nsIPresShell.h"
+#include "nsCSSRendering.h"
+#include "nsViewManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsStackLayout.h"
+#include "nsDisplayList.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+nsIFrame*
+NS_NewDeckFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsDeckFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsDeckFrame)
+
+NS_QUERYFRAME_HEAD(nsDeckFrame)
+ NS_QUERYFRAME_ENTRY(nsDeckFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+
+nsDeckFrame::nsDeckFrame(nsStyleContext* aContext)
+ : nsBoxFrame(aContext), mIndex(0)
+{
+ nsCOMPtr<nsBoxLayout> layout;
+ NS_NewStackLayout(layout);
+ SetXULLayoutManager(layout);
+}
+
+nsIAtom*
+nsDeckFrame::GetType() const
+{
+ return nsGkAtoms::deckFrame;
+}
+
+nsresult
+nsDeckFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+
+ // if the index changed hide the old element and make the new element visible
+ if (aAttribute == nsGkAtoms::selectedIndex) {
+ IndexChanged();
+ }
+
+ return rv;
+}
+
+void
+nsDeckFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mIndex = GetSelectedIndex();
+}
+
+void
+nsDeckFrame::HideBox(nsIFrame* aBox)
+{
+ nsIPresShell::ClearMouseCapture(aBox);
+}
+
+void
+nsDeckFrame::IndexChanged()
+{
+ //did the index change?
+ int32_t index = GetSelectedIndex();
+ if (index == mIndex)
+ return;
+
+ // redraw
+ InvalidateFrame();
+
+ // hide the currently showing box
+ nsIFrame* currentBox = GetSelectedBox();
+ if (currentBox) // only hide if it exists
+ HideBox(currentBox);
+
+ mIndex = index;
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ accService->DeckPanelSwitched(PresContext()->GetPresShell(), mContent,
+ currentBox, GetSelectedBox());
+ }
+#endif
+}
+
+int32_t
+nsDeckFrame::GetSelectedIndex()
+{
+ // default index is 0
+ int32_t index = 0;
+
+ // get the index attribute
+ nsAutoString value;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selectedIndex, value))
+ {
+ nsresult error;
+
+ // convert it to an integer
+ index = value.ToInteger(&error);
+ }
+
+ return index;
+}
+
+nsIFrame*
+nsDeckFrame::GetSelectedBox()
+{
+ return (mIndex >= 0) ? mFrames.FrameAt(mIndex) : nullptr;
+}
+
+void
+nsDeckFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // if a tab is hidden all its children are too.
+ if (!StyleVisibility()->mVisible)
+ return;
+
+ nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+}
+
+void
+nsDeckFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ nsIFrame* currentFrame = GetSelectedBox();
+ if (currentFrame &&
+ aOldFrame &&
+ currentFrame != aOldFrame) {
+ // If the frame we're removing is at an index that's less
+ // than mIndex, that means we're going to be shifting indexes
+ // by 1.
+ //
+ // We attempt to keep the same child displayed by automatically
+ // updating our internal notion of the current index.
+ int32_t removedIndex = mFrames.IndexOf(aOldFrame);
+ MOZ_ASSERT(removedIndex >= 0,
+ "A deck child was removed that was not in mFrames.");
+ if (removedIndex < mIndex) {
+ mIndex--;
+ // This is going to cause us to handle the index change in IndexedChanged,
+ // but since the new index will match mIndex, it's essentially a noop.
+ nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
+ mContent, nsGkAtoms::selectedIndex, mIndex));
+ }
+ }
+ nsBoxFrame::RemoveFrame(aListID, aOldFrame);
+}
+
+void
+nsDeckFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // only paint the selected box
+ nsIFrame* box = GetSelectedBox();
+ if (!box)
+ return;
+
+ // Putting the child in the background list. This is a little weird but
+ // it matches what we were doing before.
+ nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
+ BuildDisplayListForChild(aBuilder, box, aDirtyRect, set);
+}
+
+NS_IMETHODIMP
+nsDeckFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ // Make sure we tweak the state so it does not resize our children.
+ // We will do that.
+ uint32_t oldFlags = aState.LayoutFlags();
+ aState.SetLayoutFlags(NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_VISIBILITY);
+
+ // do a normal layout
+ nsresult rv = nsBoxFrame::DoXULLayout(aState);
+
+ // run though each child. Hide all but the selected one
+ nsIFrame* box = nsBox::GetChildXULBox(this);
+
+ nscoord count = 0;
+ while (box)
+ {
+ // make collapsed children not show up
+ if (count != mIndex)
+ HideBox(box);
+
+ box = GetNextXULBox(box);
+ count++;
+ }
+
+ aState.SetLayoutFlags(oldFlags);
+
+ return rv;
+}
+
diff --git a/layout/xul/nsDeckFrame.h b/layout/xul/nsDeckFrame.h
new file mode 100644
index 000000000..2c7ae1445
--- /dev/null
+++ b/layout/xul/nsDeckFrame.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ A frame that can have multiple children. Only one child may be displayed at one time. So the
+ can be flipped though like a deck of cards.
+
+**/
+
+#ifndef nsDeckFrame_h___
+#define nsDeckFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsDeckFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsDeckFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewDeckFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aState) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("Deck"), aResult);
+ }
+#endif
+
+ explicit nsDeckFrame(nsStyleContext* aContext);
+
+ nsIFrame* GetSelectedBox();
+
+protected:
+
+ void IndexChanged();
+ int32_t GetSelectedIndex();
+ void HideBox(nsIFrame* aBox);
+
+private:
+
+ int32_t mIndex;
+
+}; // class nsDeckFrame
+
+#endif
+
diff --git a/layout/xul/nsDocElementBoxFrame.cpp b/layout/xul/nsDocElementBoxFrame.cpp
new file mode 100644
index 000000000..4d978617a
--- /dev/null
+++ b/layout/xul/nsDocElementBoxFrame.cpp
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsHTMLParts.h"
+#include "nsContainerFrame.h"
+#include "nsCSSRendering.h"
+#include "nsIDocument.h"
+#include "nsPageFrame.h"
+#include "nsIDOMEvent.h"
+#include "nsStyleConsts.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsBoxFrame.h"
+#include "nsStackLayout.h"
+#include "nsIAnonymousContentCreator.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIServiceManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsContentList.h"
+#include "mozilla/dom/Element.h"
+
+//#define DEBUG_REFLOW
+
+using namespace mozilla::dom;
+
+class nsDocElementBoxFrame : public nsBoxFrame,
+ public nsIAnonymousContentCreator
+{
+public:
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ explicit nsDocElementBoxFrame(nsStyleContext* aContext)
+ :nsBoxFrame(aContext, true) {}
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // Override nsBoxFrame.
+ if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced))
+ return false;
+ return nsBoxFrame::IsFrameOfType(aFlags);
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+private:
+ nsCOMPtr<Element> mPopupgroupContent;
+ nsCOMPtr<Element> mTooltipContent;
+};
+
+//----------------------------------------------------------------------
+
+nsContainerFrame*
+NS_NewDocElementBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsDocElementBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsDocElementBoxFrame)
+
+void
+nsDocElementBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsContentUtils::DestroyAnonymousContent(&mPopupgroupContent);
+ nsContentUtils::DestroyAnonymousContent(&mTooltipContent);
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsDocElementBoxFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsIDocument* doc = mContent->GetComposedDoc();
+ if (!doc) {
+ // The page is currently being torn down. Why bother.
+ return NS_ERROR_FAILURE;
+ }
+ nsNodeInfoManager *nodeInfoManager = doc->NodeInfoManager();
+
+ // create the top-secret popupgroup node. shhhhh!
+ RefPtr<NodeInfo> nodeInfo;
+ nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::popupgroup,
+ nullptr, kNameSpaceID_XUL,
+ nsIDOMNode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = NS_NewXULElement(getter_AddRefs(mPopupgroupContent),
+ nodeInfo.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aElements.AppendElement(mPopupgroupContent))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // create the top-secret default tooltip node. shhhhh!
+ nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::tooltip, nullptr,
+ kNameSpaceID_XUL,
+ nsIDOMNode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = NS_NewXULElement(getter_AddRefs(mTooltipContent), nodeInfo.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_default,
+ NS_LITERAL_STRING("true"), false);
+
+ if (!aElements.AppendElement(mTooltipContent))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+void
+nsDocElementBoxFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mPopupgroupContent) {
+ aElements.AppendElement(mPopupgroupContent);
+ }
+
+ if (mTooltipContent) {
+ aElements.AppendElement(mTooltipContent);
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsDocElementBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsDocElementBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("DocElementBox"), aResult);
+}
+#endif
diff --git a/layout/xul/nsGroupBoxFrame.cpp b/layout/xul/nsGroupBoxFrame.cpp
new file mode 100644
index 000000000..514287a24
--- /dev/null
+++ b/layout/xul/nsGroupBoxFrame.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// YY need to pass isMultiple before create called
+
+#include "nsBoxFrame.h"
+
+#include "mozilla/gfx/2D.h"
+#include "nsCSSRendering.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsStyleContext.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+class nsGroupBoxFrame : public nsBoxFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsGroupBoxFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext) {}
+
+ virtual nsresult GetXULBorderAndPadding(nsMargin& aBorderAndPadding) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("GroupBoxFrame"), aResult);
+ }
+#endif
+
+ virtual bool HonorPrintBackgroundSettings() override { return false; }
+
+ DrawResult PaintBorder(nsRenderingContext& aRenderingContext,
+ nsPoint aPt,
+ const nsRect& aDirtyRect);
+ nsRect GetBackgroundRectRelativeToSelf(nscoord* aOutYOffset = nullptr, nsRect* aOutGroupRect = nullptr);
+
+ // make sure we our kids get our orient and align instead of us.
+ // our child box has no content node so it will search for a parent with one.
+ // that will be us.
+ virtual void GetInitialOrientation(bool& aHorizontal) override { aHorizontal = false; }
+ virtual bool GetInitialHAlignment(Halignment& aHalign) override { aHalign = hAlign_Left; return true; }
+ virtual bool GetInitialVAlignment(Valignment& aValign) override { aValign = vAlign_Top; return true; }
+ virtual bool GetInitialAutoStretch(bool& aStretch) override { aStretch = true; return true; }
+
+ nsIFrame* GetCaptionBox(nsRect& aCaptionRect);
+};
+
+/*
+class nsGroupBoxInnerFrame : public nsBoxFrame {
+public:
+
+ nsGroupBoxInnerFrame(nsIPresShell* aShell, nsStyleContext* aContext):
+ nsBoxFrame(aShell, aContext) {}
+
+
+#ifdef DEBUG_FRAME_DUMP
+ NS_IMETHOD GetFrameName(nsString& aResult) const {
+ return MakeFrameName("GroupBoxFrameInner", aResult);
+ }
+#endif
+
+ // we are always flexible
+ virtual bool GetDefaultFlex(int32_t& aFlex) { aFlex = 1; return true; }
+
+};
+*/
+
+nsIFrame*
+NS_NewGroupBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsGroupBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGroupBoxFrame)
+
+class nsDisplayXULGroupBorder : public nsDisplayItem {
+public:
+ nsDisplayXULGroupBorder(nsDisplayListBuilder* aBuilder,
+ nsGroupBoxFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULGroupBorder);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULGroupBorder() {
+ MOZ_COUNT_DTOR(nsDisplayXULGroupBorder);
+ }
+#endif
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("XULGroupBackground", TYPE_XUL_GROUP_BACKGROUND)
+};
+
+nsDisplayItemGeometry*
+nsDisplayXULGroupBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayXULGroupBorder::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+void
+nsDisplayXULGroupBorder::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawResult result = static_cast<nsGroupBoxFrame*>(mFrame)
+ ->PaintBorder(*aCtx, ToReferenceFrame(), mVisibleRect);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+void
+nsGroupBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // Paint our background and border
+ if (IsVisibleForPainting(aBuilder)) {
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, this, GetBackgroundRectRelativeToSelf(),
+ aLists.BorderBackground());
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayXULGroupBorder(aBuilder, this));
+
+ DisplayOutline(aBuilder, aLists);
+ }
+
+ BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+nsRect
+nsGroupBoxFrame::GetBackgroundRectRelativeToSelf(nscoord* aOutYOffset, nsRect* aOutGroupRect)
+{
+ const nsMargin& border = StyleBorder()->GetComputedBorder();
+
+ nsRect groupRect;
+ nsIFrame* groupBox = GetCaptionBox(groupRect);
+
+ nscoord yoff = 0;
+ if (groupBox) {
+ // If the border is smaller than the legend, move the border down
+ // to be centered on the legend.
+ nsMargin groupMargin;
+ groupBox->StyleMargin()->GetMargin(groupMargin);
+ groupRect.Inflate(groupMargin);
+
+ if (border.top < groupRect.height) {
+ yoff = (groupRect.height - border.top) / 2 + groupRect.y;
+ }
+ }
+
+ if (aOutYOffset) {
+ *aOutYOffset = yoff;
+ }
+ if (aOutGroupRect) {
+ *aOutGroupRect = groupRect;
+ }
+
+ return nsRect(0, yoff, mRect.width, mRect.height - yoff);
+}
+
+DrawResult
+nsGroupBoxFrame::PaintBorder(nsRenderingContext& aRenderingContext,
+ nsPoint aPt, const nsRect& aDirtyRect) {
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ gfxContext* gfx = aRenderingContext.ThebesContext();
+
+ Sides skipSides;
+ const nsStyleBorder* borderStyleData = StyleBorder();
+ const nsMargin& border = borderStyleData->GetComputedBorder();
+ nsPresContext* presContext = PresContext();
+
+ nsRect groupRect;
+ nsIFrame* groupBox = GetCaptionBox(groupRect);
+
+ nscoord yoff = 0;
+ nsRect rect = GetBackgroundRectRelativeToSelf(&yoff, &groupRect) + aPt;
+ groupRect += aPt;
+
+ DrawResult result = DrawResult::SUCCESS;
+ if (groupBox) {
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+
+ // we should probably use PaintBorderEdges to do this but for now just use clipping
+ // to achieve the same effect.
+
+ // draw left side
+ nsRect clipRect(rect);
+ clipRect.width = groupRect.x - rect.x;
+ clipRect.height = border.top;
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides);
+ gfx->Restore();
+
+ // draw right side
+ clipRect = rect;
+ clipRect.x = groupRect.XMost();
+ clipRect.width = rect.XMost() - groupRect.XMost();
+ clipRect.height = border.top;
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides);
+ gfx->Restore();
+
+ // draw bottom
+
+ clipRect = rect;
+ clipRect.y += border.top;
+ clipRect.height = mRect.height - (yoff + border.top);
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides);
+ gfx->Restore();
+
+ } else {
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, nsRect(aPt, GetSize()),
+ mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides);
+ }
+
+ return result;
+}
+
+nsIFrame*
+nsGroupBoxFrame::GetCaptionBox(nsRect& aCaptionRect)
+{
+ // first child is our grouped area
+ nsIFrame* box = nsBox::GetChildXULBox(this);
+
+ // no area fail.
+ if (!box)
+ return nullptr;
+
+ // get the first child in the grouped area, that is the caption
+ box = nsBox::GetChildXULBox(box);
+
+ // nothing in the area? fail
+ if (!box)
+ return nullptr;
+
+ // now get the caption itself. It is in the caption frame.
+ nsIFrame* child = nsBox::GetChildXULBox(box);
+
+ if (child) {
+ // convert to our coordinates.
+ nsRect parentRect(box->GetRect());
+ aCaptionRect = child->GetRect();
+ aCaptionRect.x += parentRect.x;
+ aCaptionRect.y += parentRect.y;
+ }
+
+ return child;
+}
+
+nsresult
+nsGroupBoxFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding)
+{
+ aBorderAndPadding.SizeTo(0,0,0,0);
+ return NS_OK;
+}
+
diff --git a/layout/xul/nsIBoxObject.idl b/layout/xul/nsIBoxObject.idl
new file mode 100644
index 000000000..64b1a46f8
--- /dev/null
+++ b/layout/xul/nsIBoxObject.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+
+[scriptable, uuid(ce572460-b0f2-4650-a9e7-c53a99d3b6ad)]
+interface nsIBoxObject : nsISupports
+{
+ readonly attribute nsIDOMElement element;
+
+ readonly attribute long x;
+ readonly attribute long y;
+ readonly attribute long screenX;
+ readonly attribute long screenY;
+ readonly attribute long width;
+ readonly attribute long height;
+
+ nsISupports getPropertyAsSupports(in wstring propertyName);
+ void setPropertyAsSupports(in wstring propertyName, in nsISupports value);
+ wstring getProperty(in wstring propertyName);
+ void setProperty(in wstring propertyName, in wstring propertyValue);
+ void removeProperty(in wstring propertyName);
+
+ // for stepping through content in the expanded dom with box-ordinal-group order
+ readonly attribute nsIDOMElement parentBox;
+ readonly attribute nsIDOMElement firstChild;
+ readonly attribute nsIDOMElement lastChild;
+ readonly attribute nsIDOMElement nextSibling;
+ readonly attribute nsIDOMElement previousSibling;
+};
+
+%{C++
+nsresult
+NS_NewBoxObject(nsIBoxObject** aResult);
+
+%}
diff --git a/layout/xul/nsIBrowserBoxObject.idl b/layout/xul/nsIBrowserBoxObject.idl
new file mode 100644
index 000000000..f3eee9159
--- /dev/null
+++ b/layout/xul/nsIBrowserBoxObject.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIContainerBoxObject.idl"
+
+/**
+ * @deprecated Please consider using ContainerBoxObject.
+ */
+
+[uuid(db436f2f-c656-4754-b0fa-99bc353bd63f)]
+interface nsIBrowserBoxObject : nsIContainerBoxObject
+{
+};
+
diff --git a/layout/xul/nsIContainerBoxObject.idl b/layout/xul/nsIContainerBoxObject.idl
new file mode 100644
index 000000000..f2eab0fbd
--- /dev/null
+++ b/layout/xul/nsIContainerBoxObject.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// DEPRECATED: This file exists for shim purposes only,
+// see ContainerBoxObject.webidl
+
+[uuid(35d4c04b-3bd3-4375-92e2-a818b4b4acb6)]
+interface nsIContainerBoxObject : nsISupports
+{
+};
diff --git a/layout/xul/nsIListBoxObject.idl b/layout/xul/nsIListBoxObject.idl
new file mode 100644
index 000000000..198f76919
--- /dev/null
+++ b/layout/xul/nsIListBoxObject.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// DEPRECATED: see ListBoxObject.webidl
+
+interface nsIDOMElement;
+
+[uuid(AA9DEF4E-2E59-412d-A6DF-B76F52167795)]
+interface nsIListBoxObject : nsISupports
+{
+ long getRowCount();
+
+ nsIDOMElement getItemAtIndex(in long index);
+ long getIndexOfItem(in nsIDOMElement item);
+};
diff --git a/layout/xul/nsIMenuBoxObject.idl b/layout/xul/nsIMenuBoxObject.idl
new file mode 100644
index 000000000..63c7811c4
--- /dev/null
+++ b/layout/xul/nsIMenuBoxObject.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// DEPRECATED: This file exists for shim purposes only,
+// see MenuBoxObject.webidl
+
+[uuid(689ebf3d-0184-450a-9bfa-5a26be0e7a8c)]
+interface nsIMenuBoxObject : nsISupports
+{
+};
diff --git a/layout/xul/nsIRootBox.h b/layout/xul/nsIRootBox.h
new file mode 100644
index 000000000..9311f547e
--- /dev/null
+++ b/layout/xul/nsIRootBox.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsIRootBox_h___
+#define nsIRootBox_h___
+
+#include "nsQueryFrame.h"
+class nsPopupSetFrame;
+class nsIContent;
+class nsIPresShell;
+
+class nsIRootBox
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIRootBox)
+
+ virtual nsPopupSetFrame* GetPopupSetFrame() = 0;
+ virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) = 0;
+
+ virtual nsIContent* GetDefaultTooltip() = 0;
+ virtual void SetDefaultTooltip(nsIContent* aTooltip) = 0;
+
+ virtual nsresult AddTooltipSupport(nsIContent* aNode) = 0;
+ virtual nsresult RemoveTooltipSupport(nsIContent* aNode) = 0;
+
+ static nsIRootBox* GetRootBox(nsIPresShell* aShell);
+};
+
+#endif
+
diff --git a/layout/xul/nsIScrollBoxObject.idl b/layout/xul/nsIScrollBoxObject.idl
new file mode 100644
index 000000000..fc16f61b1
--- /dev/null
+++ b/layout/xul/nsIScrollBoxObject.idl
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+
+[uuid(56E2ADA8-4631-11d4-BA11-001083023C1E)]
+interface nsIScrollBoxObject : nsISupports
+{
+};
diff --git a/layout/xul/nsIScrollbarMediator.h b/layout/xul/nsIScrollbarMediator.h
new file mode 100644
index 000000000..87b82e3d9
--- /dev/null
+++ b/layout/xul/nsIScrollbarMediator.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScrollbarMediator_h___
+#define nsIScrollbarMediator_h___
+
+#include "nsQueryFrame.h"
+#include "nsCoord.h"
+
+class nsScrollbarFrame;
+class nsIFrame;
+
+class nsIScrollbarMediator : public nsQueryFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIScrollbarMediator)
+
+ /**
+ * The aScrollbar argument denotes the scrollbar that's firing the notification.
+ * aScrollbar is never null.
+ * aDirection is either -1, 0, or 1.
+ */
+
+ /**
+ * When set to ENABLE_SNAP, additional scrolling will be performed after the
+ * scroll operation to maintain the constraints set by CSS Scroll snapping.
+ * The additional scrolling may include asynchronous smooth scrolls that
+ * continue to animate after the initial scroll position has been set.
+ */
+ enum ScrollSnapMode { DISABLE_SNAP, ENABLE_SNAP };
+
+ /**
+ * One of the following three methods is called when the scrollbar's button is
+ * clicked.
+ * @note These methods might destroy the frame, pres shell, and other objects.
+ */
+ virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapMode aSnap = DISABLE_SNAP) = 0;
+ virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapMode aSnap = DISABLE_SNAP) = 0;
+ virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapMode aSnap = DISABLE_SNAP) = 0;
+ /**
+ * RepeatButtonScroll is called when the scrollbar's button is held down. When the
+ * button is first clicked the increment is set; RepeatButtonScroll adds this
+ * increment to the current position.
+ * @note This method might destroy the frame, pres shell, and other objects.
+ */
+ virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) = 0;
+ /**
+ * aOldPos and aNewPos are scroll positions.
+ * The scroll positions start with zero at the left edge; implementors that want
+ * zero at the right edge for RTL content will need to adjust accordingly.
+ * (See ScrollFrameHelper::ThumbMoved in nsGfxScrollFrame.cpp.)
+ * @note This method might destroy the frame, pres shell, and other objects.
+ */
+ virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos) = 0;
+ /**
+ * Called when the scroll bar thumb, slider, or any other component is
+ * released.
+ */
+ virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) = 0;
+ virtual void VisibilityChanged(bool aVisible) = 0;
+
+ /**
+ * Obtain the frame for the horizontal or vertical scrollbar, or null
+ * if there is no such box.
+ */
+ virtual nsIFrame* GetScrollbarBox(bool aVertical) = 0;
+ /**
+ * Show or hide scrollbars on 2 fingers touch.
+ * Subclasses should call their ScrollbarActivity's corresponding methods.
+ */
+ virtual void ScrollbarActivityStarted() const = 0;
+ virtual void ScrollbarActivityStopped() const = 0;
+
+ virtual bool IsScrollbarOnRight() const = 0;
+
+ /**
+ * Returns true if the mediator is asking the scrollbar to suppress
+ * repainting itself on changes.
+ */
+ virtual bool ShouldSuppressScrollbarRepaints() const = 0;
+};
+
+#endif
+
diff --git a/layout/xul/nsISliderListener.idl b/layout/xul/nsISliderListener.idl
new file mode 100644
index 000000000..9605782f0
--- /dev/null
+++ b/layout/xul/nsISliderListener.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Used for <scale> to listen to slider changes to avoid mutation listeners
+ */
+[scriptable, uuid(e5b3074e-ee18-4538-83b9-2487d90a2a34)]
+interface nsISliderListener : nsISupports
+{
+ /**
+ * Called when the current, minimum or maximum value has been changed to
+ * newValue. The which parameter will either be 'curpos', 'minpos' or 'maxpos'.
+ * If userChanged is true, then the user changed ths slider, otherwise it
+ * was changed via some other means.
+ */
+ void valueChanged(in AString which, in long newValue, in boolean userChanged);
+
+ /**
+ * Called when the user begins or ends dragging the thumb.
+ */
+ void dragStateChanged(in boolean isDragging);
+};
diff --git a/layout/xul/nsImageBoxFrame.cpp b/layout/xul/nsImageBoxFrame.cpp
new file mode 100644
index 000000000..fd7c7becf
--- /dev/null
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -0,0 +1,828 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsImageBoxFrame.h"
+#include "nsGkAtoms.h"
+#include "nsRenderingContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsStyleUtil.h"
+#include "nsCOMPtr.h"
+#include "nsPresContext.h"
+#include "nsBoxLayoutState.h"
+
+#include "nsHTMLParts.h"
+#include "nsString.h"
+#include "nsLeafFrame.h"
+#include "nsIPresShell.h"
+#include "nsIDocument.h"
+#include "nsImageMap.h"
+#include "nsILinkHandler.h"
+#include "nsIURL.h"
+#include "nsILoadGroup.h"
+#include "nsContainerFrame.h"
+#include "prprf.h"
+#include "nsCSSRendering.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsNameSpaceManager.h"
+#include "nsTextFragment.h"
+#include "nsIDOMHTMLMapElement.h"
+#include "nsTransform2D.h"
+#include "nsITheme.h"
+
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsThreadUtils.h"
+#include "nsDisplayList.h"
+#include "ImageLayers.h"
+#include "ImageContainer.h"
+#include "nsIContent.h"
+
+#include "nsContentUtils.h"
+#include "nsSerializationHelper.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Maybe.h"
+
+#define ONLOAD_CALLED_TOO_EARLY 1
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+
+class nsImageBoxFrameEvent : public Runnable
+{
+public:
+ nsImageBoxFrameEvent(nsIContent *content, EventMessage message)
+ : mContent(content), mMessage(message) {}
+
+ NS_IMETHOD Run() override;
+
+private:
+ nsCOMPtr<nsIContent> mContent;
+ EventMessage mMessage;
+};
+
+NS_IMETHODIMP
+nsImageBoxFrameEvent::Run()
+{
+ nsIPresShell *pres_shell = mContent->OwnerDoc()->GetShell();
+ if (!pres_shell) {
+ return NS_OK;
+ }
+
+ RefPtr<nsPresContext> pres_context = pres_shell->GetPresContext();
+ if (!pres_context) {
+ return NS_OK;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, mMessage);
+
+ event.mFlags.mBubbles = false;
+ EventDispatcher::Dispatch(mContent, pres_context, &event, nullptr, &status);
+ return NS_OK;
+}
+
+// Fire off an event that'll asynchronously call the image elements
+// onload handler once handled. This is needed since the image library
+// can't decide if it wants to call it's observer methods
+// synchronously or asynchronously. If an image is loaded from the
+// cache the notifications come back synchronously, but if the image
+// is loaded from the netswork the notifications come back
+// asynchronously.
+
+void
+FireImageDOMEvent(nsIContent* aContent, EventMessage aMessage)
+{
+ NS_ASSERTION(aMessage == eLoad || aMessage == eLoadError,
+ "invalid message");
+
+ nsCOMPtr<nsIRunnable> event = new nsImageBoxFrameEvent(aContent, aMessage);
+ if (NS_FAILED(NS_DispatchToCurrentThread(event)))
+ NS_WARNING("failed to dispatch image event");
+}
+
+//
+// NS_NewImageBoxFrame
+//
+// Creates a new image frame and returns it
+//
+nsIFrame*
+NS_NewImageBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsImageBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame)
+
+nsresult
+nsImageBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsLeafBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ if (aAttribute == nsGkAtoms::src) {
+ UpdateImage();
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ else if (aAttribute == nsGkAtoms::validate)
+ UpdateLoadFlags();
+
+ return rv;
+}
+
+nsImageBoxFrame::nsImageBoxFrame(nsStyleContext* aContext):
+ nsLeafBoxFrame(aContext),
+ mIntrinsicSize(0,0),
+ mLoadFlags(nsIRequest::LOAD_NORMAL),
+ mRequestRegistered(false),
+ mUseSrcAttr(false),
+ mSuppressStyleCheck(false)
+{
+ MarkIntrinsicISizesDirty();
+}
+
+nsImageBoxFrame::~nsImageBoxFrame()
+{
+}
+
+
+/* virtual */ void
+nsImageBoxFrame::MarkIntrinsicISizesDirty()
+{
+ SizeNeedsRecalc(mImageSize);
+ nsLeafBoxFrame::MarkIntrinsicISizesDirty();
+}
+
+void
+nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (mImageRequest) {
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
+ &mRequestRegistered);
+
+ // Release image loader first so that it's refcnt can go to zero
+ mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
+ }
+
+ if (mListener)
+ reinterpret_cast<nsImageBoxListener*>(mListener.get())->SetFrame(nullptr); // set the frame to null so we don't send messages to a dead object.
+
+ nsLeafBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+
+void
+nsImageBoxFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ if (!mListener) {
+ RefPtr<nsImageBoxListener> listener = new nsImageBoxListener();
+ listener->SetFrame(this);
+ mListener = listener.forget();
+ }
+
+ mSuppressStyleCheck = true;
+ nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
+ mSuppressStyleCheck = false;
+
+ UpdateLoadFlags();
+ UpdateImage();
+}
+
+void
+nsImageBoxFrame::UpdateImage()
+{
+ nsPresContext* presContext = PresContext();
+
+ RefPtr<imgRequestProxy> oldImageRequest = mImageRequest;
+
+ if (mImageRequest) {
+ nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest,
+ &mRequestRegistered);
+ mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
+ mImageRequest = nullptr;
+ }
+
+ // get the new image src
+ nsAutoString src;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
+ mUseSrcAttr = !src.IsEmpty();
+ if (mUseSrcAttr) {
+ nsIDocument* doc = mContent->GetComposedDoc();
+ if (doc) {
+ // Use the serialized loadingPrincipal from the image element. Fall back
+ // to mContent's principal (SystemPrincipal) if not available.
+ nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = mContent->NodePrincipal();
+ nsAutoString imageLoadingPrincipal;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::loadingprincipal,
+ imageLoadingPrincipal);
+ if (!imageLoadingPrincipal.IsEmpty()) {
+ nsCOMPtr<nsISupports> serializedPrincipal;
+ NS_DeserializeObject(NS_ConvertUTF16toUTF8(imageLoadingPrincipal),
+ getter_AddRefs(serializedPrincipal));
+ loadingPrincipal = do_QueryInterface(serializedPrincipal);
+
+ if (loadingPrincipal) {
+ // Set the content policy type to TYPE_INTERNAL_IMAGE_FAVICON for
+ // indicating it's a favicon loading.
+ contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON;
+ } else {
+ // Fallback if the deserialization is failed.
+ loadingPrincipal = mContent->NodePrincipal();
+ }
+ }
+
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
+ src,
+ doc,
+ baseURI);
+ if (uri) {
+ nsresult rv = nsContentUtils::LoadImage(uri, mContent, doc, loadingPrincipal,
+ doc->GetDocumentURI(), doc->GetReferrerPolicy(),
+ mListener, mLoadFlags,
+ EmptyString(), getter_AddRefs(mImageRequest),
+ contentPolicyType);
+
+ if (NS_SUCCEEDED(rv) && mImageRequest) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext,
+ mImageRequest,
+ &mRequestRegistered);
+ }
+ }
+ }
+ } else {
+ // Only get the list-style-image if we aren't being drawn
+ // by a native theme.
+ uint8_t appearance = StyleDisplay()->mAppearance;
+ if (!(appearance && nsBox::gTheme &&
+ nsBox::gTheme->ThemeSupportsWidget(nullptr, this, appearance))) {
+ // get the list-style-image
+ imgRequestProxy *styleRequest = StyleList()->GetListStyleImage();
+ if (styleRequest) {
+ styleRequest->Clone(mListener, getter_AddRefs(mImageRequest));
+ }
+ }
+ }
+
+ if (!mImageRequest) {
+ // We have no image, so size to 0
+ mIntrinsicSize.SizeTo(0, 0);
+ } else {
+ // We don't want discarding or decode-on-draw for xul images.
+ mImageRequest->StartDecoding();
+ mImageRequest->LockImage();
+ }
+
+ // Do this _after_ locking the new image in case they are the same image.
+ if (oldImageRequest) {
+ oldImageRequest->UnlockImage();
+ }
+}
+
+void
+nsImageBoxFrame::UpdateLoadFlags()
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::always, &nsGkAtoms::never, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::validate,
+ strings, eCaseMatters)) {
+ case 0:
+ mLoadFlags = nsIRequest::VALIDATE_ALWAYS;
+ break;
+ case 1:
+ mLoadFlags = nsIRequest::VALIDATE_NEVER|nsIRequest::LOAD_FROM_CACHE;
+ break;
+ default:
+ mLoadFlags = nsIRequest::LOAD_NORMAL;
+ break;
+ }
+}
+
+void
+nsImageBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ if ((0 == mRect.width) || (0 == mRect.height)) {
+ // Do not render when given a zero area. This avoids some useless
+ // scaling work while we wait for our image dimensions to arrive
+ // asynchronously.
+ return;
+ }
+
+ if (!IsVisibleForPainting(aBuilder))
+ return;
+
+ uint32_t clipFlags =
+ nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
+ 0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
+
+ DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
+ clip(aBuilder, this, clipFlags);
+
+ nsDisplayList list;
+ list.AppendNewToTop(
+ new (aBuilder) nsDisplayXULImage(aBuilder, this));
+
+ CreateOwnLayerIfNeeded(aBuilder, &list);
+
+ aLists.Content()->AppendToTop(&list);
+}
+
+DrawResult
+nsImageBoxFrame::PaintImage(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ uint32_t aFlags)
+{
+ nsRect constraintRect;
+ GetXULClientRect(constraintRect);
+
+ constraintRect += aPt;
+
+ if (!mImageRequest) {
+ // This probably means we're drawn by a native theme.
+ return DrawResult::SUCCESS;
+ }
+
+ // don't draw if the image is not dirty
+ // XXX(seth): Can this actually happen anymore?
+ nsRect dirty;
+ if (!dirty.IntersectRect(aDirtyRect, constraintRect)) {
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ // Don't draw if the image's size isn't available.
+ uint32_t imgStatus;
+ if (!NS_SUCCEEDED(mImageRequest->GetImageStatus(&imgStatus)) ||
+ !(imgStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
+ return DrawResult::NOT_READY;
+ }
+
+ nsCOMPtr<imgIContainer> imgCon;
+ mImageRequest->GetImage(getter_AddRefs(imgCon));
+
+ if (!imgCon) {
+ return DrawResult::NOT_READY;
+ }
+
+ bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0);
+
+ Maybe<nsPoint> anchorPoint;
+ nsRect dest;
+ if (!mUseSrcAttr) {
+ // Our image (if we have one) is coming from the CSS property
+ // 'list-style-image' (combined with '-moz-image-region'). For now, ignore
+ // 'object-fit' & 'object-position' in this case, and just fill our rect.
+ // XXXdholbert Should we even honor these properties in this case? They only
+ // apply to replaced elements, and I'm not sure we count as a replaced
+ // element when our image data is determined by CSS.
+ dest = constraintRect;
+ } else {
+ // Determine dest rect based on intrinsic size & ratio, along with
+ // 'object-fit' & 'object-position' properties:
+ IntrinsicSize intrinsicSize;
+ nsSize intrinsicRatio;
+ if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) {
+ // Image has a valid size; use it as intrinsic size & ratio.
+ intrinsicSize.width.SetCoordValue(mIntrinsicSize.width);
+ intrinsicSize.height.SetCoordValue(mIntrinsicSize.height);
+ intrinsicRatio = mIntrinsicSize;
+ } else {
+ // Image doesn't have a (valid) intrinsic size.
+ // Try to look up intrinsic ratio and use that at least.
+ imgCon->GetIntrinsicRatio(&intrinsicRatio);
+ }
+ anchorPoint.emplace();
+ dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect,
+ intrinsicSize,
+ intrinsicRatio,
+ StylePosition(),
+ anchorPoint.ptr());
+ }
+
+
+ return nsLayoutUtils::DrawSingleImage(
+ *aRenderingContext.ThebesContext(),
+ PresContext(), imgCon,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ dest, dirty, nullptr, aFlags,
+ anchorPoint.ptrOr(nullptr),
+ hasSubRect ? &mSubRect : nullptr);
+}
+
+void nsDisplayXULImage::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ uint32_t flags = imgIContainer::FLAG_NONE;
+ if (aBuilder->ShouldSyncDecodeImages())
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ if (aBuilder->IsPaintingToWindow())
+ flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+
+ DrawResult result = static_cast<nsImageBoxFrame*>(mFrame)->
+ PaintImage(*aCtx, mVisibleRect, ToReferenceFrame(), flags);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+nsDisplayItemGeometry*
+nsDisplayXULImage::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayXULImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto boxFrame = static_cast<nsImageBoxFrame*>(mFrame);
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ boxFrame->mImageRequest &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+bool
+nsDisplayXULImage::CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder)
+{
+ nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame);
+ if (!imageFrame->CanOptimizeToImageLayer()) {
+ return false;
+ }
+
+ return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder);
+}
+
+already_AddRefed<imgIContainer>
+nsDisplayXULImage::GetImage()
+{
+ nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame);
+ if (!imageFrame->mImageRequest) {
+ return nullptr;
+ }
+
+ nsCOMPtr<imgIContainer> imgCon;
+ imageFrame->mImageRequest->GetImage(getter_AddRefs(imgCon));
+
+ return imgCon.forget();
+}
+
+nsRect
+nsDisplayXULImage::GetDestRect()
+{
+ nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame);
+
+ nsRect clientRect;
+ imageFrame->GetXULClientRect(clientRect);
+
+ return clientRect + ToReferenceFrame();
+}
+
+bool
+nsImageBoxFrame::CanOptimizeToImageLayer()
+{
+ bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0);
+ if (hasSubRect) {
+ return false;
+ }
+ return true;
+}
+
+//
+// DidSetStyleContext
+//
+// When the style context changes, make sure that all of our image is up to date.
+//
+/* virtual */ void
+nsImageBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
+
+ // Fetch our subrect.
+ const nsStyleList* myList = StyleList();
+ mSubRect = myList->mImageRegion; // before |mSuppressStyleCheck| test!
+
+ if (mUseSrcAttr || mSuppressStyleCheck)
+ return; // No more work required, since the image isn't specified by style.
+
+ // If we're using a native theme implementation, we shouldn't draw anything.
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (disp->mAppearance && nsBox::gTheme &&
+ nsBox::gTheme->ThemeSupportsWidget(nullptr, this, disp->mAppearance))
+ return;
+
+ // If list-style-image changes, we have a new image.
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ if (mImageRequest)
+ mImageRequest->GetURI(getter_AddRefs(oldURI));
+ if (myList->GetListStyleImage())
+ myList->GetListStyleImage()->GetURI(getter_AddRefs(newURI));
+ bool equal;
+ if (newURI == oldURI || // handles null==null
+ (newURI && oldURI &&
+ NS_SUCCEEDED(newURI->Equals(oldURI, &equal)) && equal))
+ return;
+
+ UpdateImage();
+} // DidSetStyleContext
+
+void
+nsImageBoxFrame::GetImageSize()
+{
+ if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) {
+ mImageSize.width = mIntrinsicSize.width;
+ mImageSize.height = mIntrinsicSize.height;
+ } else {
+ mImageSize.width = 0;
+ mImageSize.height = 0;
+ }
+}
+
+/**
+ * Ok return our dimensions
+ */
+nsSize
+nsImageBoxFrame::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ nsSize size(0,0);
+ DISPLAY_PREF_SIZE(this, size);
+ if (DoesNeedRecalc(mImageSize))
+ GetImageSize();
+
+ if (!mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0))
+ size = mSubRect.Size();
+ else
+ size = mImageSize;
+
+ nsSize intrinsicSize = size;
+
+ nsMargin borderPadding(0,0,0,0);
+ GetXULBorderAndPadding(borderPadding);
+ size.width += borderPadding.LeftRight();
+ size.height += borderPadding.TopBottom();
+
+ bool widthSet, heightSet;
+ nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
+ NS_ASSERTION(size.width != NS_INTRINSICSIZE && size.height != NS_INTRINSICSIZE,
+ "non-intrinsic size expected");
+
+ nsSize minSize = GetXULMinSize(aState);
+ nsSize maxSize = GetXULMaxSize(aState);
+
+ if (!widthSet && !heightSet) {
+ if (minSize.width != NS_INTRINSICSIZE)
+ minSize.width -= borderPadding.LeftRight();
+ if (minSize.height != NS_INTRINSICSIZE)
+ minSize.height -= borderPadding.TopBottom();
+ if (maxSize.width != NS_INTRINSICSIZE)
+ maxSize.width -= borderPadding.LeftRight();
+ if (maxSize.height != NS_INTRINSICSIZE)
+ maxSize.height -= borderPadding.TopBottom();
+
+ size = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(minSize.width, minSize.height,
+ maxSize.width, maxSize.height,
+ intrinsicSize.width, intrinsicSize.height);
+ NS_ASSERTION(size.width != NS_INTRINSICSIZE && size.height != NS_INTRINSICSIZE,
+ "non-intrinsic size expected");
+ size.width += borderPadding.LeftRight();
+ size.height += borderPadding.TopBottom();
+ return size;
+ }
+
+ if (!widthSet) {
+ if (intrinsicSize.height > 0) {
+ // Subtract off the border and padding from the height because the
+ // content-box needs to be used to determine the ratio
+ nscoord height = size.height - borderPadding.TopBottom();
+ size.width = nscoord(int64_t(height) * int64_t(intrinsicSize.width) /
+ int64_t(intrinsicSize.height));
+ }
+ else {
+ size.width = intrinsicSize.width;
+ }
+
+ size.width += borderPadding.LeftRight();
+ }
+ else if (!heightSet) {
+ if (intrinsicSize.width > 0) {
+ nscoord width = size.width - borderPadding.LeftRight();
+ size.height = nscoord(int64_t(width) * int64_t(intrinsicSize.height) /
+ int64_t(intrinsicSize.width));
+ }
+ else {
+ size.height = intrinsicSize.height;
+ }
+
+ size.height += borderPadding.TopBottom();
+ }
+
+ return BoundsCheck(minSize, size, maxSize);
+}
+
+nsSize
+nsImageBoxFrame::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ // An image can always scale down to (0,0).
+ nsSize size(0,0);
+ DISPLAY_MIN_SIZE(this, size);
+ AddBorderAndPadding(size);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(aState, this, size, widthSet, heightSet);
+ return size;
+}
+
+nscoord
+nsImageBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aState)
+{
+ return GetXULPrefSize(aState).height;
+}
+
+nsIAtom*
+nsImageBoxFrame::GetType() const
+{
+ return nsGkAtoms::imageBoxFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsImageBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ImageBox"), aResult);
+}
+#endif
+
+nsresult
+nsImageBoxFrame::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ return OnDecodeComplete(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t imgStatus;
+ aRequest->GetImageStatus(&imgStatus);
+ nsresult status =
+ imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return OnImageIsAnimated(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ return OnFrameUpdate(aRequest);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageBoxFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
+{
+ NS_ENSURE_ARG_POINTER(aImage);
+
+ // Ensure the animation (if any) is started. Note: There is no
+ // corresponding call to Decrement for this. This Increment will be
+ // 'cleaned up' by the Request when it is destroyed, but only then.
+ aRequest->IncrementAnimationConsumers();
+
+ nscoord w, h;
+ aImage->GetWidth(&w);
+ aImage->GetHeight(&h);
+
+ mIntrinsicSize.SizeTo(nsPresContext::CSSPixelsToAppUnits(w),
+ nsPresContext::CSSPixelsToAppUnits(h));
+
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageBoxFrame::OnDecodeComplete(imgIRequest* aRequest)
+{
+ nsBoxLayoutState state(PresContext());
+ this->XULRedraw(state);
+ return NS_OK;
+}
+
+nsresult
+nsImageBoxFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus)) {
+ // Fire an onload DOM event.
+ FireImageDOMEvent(mContent, eLoad);
+ } else {
+ // Fire an onerror DOM event.
+ mIntrinsicSize.SizeTo(0, 0);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ FireImageDOMEvent(mContent, eLoadError);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageBoxFrame::OnImageIsAnimated(imgIRequest* aRequest)
+{
+ // Register with our refresh driver, if we're animated.
+ nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest,
+ &mRequestRegistered);
+
+ return NS_OK;
+}
+
+nsresult
+nsImageBoxFrame::OnFrameUpdate(imgIRequest* aRequest)
+{
+ if ((0 == mRect.width) || (0 == mRect.height)) {
+ return NS_OK;
+ }
+
+ InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsImageBoxListener, imgINotificationObserver, imgIOnloadBlocker)
+
+nsImageBoxListener::nsImageBoxListener()
+{
+}
+
+nsImageBoxListener::~nsImageBoxListener()
+{
+}
+
+NS_IMETHODIMP
+nsImageBoxListener::Notify(imgIRequest *request, int32_t aType, const nsIntRect* aData)
+{
+ if (!mFrame)
+ return NS_OK;
+
+ return mFrame->Notify(request, aType, aData);
+}
+
+NS_IMETHODIMP
+nsImageBoxListener::BlockOnload(imgIRequest *aRequest)
+{
+ if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetUncomposedDoc()) {
+ mFrame->GetContent()->GetUncomposedDoc()->BlockOnload();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageBoxListener::UnblockOnload(imgIRequest *aRequest)
+{
+ if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetUncomposedDoc()) {
+ mFrame->GetContent()->GetUncomposedDoc()->UnblockOnload(false);
+ }
+
+ return NS_OK;
+}
diff --git a/layout/xul/nsImageBoxFrame.h b/layout/xul/nsImageBoxFrame.h
new file mode 100644
index 000000000..7faccccae
--- /dev/null
+++ b/layout/xul/nsImageBoxFrame.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsImageBoxFrame_h___
+#define nsImageBoxFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsLeafBoxFrame.h"
+
+#include "imgILoader.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "imgIOnloadBlocker.h"
+
+class imgRequestProxy;
+class nsImageBoxFrame;
+
+class nsDisplayXULImage;
+
+class nsImageBoxListener final : public imgINotificationObserver,
+ public imgIOnloadBlocker
+{
+public:
+ nsImageBoxListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+ NS_DECL_IMGIONLOADBLOCKER
+
+ void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; }
+
+private:
+ virtual ~nsImageBoxListener();
+
+ nsImageBoxFrame *mFrame;
+};
+
+class nsImageBoxFrame final : public nsLeafBoxFrame
+{
+public:
+ typedef mozilla::image::DrawResult DrawResult;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::LayerManager LayerManager;
+
+ friend class nsDisplayXULImage;
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void MarkIntrinsicISizesDirty() override;
+
+ nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
+
+ friend nsIFrame* NS_NewImageBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* asPrevInFlow) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsIAtom* GetType() const override;
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ /**
+ * Update mUseSrcAttr from appropriate content attributes or from
+ * style, throw away the current image, and load the appropriate
+ * image.
+ * */
+ void UpdateImage();
+
+ /**
+ * Update mLoadFlags from content attributes. Does not attempt to reload the
+ * image using the new load flags.
+ */
+ void UpdateLoadFlags();
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual ~nsImageBoxFrame();
+
+ DrawResult PaintImage(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt, uint32_t aFlags);
+
+ bool CanOptimizeToImageLayer();
+
+protected:
+ explicit nsImageBoxFrame(nsStyleContext* aContext);
+
+ virtual void GetImageSize();
+
+private:
+ nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+ nsresult OnDecodeComplete(imgIRequest* aRequest);
+ nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+ nsresult OnImageIsAnimated(imgIRequest* aRequest);
+ nsresult OnFrameUpdate(imgIRequest* aRequest);
+
+ nsRect mSubRect; ///< If set, indicates that only the portion of the image specified by the rect should be used.
+ nsSize mIntrinsicSize;
+ nsSize mImageSize;
+
+ RefPtr<imgRequestProxy> mImageRequest;
+ nsCOMPtr<imgINotificationObserver> mListener;
+
+ int32_t mLoadFlags;
+
+ // Boolean variable to determine if the current image request has been
+ // registered with the refresh driver.
+ bool mRequestRegistered;
+
+ bool mUseSrcAttr; ///< Whether or not the image src comes from an attribute.
+ bool mSuppressStyleCheck;
+}; // class nsImageBoxFrame
+
+class nsDisplayXULImage : public nsDisplayImageContainer {
+public:
+ nsDisplayXULImage(nsDisplayListBuilder* aBuilder,
+ nsImageBoxFrame* aFrame) :
+ nsDisplayImageContainer(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULImage);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULImage() {
+ MOZ_COUNT_DTOR(nsDisplayXULImage);
+ }
+#endif
+
+ virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) override;
+ virtual already_AddRefed<imgIContainer> GetImage() override;
+ virtual nsRect GetDestRect() override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ *aSnap = true;
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+ // Doesn't handle HitTest because nsLeafBoxFrame already creates an
+ // event receiver for us
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("XULImage", TYPE_XUL_IMAGE)
+};
+
+#endif /* nsImageBoxFrame_h___ */
diff --git a/layout/xul/nsLeafBoxFrame.cpp b/layout/xul/nsLeafBoxFrame.cpp
new file mode 100644
index 000000000..6d1783c11
--- /dev/null
+++ b/layout/xul/nsLeafBoxFrame.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsLeafBoxFrame.h"
+#include "nsBoxFrame.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsWidgetsCID.h"
+#include "nsViewManager.h"
+#include "nsContainerFrame.h"
+#include "nsDisplayList.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// NS_NewLeafBoxFrame
+//
+// Creates a new Toolbar frame and returns it
+//
+nsIFrame*
+NS_NewLeafBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsLeafBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsLeafBoxFrame)
+
+nsLeafBoxFrame::nsLeafBoxFrame(nsStyleContext* aContext)
+ : nsLeafFrame(aContext)
+{
+}
+
+#ifdef DEBUG_LAYOUT
+void
+nsLeafBoxFrame::GetBoxName(nsAutoString& aName)
+{
+ GetFrameName(aName);
+}
+#endif
+
+
+/**
+ * Initialize us. This is a good time to get the alignment of the box
+ */
+void
+nsLeafBoxFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsLeafFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ UpdateMouseThrough();
+}
+
+nsresult
+nsLeafBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsLeafFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ if (aAttribute == nsGkAtoms::mousethrough)
+ UpdateMouseThrough();
+
+ return rv;
+}
+
+void nsLeafBoxFrame::UpdateMouseThrough()
+{
+ if (mContent) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::never, &nsGkAtoms::always, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::mousethrough,
+ strings, eCaseMatters)) {
+ case 0: AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); break;
+ case 1: AddStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); break;
+ case 2: {
+ RemoveStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS);
+ RemoveStateBits(NS_FRAME_MOUSE_THROUGH_NEVER);
+ break;
+ }
+ }
+ }
+}
+
+void
+nsLeafBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // REVIEW: GetFrameForPoint used to not report events for the background
+ // layer, whereas this code will put an event receiver for this frame in the
+ // BlockBorderBackground() list. But I don't see any need to preserve
+ // that anomalous behaviour. The important thing I'm preserving is that
+ // leaf boxes continue to receive events in the foreground layer.
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (!aBuilder->IsForEventDelivery() || !IsVisibleForPainting(aBuilder))
+ return;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayEventReceiver(aBuilder, this));
+}
+
+/* virtual */ nscoord
+nsLeafBoxFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+
+ WritingMode wm = GetWritingMode();
+ LogicalSize minSize(wm, GetXULMinSize(state));
+
+ // GetXULMinSize returns border-box size, and we want to return content
+ // inline-size. Since Reflow uses the reflow state's border and padding, we
+ // actually just want to subtract what GetXULMinSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = minSize.ISize(wm) - LogicalMargin(wm, bp).IStartEnd(wm);
+
+ return result;
+}
+
+/* virtual */ nscoord
+nsLeafBoxFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+
+ WritingMode wm = GetWritingMode();
+ LogicalSize prefSize(wm, GetXULPrefSize(state));
+
+ // GetXULPrefSize returns border-box size, and we want to return content
+ // inline-size. Since Reflow uses the reflow state's border and padding, we
+ // actually just want to subtract what GetXULPrefSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = prefSize.ISize(wm) - LogicalMargin(wm, bp).IStartEnd(wm);
+
+ return result;
+}
+
+nscoord
+nsLeafBoxFrame::GetIntrinsicISize()
+{
+ // No intrinsic width
+ return 0;
+}
+
+LogicalSize
+nsLeafBoxFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ // Important: NOT calling our direct superclass here!
+ return nsFrame::ComputeAutoSize(aRenderingContext, aWM,
+ aCBSize, aAvailableISize,
+ aMargin, aBorder, aPadding, aFlags);
+}
+
+void
+nsLeafBoxFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ // This is mostly a copy of nsBoxFrame::Reflow().
+ // We aren't able to share an implementation because of the frame
+ // class hierarchy. If you make changes here, please keep
+ // nsBoxFrame::Reflow in sync.
+
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsLeafBoxFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(aReflowInput.ComputedWidth() >=0 &&
+ aReflowInput.ComputedHeight() >= 0, "Computed Size < 0");
+
+#ifdef DO_NOISY_REFLOW
+ printf("\n-------------Starting LeafBoxFrame Reflow ----------------------------\n");
+ printf("%p ** nsLBF::Reflow %d R: ", this, myCounter++);
+ switch (aReflowInput.reason) {
+ case eReflowReason_Initial:
+ printf("Ini");break;
+ case eReflowReason_Incremental:
+ printf("Inc");break;
+ case eReflowReason_Resize:
+ printf("Rsz");break;
+ case eReflowReason_StyleChange:
+ printf("Sty");break;
+ case eReflowReason_Dirty:
+ printf("Drt ");
+ break;
+ default:printf("<unknown>%d", aReflowInput.reason);break;
+ }
+
+ printSize("AW", aReflowInput.AvailableWidth());
+ printSize("AH", aReflowInput.AvailableHeight());
+ printSize("CW", aReflowInput.ComputedWidth());
+ printSize("CH", aReflowInput.ComputedHeight());
+
+ printf(" *\n");
+
+#endif
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ // create the layout state
+ nsBoxLayoutState state(aPresContext, aReflowInput.mRenderingContext);
+
+ nsSize computedSize(aReflowInput.ComputedWidth(),aReflowInput.ComputedHeight());
+
+ nsMargin m;
+ m = aReflowInput.ComputedPhysicalBorderPadding();
+
+ //GetXULBorderAndPadding(m);
+
+ // this happens sometimes. So lets handle it gracefully.
+ if (aReflowInput.ComputedHeight() == 0) {
+ nsSize minSize = GetXULMinSize(state);
+ computedSize.height = minSize.height - m.top - m.bottom;
+ }
+
+ nsSize prefSize(0,0);
+
+ // if we are told to layout intrinic then get our preferred size.
+ if (computedSize.width == NS_INTRINSICSIZE || computedSize.height == NS_INTRINSICSIZE) {
+ prefSize = GetXULPrefSize(state);
+ nsSize minSize = GetXULMinSize(state);
+ nsSize maxSize = GetXULMaxSize(state);
+ prefSize = BoundsCheck(minSize, prefSize, maxSize);
+ }
+
+ // get our desiredSize
+ if (aReflowInput.ComputedWidth() == NS_INTRINSICSIZE) {
+ computedSize.width = prefSize.width;
+ } else {
+ computedSize.width += m.left + m.right;
+ }
+
+ if (aReflowInput.ComputedHeight() == NS_INTRINSICSIZE) {
+ computedSize.height = prefSize.height;
+ } else {
+ computedSize.height += m.top + m.bottom;
+ }
+
+ // handle reflow state min and max sizes
+ // XXXbz the width handling here seems to be wrong, since
+ // mComputedMin/MaxWidth is a content-box size, whole
+ // computedSize.width is a border-box size...
+ if (computedSize.width > aReflowInput.ComputedMaxWidth())
+ computedSize.width = aReflowInput.ComputedMaxWidth();
+
+ if (computedSize.width < aReflowInput.ComputedMinWidth())
+ computedSize.width = aReflowInput.ComputedMinWidth();
+
+ // Now adjust computedSize.height for our min and max computed
+ // height. The only problem is that those are content-box sizes,
+ // while computedSize.height is a border-box size. So subtract off
+ // m.TopBottom() before adjusting, then readd it.
+ computedSize.height = std::max(0, computedSize.height - m.TopBottom());
+ computedSize.height = NS_CSS_MINMAX(computedSize.height,
+ aReflowInput.ComputedMinHeight(),
+ aReflowInput.ComputedMaxHeight());
+ computedSize.height += m.TopBottom();
+
+ nsRect r(mRect.x, mRect.y, computedSize.width, computedSize.height);
+
+ SetXULBounds(state, r);
+
+ // layout our children
+ XULLayout(state);
+
+ // ok our child could have gotten bigger. So lets get its bounds
+ aDesiredSize.Width() = mRect.width;
+ aDesiredSize.Height() = mRect.height;
+ aDesiredSize.SetBlockStartAscent(GetXULBoxAscent(state));
+
+ // the overflow rect is set in SetXULBounds() above
+ aDesiredSize.mOverflowAreas = GetOverflowAreas();
+
+#ifdef DO_NOISY_REFLOW
+ {
+ printf("%p ** nsLBF(done) W:%d H:%d ", this, aDesiredSize.Width(), aDesiredSize.Height());
+
+ if (maxElementWidth) {
+ printf("MW:%d\n", *maxElementWidth);
+ } else {
+ printf("MW:?\n");
+ }
+
+ }
+#endif
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsLeafBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("LeafBox"), aResult);
+}
+#endif
+
+nsIAtom*
+nsLeafBoxFrame::GetType() const
+{
+ return nsGkAtoms::leafBoxFrame;
+}
+
+nsresult
+nsLeafBoxFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
+{
+ MarkIntrinsicISizesDirty();
+ return nsLeafFrame::CharacterDataChanged(aInfo);
+}
+
+/* virtual */ nsSize
+nsLeafBoxFrame::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ return nsBox::GetXULPrefSize(aState);
+}
+
+/* virtual */ nsSize
+nsLeafBoxFrame::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ return nsBox::GetXULMinSize(aState);
+}
+
+/* virtual */ nsSize
+nsLeafBoxFrame::GetXULMaxSize(nsBoxLayoutState& aState)
+{
+ return nsBox::GetXULMaxSize(aState);
+}
+
+/* virtual */ nscoord
+nsLeafBoxFrame::GetXULFlex()
+{
+ return nsBox::GetXULFlex();
+}
+
+/* virtual */ nscoord
+nsLeafBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aState)
+{
+ return nsBox::GetXULBoxAscent(aState);
+}
+
+/* virtual */ void
+nsLeafBoxFrame::MarkIntrinsicISizesDirty()
+{
+ // Don't call base class method, since everything it does is within an
+ // IsXULBoxWrapped check.
+}
+
+NS_IMETHODIMP
+nsLeafBoxFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ return nsBox::DoXULLayout(aState);
+}
diff --git a/layout/xul/nsLeafBoxFrame.h b/layout/xul/nsLeafBoxFrame.h
new file mode 100644
index 000000000..8aea598c8
--- /dev/null
+++ b/layout/xul/nsLeafBoxFrame.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsLeafBoxFrame_h___
+#define nsLeafBoxFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsLeafFrame.h"
+#include "nsBox.h"
+
+class nsLeafBoxFrame : public nsLeafFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewLeafBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aState) override;
+ virtual nsSize GetXULMaxSize(nsBoxLayoutState& aState) override;
+ virtual nscoord GetXULFlex() override;
+ virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aState) override;
+
+ virtual nsIAtom* GetType() const override;
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // This is bogus, but it's what we've always done.
+ // Note that nsLeafFrame is also eReplacedContainsBlock.
+ return nsLeafFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock | nsIFrame::eXULBox));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // nsIHTMLReflow overrides
+
+ virtual void MarkIntrinsicISizesDirty() override;
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ // Our auto size is that provided by nsFrame, not nsLeafFrame
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult CharacterDataChanged(CharacterDataChangeInfo* aInfo) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* asPrevInFlow) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool ComputesOwnOverflowArea() override { return false; }
+
+protected:
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aState) override;
+
+#ifdef DEBUG_LAYOUT
+ virtual void GetBoxName(nsAutoString& aName) override;
+#endif
+
+ virtual nscoord GetIntrinsicISize() override;
+
+ explicit nsLeafBoxFrame(nsStyleContext* aContext);
+
+private:
+
+ void UpdateMouseThrough();
+
+
+}; // class nsLeafBoxFrame
+
+#endif /* nsLeafBoxFrame_h___ */
diff --git a/layout/xul/nsListBoxBodyFrame.cpp b/layout/xul/nsListBoxBodyFrame.cpp
new file mode 100644
index 000000000..8c4a5e2fd
--- /dev/null
+++ b/layout/xul/nsListBoxBodyFrame.cpp
@@ -0,0 +1,1535 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsListBoxBodyFrame.h"
+
+#include "nsListBoxLayout.h"
+
+#include "mozilla/MathAlgorithms.h"
+#include "nsCOMPtr.h"
+#include "nsGridRowGroupLayout.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsIScrollableFrame.h"
+#include "nsScrollbarFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsStyleContext.h"
+#include "nsFontMetrics.h"
+#include "nsITimer.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsPIBoxObject.h"
+#include "nsLayoutUtils.h"
+#include "nsPIListBoxObject.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+#include "nsRenderingContext.h"
+#include "prtime.h"
+#include <algorithm>
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/////////////// nsListScrollSmoother //////////////////
+
+/* A mediator used to smooth out scrolling. It works by seeing if
+ * we have time to scroll the amount of rows requested. This is determined
+ * by measuring how long it takes to scroll a row. If we can scroll the
+ * rows in time we do so. If not we start a timer and skip the request. We
+ * do this until the timer finally first because the user has stopped moving
+ * the mouse. Then do all the queued requests in on shot.
+ */
+
+// the longest amount of time that can go by before the use
+// notices it as a delay.
+#define USER_TIME_THRESHOLD 150000
+
+// how long it takes to layout a single row initial value.
+// we will time this after we scroll a few rows.
+#define TIME_PER_ROW_INITAL 50000
+
+// if we decide we can't layout the rows in the amount of time. How long
+// do we wait before checking again?
+#define SMOOTH_INTERVAL 100
+
+class nsListScrollSmoother final : public nsITimerCallback
+{
+private:
+ virtual ~nsListScrollSmoother();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit nsListScrollSmoother(nsListBoxBodyFrame* aOuter);
+
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ void Start();
+ void Stop();
+ bool IsRunning();
+
+ nsCOMPtr<nsITimer> mRepeatTimer;
+ int32_t mDelta;
+ nsListBoxBodyFrame* mOuter;
+};
+
+nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter)
+{
+ mDelta = 0;
+ mOuter = aOuter;
+}
+
+nsListScrollSmoother::~nsListScrollSmoother()
+{
+ Stop();
+}
+
+NS_IMETHODIMP
+nsListScrollSmoother::Notify(nsITimer *timer)
+{
+ Stop();
+
+ NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
+ if (!mOuter) return NS_OK;
+
+ // actually do some work.
+ mOuter->InternalPositionChangedCallback();
+ return NS_OK;
+}
+
+bool
+nsListScrollSmoother::IsRunning()
+{
+ return mRepeatTimer ? true : false;
+}
+
+void
+nsListScrollSmoother::Start()
+{
+ Stop();
+ mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+nsListScrollSmoother::Stop()
+{
+ if ( mRepeatTimer ) {
+ mRepeatTimer->Cancel();
+ mRepeatTimer = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsListScrollSmoother, nsITimerCallback)
+
+/////////////// nsListBoxBodyFrame //////////////////
+
+nsListBoxBodyFrame::nsListBoxBodyFrame(nsStyleContext* aContext,
+ nsBoxLayout* aLayoutManager)
+ : nsBoxFrame(aContext, false, aLayoutManager),
+ mTopFrame(nullptr),
+ mBottomFrame(nullptr),
+ mLinkupFrame(nullptr),
+ mScrollSmoother(nullptr),
+ mRowsToPrepend(0),
+ mRowCount(-1),
+ mRowHeight(0),
+ mAvailableHeight(0),
+ mStringWidth(-1),
+ mCurrentIndex(0),
+ mOldIndex(0),
+ mYPosition(0),
+ mTimePerRow(TIME_PER_ROW_INITAL),
+ mRowHeightWasSet(false),
+ mScrolling(false),
+ mAdjustScroll(false),
+ mReflowCallbackPosted(false)
+{
+}
+
+nsListBoxBodyFrame::~nsListBoxBodyFrame()
+{
+ NS_IF_RELEASE(mScrollSmoother);
+
+#if USE_TIMER_TO_DELAY_SCROLLING
+ StopScrollTracking();
+ mAutoScrollTimer = nullptr;
+#endif
+
+}
+
+NS_QUERYFRAME_HEAD(nsListBoxBodyFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+////////// nsIFrame /////////////////
+
+void
+nsListBoxBodyFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+ // Don't call nsLayoutUtils::GetScrollableFrameFor since we are not its
+ // scrollframe child yet.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(aParent);
+ if (scrollFrame) {
+ nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true);
+ nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar);
+ if (scrollbarFrame) {
+ scrollbarFrame->SetScrollbarMediatorContent(GetContent());
+ }
+ }
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ mRowHeight = fm->MaxHeight();
+}
+
+void
+nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // make sure we cancel any posted callbacks.
+ if (mReflowCallbackPosted)
+ PresContext()->PresShell()->CancelReflowCallback(this);
+
+ // Revoke any pending position changed events
+ for (uint32_t i = 0; i < mPendingPositionChangeEvents.Length(); ++i) {
+ mPendingPositionChangeEvents[i]->Revoke();
+ }
+
+ // Make sure we tell our listbox's box object we're being destroyed.
+ if (mBoxObject) {
+ mBoxObject->ClearCachedValues();
+ }
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = NS_OK;
+
+ if (aAttribute == nsGkAtoms::rows) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ else
+ rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+
+ return rv;
+
+}
+
+/* virtual */ void
+nsListBoxBodyFrame::MarkIntrinsicISizesDirty()
+{
+ mStringWidth = -1;
+ nsBoxFrame::MarkIntrinsicISizesDirty();
+}
+
+/////////// nsBox ///////////////
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mScrolling)
+ aBoxLayoutState.SetPaintingDisabled(true);
+
+ nsresult rv = nsBoxFrame::DoXULLayout(aBoxLayoutState);
+
+ // determine the real height for the scrollable area from the total number
+ // of rows, since non-visible rows don't yet have frames
+ nsRect rect(nsPoint(0, 0), GetSize());
+ nsOverflowAreas overflow(rect, rect);
+ if (mLayoutManager) {
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ ConsiderChildOverflow(overflow, childFrame);
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ nsSize prefSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState);
+ NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
+ nsRect& o = overflow.Overflow(otype);
+ o.height = std::max(o.height, prefSize.height);
+ }
+ }
+ FinishAndStoreOverflow(overflow, GetSize());
+
+ if (mScrolling)
+ aBoxLayoutState.SetPaintingDisabled(false);
+
+ // if we are scrolled and the row height changed
+ // make sure we are scrolled to a correct index.
+ if (mAdjustScroll)
+ PostReflowCallback();
+
+ return rv;
+}
+
+nsSize
+nsListBoxBodyFrame::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize result(0, 0);
+ if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None,
+ nsGkAtoms::sizemode)) {
+ result = GetXULPrefSize(aBoxLayoutState);
+ result.height = 0;
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame &&
+ scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
+ nsMargin scrollbars =
+ scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
+ result.width += scrollbars.left + scrollbars.right;
+ }
+ }
+ return result;
+}
+
+nsSize
+nsListBoxBodyFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize pref = nsBoxFrame::GetXULPrefSize(aBoxLayoutState);
+
+ int32_t size = GetFixedRowSize();
+ if (size > -1)
+ pref.height = size*GetRowHeightAppUnits();
+
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame &&
+ scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
+ nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
+ pref.width += scrollbars.left + scrollbars.right;
+ }
+ return pref;
+}
+
+///////////// nsIScrollbarMediator ///////////////
+
+void
+nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToPage(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToWhole(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToLine(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
+{
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+int32_t
+nsListBoxBodyFrame::ToRowIndex(nscoord aPos) const
+{
+ return NS_roundf(float(std::max(aPos, 0)) / mRowHeight);
+}
+
+void
+nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos)
+{
+ if (mScrolling || mRowHeight == 0)
+ return;
+
+ int32_t newIndex = ToRowIndex(aNewPos);
+ if (newIndex == mCurrentIndex) {
+ return;
+ }
+ int32_t rowDelta = newIndex - mCurrentIndex;
+
+ nsListScrollSmoother* smoother = GetSmoother();
+
+ // if we can't scroll the rows in time then start a timer. We will eat
+ // events until the user stops moving and the timer stops.
+ if (smoother->IsRunning() || Abs(rowDelta)*mTimePerRow > USER_TIME_THRESHOLD) {
+
+ smoother->Stop();
+
+ smoother->mDelta = rowDelta;
+
+ smoother->Start();
+
+ return;
+ }
+
+ smoother->Stop();
+
+ mCurrentIndex = newIndex;
+ smoother->mDelta = 0;
+
+ if (mCurrentIndex < 0) {
+ mCurrentIndex = 0;
+ return;
+ }
+ InternalPositionChanged(rowDelta < 0, Abs(rowDelta));
+}
+
+void
+nsListBoxBodyFrame::VisibilityChanged(bool aVisible)
+{
+ if (mRowHeight == 0)
+ return;
+
+ int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
+ if (lastPageTopRow < 0)
+ lastPageTopRow = 0;
+ int32_t delta = mCurrentIndex - lastPageTopRow;
+ if (delta > 0) {
+ mCurrentIndex = lastPageTopRow;
+ InternalPositionChanged(true, delta);
+ }
+}
+
+nsIFrame*
+nsListBoxBodyFrame::GetScrollbarBox(bool aVertical)
+{
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ return scrollFrame ? scrollFrame->GetScrollbarBox(true) : nullptr;
+}
+
+void
+nsListBoxBodyFrame::UpdateIndex(int32_t aNewPos)
+{
+ int32_t newIndex = ToRowIndex(nsPresContext::CSSPixelsToAppUnits(aNewPos));
+ if (newIndex == mCurrentIndex) {
+ return;
+ }
+ bool up = newIndex < mCurrentIndex;
+ int32_t indexDelta = Abs(newIndex - mCurrentIndex);
+ mCurrentIndex = newIndex;
+ InternalPositionChanged(up, indexDelta);
+}
+
+///////////// nsIReflowCallback ///////////////
+
+bool
+nsListBoxBodyFrame::ReflowFinished()
+{
+ nsAutoScriptBlocker scriptBlocker;
+ // now create or destroy any rows as needed
+ CreateRows();
+
+ // keep scrollbar in sync
+ if (mAdjustScroll) {
+ VerticalScroll(mYPosition);
+ mAdjustScroll = false;
+ }
+
+ // if the row height changed then mark everything as a style change.
+ // That will dirty the entire listbox
+ if (mRowHeightWasSet) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ int32_t pos = mCurrentIndex * mRowHeight;
+ if (mYPosition != pos)
+ mAdjustScroll = true;
+ mRowHeightWasSet = false;
+ }
+
+ mReflowCallbackPosted = false;
+ return true;
+}
+
+void
+nsListBoxBodyFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackPosted = false;
+}
+
+///////// ListBoxObject ///////////////
+
+int32_t
+nsListBoxBodyFrame::GetNumberOfVisibleRows()
+{
+ return mRowHeight ? GetAvailableHeight() / mRowHeight : 0;
+}
+
+int32_t
+nsListBoxBodyFrame::GetIndexOfFirstVisibleRow()
+{
+ return mCurrentIndex;
+}
+
+nsresult
+nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex)
+{
+ if (aRowIndex < 0)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ int32_t rows = 0;
+ if (mRowHeight)
+ rows = GetAvailableHeight()/mRowHeight;
+ if (rows <= 0)
+ rows = 1;
+ int32_t bottomIndex = mCurrentIndex + rows;
+
+ // if row is visible, ignore
+ if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
+ return NS_OK;
+
+ int32_t delta;
+
+ bool up = aRowIndex < mCurrentIndex;
+ if (up) {
+ delta = mCurrentIndex - aRowIndex;
+ mCurrentIndex = aRowIndex;
+ }
+ else {
+ // Check to be sure we're not scrolling off the bottom of the tree
+ if (aRowIndex >= GetRowCount())
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ // Bring it just into view.
+ delta = 1 + (aRowIndex-bottomIndex);
+ mCurrentIndex += delta;
+ }
+
+ // Safe to not go off an event here, since this is coming from the
+ // box object.
+ DoInternalPositionChangedSync(up, delta);
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines)
+{
+ int32_t scrollIndex = GetIndexOfFirstVisibleRow(),
+ visibleRows = GetNumberOfVisibleRows();
+
+ scrollIndex += aNumLines;
+
+ if (scrollIndex < 0)
+ scrollIndex = 0;
+ else {
+ int32_t numRows = GetRowCount();
+ int32_t lastPageTopRow = numRows - visibleRows;
+ if (scrollIndex > lastPageTopRow)
+ scrollIndex = lastPageTopRow;
+ }
+
+ ScrollToIndex(scrollIndex);
+
+ return NS_OK;
+}
+
+// walks the DOM to get the zero-based row index of the content
+nsresult
+nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval)
+{
+ if (aItem) {
+ *_retval = 0;
+ nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem));
+
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ // we hit a list row, count it
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ // is this it?
+ if (child == itemContent)
+ return NS_OK;
+
+ ++(*_retval);
+ }
+ }
+ }
+
+ // not found
+ *_retval = -1;
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex, nsIDOMElement** aItem)
+{
+ *aItem = nullptr;
+ if (aIndex < 0)
+ return NS_OK;
+
+ int32_t itemCount = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ // we hit a list row, check if it is the one we are looking for
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ // is this it?
+ if (itemCount == aIndex) {
+ return CallQueryInterface(child, aItem);
+ }
+ ++itemCount;
+ }
+ }
+
+ // not found
+ return NS_OK;
+}
+
+/////////// nsListBoxBodyFrame ///////////////
+
+int32_t
+nsListBoxBodyFrame::GetRowCount()
+{
+ if (mRowCount < 0)
+ ComputeTotalRowCount();
+ return mRowCount;
+}
+
+int32_t
+nsListBoxBodyFrame::GetFixedRowSize()
+{
+ nsresult dummy;
+
+ nsAutoString rows;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
+ if (!rows.IsEmpty())
+ return rows.ToInteger(&dummy);
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows);
+
+ if (!rows.IsEmpty())
+ return rows.ToInteger(&dummy);
+
+ return -1;
+}
+
+void
+nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight)
+{
+ if (aRowHeight > mRowHeight) {
+ mRowHeight = aRowHeight;
+
+ // signal we need to dirty everything
+ // and we want to be notified after reflow
+ // so we can create or destory rows as needed
+ mRowHeightWasSet = true;
+ PostReflowCallback();
+ }
+}
+
+nscoord
+nsListBoxBodyFrame::GetAvailableHeight()
+{
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame) {
+ return scrollFrame->GetScrollPortRect().height;
+ }
+ return 0;
+}
+
+nscoord
+nsListBoxBodyFrame::GetYPosition()
+{
+ return mYPosition;
+}
+
+nscoord
+nsListBoxBodyFrame::ComputeIntrinsicISize(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mStringWidth != -1)
+ return mStringWidth;
+
+ nscoord largestWidth = 0;
+
+ int32_t index = 0;
+ nsCOMPtr<nsIDOMElement> firstRowEl;
+ GetItemAtIndex(index, getter_AddRefs(firstRowEl));
+ nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
+
+ if (firstRowContent) {
+ RefPtr<nsStyleContext> styleContext;
+ nsPresContext *presContext = aBoxLayoutState.PresContext();
+ styleContext = presContext->StyleSet()->
+ ResolveStyleFor(firstRowContent->AsElement(), nullptr);
+
+ nscoord width = 0;
+ nsMargin margin(0,0,0,0);
+
+ if (styleContext->StylePadding()->GetPadding(margin))
+ width += margin.LeftRight();
+ width += styleContext->StyleBorder()->GetComputedBorder().LeftRight();
+ if (styleContext->StyleMargin()->GetMargin(margin))
+ width += margin.LeftRight();
+
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
+ if (rendContext) {
+ nsAutoString value;
+ uint32_t textCount = child->GetChildCount();
+ for (uint32_t j = 0; j < textCount; ++j) {
+ nsIContent* text = child->GetChildAt(j);
+ if (text && text->IsNodeOfType(nsINode::eTEXT)) {
+ text->AppendTextTo(value);
+ }
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(styleContext);
+
+ nscoord textWidth =
+ nsLayoutUtils::AppUnitWidthOfStringBidi(value, this, *fm,
+ *rendContext);
+ textWidth += width;
+
+ if (textWidth > largestWidth)
+ largestWidth = textWidth;
+ }
+ }
+ }
+ }
+
+ mStringWidth = largestWidth;
+ return mStringWidth;
+}
+
+void
+nsListBoxBodyFrame::ComputeTotalRowCount()
+{
+ mRowCount = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++mRowCount;
+ }
+ }
+}
+
+void
+nsListBoxBodyFrame::PostReflowCallback()
+{
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+}
+
+////////// scrolling
+
+nsresult
+nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex)
+{
+ if (( aRowIndex < 0 ) || (mRowHeight == 0))
+ return NS_OK;
+
+ int32_t newIndex = aRowIndex;
+ int32_t delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
+ bool up = newIndex < mCurrentIndex;
+
+ // Check to be sure we're not scrolling off the bottom of the tree
+ int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
+ if (lastPageTopRow < 0)
+ lastPageTopRow = 0;
+
+ if (aRowIndex > lastPageTopRow)
+ return NS_OK;
+
+ mCurrentIndex = newIndex;
+
+ nsWeakFrame weak(this);
+
+ // Since we're going to flush anyway, we need to not do this off an event
+ DoInternalPositionChangedSync(up, delta);
+
+ if (!weak.IsAlive()) {
+ return NS_OK;
+ }
+
+ // This change has to happen immediately.
+ // Flush any pending reflow commands.
+ // XXXbz why, exactly?
+ mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
+
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::InternalPositionChangedCallback()
+{
+ nsListScrollSmoother* smoother = GetSmoother();
+
+ if (smoother->mDelta == 0)
+ return NS_OK;
+
+ mCurrentIndex += smoother->mDelta;
+
+ if (mCurrentIndex < 0)
+ mCurrentIndex = 0;
+
+ return DoInternalPositionChangedSync(smoother->mDelta < 0,
+ smoother->mDelta < 0 ?
+ -smoother->mDelta : smoother->mDelta);
+}
+
+nsresult
+nsListBoxBodyFrame::InternalPositionChanged(bool aUp, int32_t aDelta)
+{
+ RefPtr<nsPositionChangedEvent> ev =
+ new nsPositionChangedEvent(this, aUp, aDelta);
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mPendingPositionChangeEvents.AppendElement(ev)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ ev->Revoke();
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, int32_t aDelta)
+{
+ nsWeakFrame weak(this);
+
+ // Process all the pending position changes first
+ nsTArray< RefPtr<nsPositionChangedEvent> > temp;
+ temp.SwapElements(mPendingPositionChangeEvents);
+ for (uint32_t i = 0; i < temp.Length(); ++i) {
+ if (weak.IsAlive()) {
+ temp[i]->Run();
+ }
+ temp[i]->Revoke();
+ }
+
+ if (!weak.IsAlive()) {
+ return NS_OK;
+ }
+
+ return DoInternalPositionChanged(aUp, aDelta);
+}
+
+nsresult
+nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, int32_t aDelta)
+{
+ if (aDelta == 0)
+ return NS_OK;
+
+ RefPtr<nsPresContext> presContext(PresContext());
+ nsBoxLayoutState state(presContext);
+
+ // begin timing how long it takes to scroll a row
+ PRTime start = PR_Now();
+
+ nsWeakFrame weakThis(this);
+ mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ int32_t visibleRows = 0;
+ if (mRowHeight)
+ visibleRows = GetAvailableHeight()/mRowHeight;
+
+ if (aDelta < visibleRows) {
+ int32_t loseRows = aDelta;
+ if (aUp) {
+ // scrolling up, destroy rows from the bottom downwards
+ ReverseDestroyRows(loseRows);
+ mRowsToPrepend += aDelta;
+ mLinkupFrame = nullptr;
+ }
+ else {
+ // scrolling down, destroy rows from the top upwards
+ DestroyRows(loseRows);
+ mRowsToPrepend = 0;
+ }
+ }
+ else {
+ // We have scrolled so much that all of our current frames will
+ // go off screen, so blow them all away. Weeee!
+ nsIFrame *currBox = mFrames.FirstChild();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (currBox) {
+ nsIFrame *nextBox = currBox->GetNextSibling();
+ RemoveChildFrame(state, currBox);
+ currBox = nextBox;
+ }
+ fc->EndUpdate();
+ }
+
+ // clear frame markers so that CreateRows will re-create
+ mTopFrame = mBottomFrame = nullptr;
+
+ mYPosition = mCurrentIndex*mRowHeight;
+ mScrolling = true;
+ presContext->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+ // Flush calls CreateRows
+ // XXXbz there has to be a better way to do this than flushing!
+ presContext->PresShell()->FlushPendingNotifications(Flush_Layout);
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+
+ mScrolling = false;
+
+ VerticalScroll(mYPosition);
+
+ PRTime end = PR_Now();
+
+ int32_t newTime = int32_t(end - start) / aDelta;
+
+ // average old and new
+ mTimePerRow = (newTime + mTimePerRow)/2;
+
+ return NS_OK;
+}
+
+nsListScrollSmoother*
+nsListBoxBodyFrame::GetSmoother()
+{
+ if (!mScrollSmoother) {
+ mScrollSmoother = new nsListScrollSmoother(this);
+ NS_ASSERTION(mScrollSmoother, "out of memory");
+ NS_IF_ADDREF(mScrollSmoother);
+ }
+
+ return mScrollSmoother;
+}
+
+void
+nsListBoxBodyFrame::VerticalScroll(int32_t aPosition)
+{
+ nsIScrollableFrame* scrollFrame
+ = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (!scrollFrame) {
+ return;
+ }
+
+ nsPoint scrollPosition = scrollFrame->GetScrollPosition();
+
+ nsWeakFrame weakFrame(this);
+ scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition),
+ nsIScrollableFrame::INSTANT);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ mYPosition = aPosition;
+}
+
+////////// frame and box retrieval
+
+nsIFrame*
+nsListBoxBodyFrame::GetFirstFrame()
+{
+ mTopFrame = mFrames.FirstChild();
+ return mTopFrame;
+}
+
+nsIFrame*
+nsListBoxBodyFrame::GetLastFrame()
+{
+ return mFrames.LastChild();
+}
+
+bool
+nsListBoxBodyFrame::SupportsOrdinalsInChildren()
+{
+ return false;
+}
+
+////////// lazy row creation and destruction
+
+void
+nsListBoxBodyFrame::CreateRows()
+{
+ // Get our client rect.
+ nsRect clientRect;
+ GetXULClientRect(clientRect);
+
+ // Get the starting y position and the remaining available
+ // height.
+ nscoord availableHeight = GetAvailableHeight();
+
+ if (availableHeight <= 0) {
+ bool fixed = (GetFixedRowSize() != -1);
+ if (fixed)
+ availableHeight = 10;
+ else
+ return;
+ }
+
+ // get the first tree box. If there isn't one create one.
+ bool created = false;
+ nsIFrame* box = GetFirstItemBox(0, &created);
+ nscoord rowHeight = GetRowHeightAppUnits();
+ while (box) {
+ if (created && mRowsToPrepend > 0)
+ --mRowsToPrepend;
+
+ // if the row height is 0 then fail. Wait until someone
+ // laid out and sets the row height.
+ if (rowHeight == 0)
+ return;
+
+ availableHeight -= rowHeight;
+
+ // should we continue? Is the enought height?
+ if (!ContinueReflow(availableHeight))
+ break;
+
+ // get the next tree box. Create one if needed.
+ box = GetNextItemBox(box, 0, &created);
+ }
+
+ mRowsToPrepend = 0;
+ mLinkupFrame = nullptr;
+}
+
+void
+nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose)
+{
+ // We need to destroy frames until our row count has been properly
+ // reduced. A reflow will then pick up and create the new frames.
+ nsIFrame* childFrame = GetFirstFrame();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (childFrame && aRowsToLose > 0) {
+ --aRowsToLose;
+
+ nsIFrame* nextFrame = childFrame->GetNextSibling();
+ RemoveChildFrame(state, childFrame);
+
+ mTopFrame = childFrame = nextFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose)
+{
+ // We need to destroy frames until our row count has been properly
+ // reduced. A reflow will then pick up and create the new frames.
+ nsIFrame* childFrame = GetLastFrame();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (childFrame && aRowsToLose > 0) {
+ --aRowsToLose;
+
+ nsIFrame* prevFrame;
+ prevFrame = childFrame->GetPrevSibling();
+ RemoveChildFrame(state, childFrame);
+
+ mBottomFrame = childFrame = prevFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+static bool
+IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild,
+ nsIFrame** aChildFrame)
+{
+ *aChildFrame = nullptr;
+ if (!aChild->IsXULElement(nsGkAtoms::listitem)) {
+ return false;
+ }
+ nsIFrame* existingFrame = aChild->GetPrimaryFrame();
+ if (existingFrame && existingFrame->GetParent() != aParent) {
+ return false;
+ }
+ *aChildFrame = existingFrame;
+ return true;
+}
+
+//
+// Get the nsIFrame for the first visible listitem, and if none exists,
+// create one.
+//
+nsIFrame*
+nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset, bool* aCreated)
+{
+ if (aCreated)
+ *aCreated = false;
+
+ // Clear ourselves out.
+ mBottomFrame = mTopFrame;
+
+ if (mTopFrame) {
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ }
+
+ // top frame was cleared out
+ mTopFrame = GetFirstFrame();
+ mBottomFrame = mTopFrame;
+
+ if (mTopFrame && mRowsToPrepend <= 0) {
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ }
+
+ // At this point, we either have no frames at all,
+ // or the user has scrolled upwards, leaving frames
+ // to be created at the top. Let's determine which
+ // content needs a new frame first.
+
+ nsCOMPtr<nsIContent> startContent;
+ if (mTopFrame && mRowsToPrepend > 0) {
+ // We need to insert rows before the top frame
+ nsIContent* topContent = mTopFrame->GetContent();
+ nsIContent* topParent = topContent->GetParent();
+ int32_t contentIndex = topParent->IndexOf(topContent);
+ contentIndex -= aOffset;
+ if (contentIndex < 0)
+ return nullptr;
+ startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend);
+ } else {
+ // This will be the first item frame we create. Use the content
+ // at the current index, which is the first index scrolled into view
+ GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent));
+ }
+
+ if (startContent) {
+ nsIFrame* existingFrame;
+ if (!IsListItemChild(this, startContent, &existingFrame)) {
+ return GetFirstItemBox(++aOffset, aCreated);
+ }
+ if (existingFrame) {
+ return existingFrame->IsXULBoxFrame() ? existingFrame : nullptr;
+ }
+
+ // Either append the new frame, or prepend it (at index 0)
+ // XXX check here if frame was even created, it may not have been if
+ // display: none was on listitem content
+ bool isAppend = mRowsToPrepend <= 0;
+
+ nsPresContext* presContext = PresContext();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ nsIFrame* topFrame = nullptr;
+ fc->CreateListBoxContent(this, nullptr, startContent, &topFrame, isAppend);
+ mTopFrame = topFrame;
+ if (mTopFrame) {
+ if (aCreated)
+ *aCreated = true;
+
+ mBottomFrame = mTopFrame;
+
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ } else
+ return GetFirstItemBox(++aOffset, 0);
+ }
+
+ return nullptr;
+}
+
+//
+// Get the nsIFrame for the next visible listitem after aBox, and if none
+// exists, create one.
+//
+nsIFrame*
+nsListBoxBodyFrame::GetNextItemBox(nsIFrame* aBox, int32_t aOffset,
+ bool* aCreated)
+{
+ if (aCreated)
+ *aCreated = false;
+
+ nsIFrame* result = aBox->GetNextSibling();
+
+ if (!result || result == mLinkupFrame || mRowsToPrepend > 0) {
+ // No result found. See if there's a content node that wants a frame.
+ nsIContent* prevContent = aBox->GetContent();
+ nsIContent* parentContent = prevContent->GetParent();
+
+ int32_t i = parentContent->IndexOf(prevContent);
+
+ uint32_t childCount = parentContent->GetChildCount();
+ if (((uint32_t)i + aOffset + 1) < childCount) {
+ // There is a content node that wants a frame.
+ nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1);
+
+ nsIFrame* existingFrame;
+ if (!IsListItemChild(this, nextContent, &existingFrame)) {
+ return GetNextItemBox(aBox, ++aOffset, aCreated);
+ }
+ if (!existingFrame) {
+ // Either append the new frame, or insert it after the current frame
+ bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0;
+ nsIFrame* prevFrame = isAppend ? nullptr : aBox;
+
+ nsPresContext* presContext = PresContext();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ fc->CreateListBoxContent(this, prevFrame, nextContent,
+ &result, isAppend);
+
+ if (result) {
+ if (aCreated)
+ *aCreated = true;
+ } else
+ return GetNextItemBox(aBox, ++aOffset, aCreated);
+ } else {
+ result = existingFrame;
+ }
+
+ mLinkupFrame = nullptr;
+ }
+ }
+
+ if (!result)
+ return nullptr;
+
+ mBottomFrame = result;
+
+ NS_ASSERTION(!result->IsXULBoxFrame() || result->GetParent() == this,
+ "returning frame that is not in childlist");
+
+ return result->IsXULBoxFrame() ? result : nullptr;
+}
+
+bool
+nsListBoxBodyFrame::ContinueReflow(nscoord height)
+{
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ // Create all the frames at once so screen readers and
+ // onscreen keyboards can see the full list right away
+ return true;
+ }
+#endif
+
+ if (height <= 0) {
+ nsIFrame* lastChild = GetLastFrame();
+ nsIFrame* startingPoint = mBottomFrame;
+ if (startingPoint == nullptr) {
+ // We just want to delete everything but the first item.
+ startingPoint = GetFirstFrame();
+ }
+
+ if (lastChild != startingPoint) {
+ // We have some hangers on (probably caused by shrinking the size of the window).
+ // Nuke them.
+ nsIFrame* currFrame = startingPoint->GetNextSibling();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc =
+ PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (currFrame) {
+ nsIFrame* nextFrame = currFrame->GetNextSibling();
+ RemoveChildFrame(state, currFrame);
+ currFrame = nextFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ return false;
+ }
+ else
+ return true;
+}
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList)
+{
+ // append them after
+ nsBoxLayoutState state(PresContext());
+ const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nullptr, aFrameList);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenAppended(this, state, newFrames);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // insert the frames to our info list
+ nsBoxLayoutState state(PresContext());
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ return NS_OK;
+}
+
+//
+// Called by nsCSSFrameConstructor when a new listitem content is inserted.
+//
+void
+nsListBoxBodyFrame::OnContentInserted(nsIContent* aChildContent)
+{
+ if (mRowCount >= 0)
+ ++mRowCount;
+
+ // The RDF content builder will build content nodes such that they are all
+ // ready when OnContentInserted is first called, meaning the first call
+ // to CreateRows will create all the frames, but OnContentInserted will
+ // still be called again for each content node - so we need to make sure
+ // that the frame for each content node hasn't already been created.
+ nsIFrame* childFrame = aChildContent->GetPrimaryFrame();
+ if (childFrame)
+ return;
+
+ int32_t siblingIndex;
+ nsCOMPtr<nsIContent> nextSiblingContent;
+ GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex);
+
+ // if we're inserting our item before the first visible content,
+ // then we need to shift all rows down by one
+ if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) {
+ mTopFrame = nullptr;
+ mRowsToPrepend = 1;
+ } else if (nextSiblingContent) {
+ // we may be inserting before a frame that is on screen
+ nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame();
+ mLinkupFrame = nextSiblingFrame;
+ }
+
+ CreateRows();
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+//
+// Called by nsCSSFrameConstructor when listitem content is removed.
+//
+void
+nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext,
+ nsIContent* aContainer,
+ nsIFrame* aChildFrame,
+ nsIContent* aOldNextSibling)
+{
+ NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this,
+ "Removing frame that's not our child... Not good");
+
+ if (mRowCount >= 0)
+ --mRowCount;
+
+ if (aContainer) {
+ if (!aChildFrame) {
+ // The row we are removing is out of view, so we need to try to
+ // determine the index of its next sibling.
+ int32_t siblingIndex = -1;
+ if (aOldNextSibling) {
+ nsCOMPtr<nsIContent> nextSiblingContent;
+ GetListItemNextSibling(aOldNextSibling,
+ getter_AddRefs(nextSiblingContent),
+ siblingIndex);
+ }
+
+ // if the row being removed is off-screen and above the top frame, we need to
+ // adjust our top index and tell the scrollbar to shift up one row.
+ if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) {
+ NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0");
+ --mCurrentIndex;
+ mYPosition = mCurrentIndex*mRowHeight;
+ nsWeakFrame weakChildFrame(aChildFrame);
+ VerticalScroll(mYPosition);
+ if (!weakChildFrame.IsAlive()) {
+ return;
+ }
+ }
+ } else if (mCurrentIndex > 0) {
+ // At this point, we know we have a scrollbar, and we need to know
+ // if we are scrolled to the last row. In this case, the behavior
+ // of the scrollbar is to stay locked to the bottom. Since we are
+ // removing visible content, the first visible row will have to move
+ // down by one, and we will have to insert a new frame at the top.
+
+ // if the last content node has a frame, we are scrolled to the bottom
+ nsIContent* lastChild = nullptr;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ lastChild = child;
+ }
+
+ if (lastChild) {
+ nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame();
+
+ if (lastChildFrame) {
+ mTopFrame = nullptr;
+ mRowsToPrepend = 1;
+ --mCurrentIndex;
+ mYPosition = mCurrentIndex*mRowHeight;
+ nsWeakFrame weakChildFrame(aChildFrame);
+ VerticalScroll(mYPosition);
+ if (!weakChildFrame.IsAlive()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // if we're removing the top row, the new top row is the next row
+ if (mTopFrame && mTopFrame == aChildFrame)
+ mTopFrame = mTopFrame->GetNextSibling();
+
+ // Go ahead and delete the frame.
+ nsBoxLayoutState state(aPresContext);
+ if (aChildFrame) {
+ RemoveChildFrame(state, aChildFrame);
+ }
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex, nsIContent** aContent)
+{
+ *aContent = nullptr;
+
+ int32_t itemsFound = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++itemsFound;
+ if (itemsFound-1 == aIndex) {
+ *aContent = child;
+ NS_IF_ADDREF(*aContent);
+ return;
+ }
+ }
+ }
+}
+
+void
+nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex)
+{
+ *aContent = nullptr;
+ aSiblingIndex = -1;
+ nsIContent *prevKid = nullptr;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++aSiblingIndex;
+ if (prevKid == aListItem) {
+ *aContent = child;
+ NS_IF_ADDREF(*aContent);
+ return;
+ }
+ }
+ prevKid = child;
+ }
+
+ aSiblingIndex = -1; // no match, so there is no next sibling
+}
+
+void
+nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState,
+ nsIFrame *aFrame)
+{
+ MOZ_ASSERT(mFrames.ContainsFrame(aFrame));
+ MOZ_ASSERT(aFrame != GetContentInsertionFrame());
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIContent* content = aFrame->GetContent();
+ accService->ContentRemoved(PresContext()->PresShell(), content);
+ }
+#endif
+
+ mFrames.RemoveFrame(aFrame);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenRemoved(this, aState, aFrame);
+ aFrame->Destroy();
+}
+
+// Creation Routines ///////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsBoxLayout> NS_NewListBoxLayout();
+
+nsIFrame*
+NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout();
+ return new (aPresShell) nsListBoxBodyFrame(aContext, layout);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame)
diff --git a/layout/xul/nsListBoxBodyFrame.h b/layout/xul/nsListBoxBodyFrame.h
new file mode 100644
index 000000000..57bbf0257
--- /dev/null
+++ b/layout/xul/nsListBoxBodyFrame.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsListBoxBodyFrame_h
+#define nsListBoxBodyFrame_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsBoxFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsIReflowCallback.h"
+#include "nsBoxLayoutState.h"
+#include "nsThreadUtils.h"
+#include "nsPIBoxObject.h"
+
+class nsPresContext;
+class nsListScrollSmoother;
+nsIFrame* NS_NewListBoxBodyFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+class nsListBoxBodyFrame final : public nsBoxFrame,
+ public nsIScrollbarMediator,
+ public nsIReflowCallback
+{
+ nsListBoxBodyFrame(nsStyleContext* aContext,
+ nsBoxLayout* aLayoutManager);
+ virtual ~nsListBoxBodyFrame();
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsListBoxBodyFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // non-virtual ListBoxObject
+ int32_t GetNumberOfVisibleRows();
+ int32_t GetIndexOfFirstVisibleRow();
+ nsresult EnsureIndexIsVisible(int32_t aRowIndex);
+ nsresult ScrollToIndex(int32_t aRowIndex);
+ nsresult ScrollByLines(int32_t aNumLines);
+ nsresult GetItemAtIndex(int32_t aIndex, nsIDOMElement **aResult);
+ nsresult GetIndexOfItem(nsIDOMElement *aItem, int32_t *aResult);
+
+ friend nsIFrame* NS_NewListBoxBodyFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // nsIFrame
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) override;
+
+ // nsIScrollbarMediator
+ virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode snapMode
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode snapMode
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode snapMode
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) override;
+ virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+ int32_t aOldPos,
+ int32_t aNewPos) override;
+ virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) override {}
+ virtual void VisibilityChanged(bool aVisible) override;
+ virtual nsIFrame* GetScrollbarBox(bool aVertical) override;
+ virtual void ScrollbarActivityStarted() const override {}
+ virtual void ScrollbarActivityStopped() const override {}
+ virtual bool IsScrollbarOnRight() const override {
+ return (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR);
+ }
+ virtual bool ShouldSuppressScrollbarRepaints() const override {
+ return false;
+ }
+
+
+ // nsIReflowCallback
+ virtual bool ReflowFinished() override;
+ virtual void ReflowCallbackCanceled() override;
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void MarkIntrinsicISizesDirty() override;
+
+ virtual nsSize GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+
+ // size calculation
+ int32_t GetRowCount();
+ int32_t GetRowHeightAppUnits() { return mRowHeight; }
+ int32_t GetFixedRowSize();
+ void SetRowHeight(nscoord aRowHeight);
+ nscoord GetYPosition();
+ nscoord GetAvailableHeight();
+ nscoord ComputeIntrinsicISize(nsBoxLayoutState& aBoxLayoutState);
+
+ // scrolling
+ nsresult InternalPositionChangedCallback();
+ nsresult InternalPositionChanged(bool aUp, int32_t aDelta);
+ // Process pending position changed events, then do the position change.
+ // This can wipe out the frametree.
+ nsresult DoInternalPositionChangedSync(bool aUp, int32_t aDelta);
+ // Actually do the internal position change. This can wipe out the frametree
+ nsresult DoInternalPositionChanged(bool aUp, int32_t aDelta);
+ nsListScrollSmoother* GetSmoother();
+ void VerticalScroll(int32_t aDelta);
+ // Update the scroll index given a position, in CSS pixels
+ void UpdateIndex(int32_t aNewPos);
+
+ // frames
+ nsIFrame* GetFirstFrame();
+ nsIFrame* GetLastFrame();
+
+ // lazy row creation and destruction
+ void CreateRows();
+ void DestroyRows(int32_t& aRowsToLose);
+ void ReverseDestroyRows(int32_t& aRowsToLose);
+ nsIFrame* GetFirstItemBox(int32_t aOffset, bool* aCreated);
+ nsIFrame* GetNextItemBox(nsIFrame* aBox, int32_t aOffset, bool* aCreated);
+ bool ContinueReflow(nscoord height);
+ NS_IMETHOD ListBoxAppendFrames(nsFrameList& aFrameList);
+ NS_IMETHOD ListBoxInsertFrames(nsIFrame* aPrevFrame, nsFrameList& aFrameList);
+ void OnContentInserted(nsIContent* aContent);
+ void OnContentRemoved(nsPresContext* aPresContext, nsIContent* aContainer,
+ nsIFrame* aChildFrame, nsIContent* aOldNextSibling);
+
+ void GetListItemContentAt(int32_t aIndex, nsIContent** aContent);
+ void GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex);
+
+ void PostReflowCallback();
+
+ bool SetBoxObject(nsPIBoxObject* aBoxObject)
+ {
+ NS_ENSURE_TRUE(!mBoxObject, false);
+ mBoxObject = aBoxObject;
+ return true;
+ }
+
+ virtual bool SupportsOrdinalsInChildren() override;
+
+ virtual bool ComputesOwnOverflowArea() override { return true; }
+
+protected:
+ class nsPositionChangedEvent;
+ friend class nsPositionChangedEvent;
+
+ class nsPositionChangedEvent : public mozilla::Runnable
+ {
+ public:
+ nsPositionChangedEvent(nsListBoxBodyFrame* aFrame,
+ bool aUp, int32_t aDelta) :
+ mFrame(aFrame), mUp(aUp), mDelta(aDelta)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ if (!mFrame) {
+ return NS_OK;
+ }
+
+ mFrame->mPendingPositionChangeEvents.RemoveElement(this);
+
+ return mFrame->DoInternalPositionChanged(mUp, mDelta);
+ }
+
+ void Revoke() {
+ mFrame = nullptr;
+ }
+
+ nsListBoxBodyFrame* mFrame;
+ bool mUp;
+ int32_t mDelta;
+ };
+
+ void ComputeTotalRowCount();
+ int32_t ToRowIndex(nscoord aPos) const;
+ void RemoveChildFrame(nsBoxLayoutState &aState, nsIFrame *aChild);
+
+ nsTArray< RefPtr<nsPositionChangedEvent> > mPendingPositionChangeEvents;
+ nsCOMPtr<nsPIBoxObject> mBoxObject;
+
+ // frame markers
+ nsWeakFrame mTopFrame;
+ nsIFrame* mBottomFrame;
+ nsIFrame* mLinkupFrame;
+
+ nsListScrollSmoother* mScrollSmoother;
+
+ int32_t mRowsToPrepend;
+
+ // row height
+ int32_t mRowCount;
+ nscoord mRowHeight;
+ nscoord mAvailableHeight;
+ nscoord mStringWidth;
+
+ // scrolling
+ int32_t mCurrentIndex; // Row-based
+ int32_t mOldIndex;
+ int32_t mYPosition;
+ int32_t mTimePerRow;
+
+ // row height
+ bool mRowHeightWasSet;
+ // scrolling
+ bool mScrolling;
+ bool mAdjustScroll;
+
+ bool mReflowCallbackPosted;
+};
+
+#endif // nsListBoxBodyFrame_h
diff --git a/layout/xul/nsListBoxLayout.cpp b/layout/xul/nsListBoxLayout.cpp
new file mode 100644
index 000000000..a2f915191
--- /dev/null
+++ b/layout/xul/nsListBoxLayout.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsListBoxLayout.h"
+
+#include "nsListBoxBodyFrame.h"
+#include "nsBox.h"
+#include "nsBoxLayoutState.h"
+#include "nsIScrollableFrame.h"
+#include "nsIReflowCallback.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+
+nsListBoxLayout::nsListBoxLayout() : nsGridRowGroupLayout()
+{
+}
+
+////////// nsBoxLayout //////////////
+
+nsSize
+nsListBoxLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize pref = nsGridRowGroupLayout::GetXULPrefSize(aBox, aBoxLayoutState);
+
+ nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox);
+ if (frame) {
+ nscoord rowheight = frame->GetRowHeightAppUnits();
+ pref.height = frame->GetRowCount() * rowheight;
+ // Pad the height.
+ nscoord y = frame->GetAvailableHeight();
+ if (pref.height > y && y > 0 && rowheight > 0) {
+ nscoord m = (pref.height-y)%rowheight;
+ nscoord remainder = m == 0 ? 0 : rowheight - m;
+ pref.height += remainder;
+ }
+ if (nsContentUtils::HasNonEmptyAttr(frame->GetContent(), kNameSpaceID_None,
+ nsGkAtoms::sizemode)) {
+ nscoord width = frame->ComputeIntrinsicISize(aBoxLayoutState);
+ if (width > pref.width)
+ pref.width = width;
+ }
+ }
+ return pref;
+}
+
+nsSize
+nsListBoxLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize minSize = nsGridRowGroupLayout::GetXULMinSize(aBox, aBoxLayoutState);
+
+ nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox);
+ if (frame) {
+ nscoord rowheight = frame->GetRowHeightAppUnits();
+ minSize.height = frame->GetRowCount() * rowheight;
+ // Pad the height.
+ nscoord y = frame->GetAvailableHeight();
+ if (minSize.height > y && y > 0 && rowheight > 0) {
+ nscoord m = (minSize.height-y)%rowheight;
+ nscoord remainder = m == 0 ? 0 : rowheight - m;
+ minSize.height += remainder;
+ }
+ if (nsContentUtils::HasNonEmptyAttr(frame->GetContent(), kNameSpaceID_None,
+ nsGkAtoms::sizemode)) {
+ nscoord width = frame->ComputeIntrinsicISize(aBoxLayoutState);
+ if (width > minSize.width)
+ minSize.width = width;
+ }
+ }
+ return minSize;
+}
+
+nsSize
+nsListBoxLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize maxSize = nsGridRowGroupLayout::GetXULMaxSize(aBox, aBoxLayoutState);
+
+ nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox);
+ if (frame) {
+ nscoord rowheight = frame->GetRowHeightAppUnits();
+ maxSize.height = frame->GetRowCount() * rowheight;
+ // Pad the height.
+ nscoord y = frame->GetAvailableHeight();
+ if (maxSize.height > y && y > 0 && rowheight > 0) {
+ nscoord m = (maxSize.height-y)%rowheight;
+ nscoord remainder = m == 0 ? 0 : rowheight - m;
+ maxSize.height += remainder;
+ }
+ }
+ return maxSize;
+}
+
+NS_IMETHODIMP
+nsListBoxLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ return LayoutInternal(aBox, aState);
+}
+
+
+/////////// nsListBoxLayout /////////////////////////
+
+/**
+ * Called to layout our our children. Does no frame construction
+ */
+NS_IMETHODIMP
+nsListBoxLayout::LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ int32_t redrawStart = -1;
+
+ // Get the start y position.
+ nsListBoxBodyFrame* body = static_cast<nsListBoxBodyFrame*>(aBox);
+ if (!body) {
+ NS_ERROR("Frame encountered that isn't a listboxbody!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMargin margin;
+
+ // Get our client rect.
+ nsRect clientRect;
+ aBox->GetXULClientRect(clientRect);
+
+ // Get the starting y position and the remaining available
+ // height.
+ nscoord availableHeight = body->GetAvailableHeight();
+ nscoord yOffset = body->GetYPosition();
+
+ if (availableHeight <= 0) {
+ bool fixed = (body->GetFixedRowSize() != -1);
+ if (fixed)
+ availableHeight = 10;
+ else
+ return NS_OK;
+ }
+
+ // run through all our currently created children
+ nsIFrame* box = nsBox::GetChildXULBox(body);
+
+ // if the reason is resize or initial we must relayout.
+ nscoord rowHeight = body->GetRowHeightAppUnits();
+
+ while (box) {
+ // If this box is dirty or if it has dirty children, we
+ // call layout on it.
+ nsRect childRect(box->GetRect());
+ box->GetXULMargin(margin);
+
+ // relayout if we must or we are dirty or some of our children are dirty
+ // or the client area is wider than us
+ // XXXldb There should probably be a resize check here too!
+ if (NS_SUBTREE_DIRTY(box) || childRect.width < clientRect.width) {
+ childRect.x = 0;
+ childRect.y = yOffset;
+ childRect.width = clientRect.width;
+
+ nsSize size = box->GetXULPrefSize(aState);
+ body->SetRowHeight(size.height);
+
+ childRect.height = rowHeight;
+
+ childRect.Deflate(margin);
+ box->SetXULBounds(aState, childRect);
+ box->XULLayout(aState);
+ } else {
+ // if the child did not need to be relayed out. Then its easy.
+ // Place the child by just grabbing its rect and adjusting the y.
+ int32_t newPos = yOffset+margin.top;
+
+ // are we pushing down or pulling up any rows?
+ // Then we may have to redraw everything below the moved
+ // rows.
+ if (redrawStart == -1 && childRect.y != newPos)
+ redrawStart = newPos;
+
+ childRect.y = newPos;
+ box->SetXULBounds(aState, childRect);
+ }
+
+ // Ok now the available size gets smaller and we move the
+ // starting position of the next child down some.
+ nscoord size = childRect.height + margin.top + margin.bottom;
+
+ yOffset += size;
+ availableHeight -= size;
+
+ box = nsBox::GetNextXULBox(box);
+ }
+
+ // We have enough available height left to add some more rows
+ // Since we can't do this during layout, we post a callback
+ // that will be processed after the reflow completes.
+ body->PostReflowCallback();
+
+ // if rows were pushed down or pulled up because some rows were added
+ // before them then redraw everything under the inserted rows. The inserted
+ // rows will automatically be redrawn because the were marked dirty on insertion.
+ if (redrawStart > -1) {
+ aBox->XULRedraw(aState);
+ }
+
+ return NS_OK;
+}
+
+// Creation Routines ///////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsBoxLayout> NS_NewListBoxLayout()
+{
+ RefPtr<nsBoxLayout> layout = new nsListBoxLayout();
+ return layout.forget();
+}
diff --git a/layout/xul/nsListBoxLayout.h b/layout/xul/nsListBoxLayout.h
new file mode 100644
index 000000000..d1f978843
--- /dev/null
+++ b/layout/xul/nsListBoxLayout.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsListBoxLayout_h___
+#define nsListBoxLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGridRowGroupLayout.h"
+
+class nsIFrame;
+typedef class nsIFrame nsIFrame;
+class nsBoxLayoutState;
+
+class nsListBoxLayout : public nsGridRowGroupLayout
+{
+public:
+ nsListBoxLayout();
+
+ // nsBoxLayout
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+
+protected:
+ NS_IMETHOD LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState);
+};
+
+#endif
+
diff --git a/layout/xul/nsListItemFrame.cpp b/layout/xul/nsListItemFrame.cpp
new file mode 100644
index 000000000..1776f1b6c
--- /dev/null
+++ b/layout/xul/nsListItemFrame.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsListItemFrame.h"
+
+#include <algorithm>
+
+#include "nsCOMPtr.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsDisplayList.h"
+#include "nsBoxLayout.h"
+#include "nsIContent.h"
+
+nsListItemFrame::nsListItemFrame(nsStyleContext* aContext,
+ bool aIsRoot,
+ nsBoxLayout* aLayoutManager)
+ : nsGridRowLeafFrame(aContext, aIsRoot, aLayoutManager)
+{
+}
+
+nsListItemFrame::~nsListItemFrame()
+{
+}
+
+nsSize
+nsListItemFrame::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ nsSize size = nsBoxFrame::GetXULPrefSize(aState);
+ DISPLAY_PREF_SIZE(this, size);
+
+ // guarantee that our preferred height doesn't exceed the standard
+ // listbox row height
+ size.height = std::max(mRect.height, size.height);
+ return size;
+}
+
+void
+nsListItemFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (aBuilder->IsForEventDelivery()) {
+ if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents,
+ nsGkAtoms::_true, eCaseMatters))
+ return;
+ }
+
+ nsGridRowLeafFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout();
+
+nsIFrame*
+NS_NewListItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowLeafLayout();
+ if (!layout) {
+ return nullptr;
+ }
+
+ return new (aPresShell) nsListItemFrame(aContext, false, layout);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsListItemFrame)
diff --git a/layout/xul/nsListItemFrame.h b/layout/xul/nsListItemFrame.h
new file mode 100644
index 000000000..40e731efa
--- /dev/null
+++ b/layout/xul/nsListItemFrame.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "nsGridRowLeafFrame.h"
+
+nsIFrame* NS_NewListItemFrame(nsIPresShell* aPresShell,
+ nsStyleContext *aContext);
+
+class nsListItemFrame : public nsGridRowLeafFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewListItemFrame(nsIPresShell* aPresShell,
+ nsStyleContext *aContext);
+
+ // overridden so that children of listitems don't handle mouse events,
+ // unless allowevents="true" is specified on the listitem
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aState) override;
+
+protected:
+ explicit nsListItemFrame(nsStyleContext *aContext,
+ bool aIsRoot = false,
+ nsBoxLayout* aLayoutManager = nullptr);
+ virtual ~nsListItemFrame();
+
+}; // class nsListItemFrame
diff --git a/layout/xul/nsMenuBarFrame.cpp b/layout/xul/nsMenuBarFrame.cpp
new file mode 100644
index 000000000..80aa98fbd
--- /dev/null
+++ b/layout/xul/nsMenuBarFrame.cpp
@@ -0,0 +1,434 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuBarFrame.h"
+#include "nsIServiceManager.h"
+#include "nsIContent.h"
+#include "nsIAtom.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsCSSRendering.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsGkAtoms.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsUnicharUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsCSSFrameConstructor.h"
+#ifdef XP_WIN
+#include "nsISound.h"
+#include "nsWidgetsCID.h"
+#endif
+#include "nsContentUtils.h"
+#include "nsUTF8Utils.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Event.h"
+
+using namespace mozilla;
+
+//
+// NS_NewMenuBarFrame
+//
+// Wrapper for creating a new menu Bar container
+//
+nsIFrame*
+NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMenuBarFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
+
+NS_QUERYFRAME_HEAD(nsMenuBarFrame)
+ NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+//
+// nsMenuBarFrame cntr
+//
+nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext),
+ mStayActive(false),
+ mIsActive(false),
+ mCurrentMenu(nullptr),
+ mTarget(nullptr)
+{
+} // cntr
+
+void
+nsMenuBarFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // Create the menu bar listener.
+ mMenuBarListener = new nsMenuBarListener(this);
+
+ // Hook up the menu bar as a key listener on the whole document. It will see every
+ // key press that occurs, but after everyone else does.
+ mTarget = aContent->GetComposedDoc();
+
+ // Also hook up the listener to the window listening for focus events. This is so we can keep proper
+ // state as the user alt-tabs through processes.
+
+ mTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
+ mTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
+ mTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
+ mTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
+
+ // mousedown event should be handled in all phase
+ mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
+ mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
+ mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
+
+ mTarget->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
+}
+
+NS_IMETHODIMP
+nsMenuBarFrame::SetActive(bool aActiveFlag)
+{
+ // If the activity is not changed, there is nothing to do.
+ if (mIsActive == aActiveFlag)
+ return NS_OK;
+
+ if (!aActiveFlag) {
+ // Don't deactivate when switching between menus on the menubar.
+ if (mStayActive)
+ return NS_OK;
+
+ // if there is a request to deactivate the menu bar, check to see whether
+ // there is a menu popup open for the menu bar. In this case, don't
+ // deactivate the menu bar.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && pm->IsPopupOpenForMenuParent(this))
+ return NS_OK;
+ }
+
+ mIsActive = aActiveFlag;
+ if (mIsActive) {
+ InstallKeyboardNavigator();
+ }
+ else {
+ mActiveByKeyboard = false;
+ RemoveKeyboardNavigator();
+ }
+
+ NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive");
+ NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive");
+
+ FireDOMEvent(mIsActive ? active : inactive, mContent);
+
+ return NS_OK;
+}
+
+nsMenuFrame*
+nsMenuBarFrame::ToggleMenuActiveState()
+{
+ if (mIsActive) {
+ // Deactivate the menu bar
+ SetActive(false);
+ if (mCurrentMenu) {
+ nsMenuFrame* closeframe = mCurrentMenu;
+ closeframe->SelectMenu(false);
+ mCurrentMenu = nullptr;
+ return closeframe;
+ }
+ }
+ else {
+ // if the menu bar is already selected (eg. mouseover), deselect it
+ if (mCurrentMenu)
+ mCurrentMenu->SelectMenu(false);
+
+ // Set the active menu to be the top left item (e.g., the File menu).
+ // We use an attribute called "menuactive" to track the current
+ // active menu.
+ nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false);
+ if (firstFrame) {
+ // Activate the menu bar
+ SetActive(true);
+ firstFrame->SelectMenu(true);
+
+ // Track this item for keyboard navigation.
+ mCurrentMenu = firstFrame;
+ }
+ }
+
+ return nullptr;
+}
+
+nsMenuFrame*
+nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent)
+{
+ uint32_t charCode;
+ aKeyEvent->GetCharCode(&charCode);
+
+ AutoTArray<uint32_t, 10> accessKeys;
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ if (nativeKeyEvent) {
+ nativeKeyEvent->GetAccessKeyCandidates(accessKeys);
+ }
+ if (accessKeys.IsEmpty() && charCode)
+ accessKeys.AppendElement(charCode);
+
+ if (accessKeys.IsEmpty())
+ return nullptr; // no character was pressed so just return
+
+ // Enumerate over our list of frames.
+ auto insertion = PresContext()->PresShell()->FrameConstructor()->
+ GetInsertionPoint(GetContent(), nullptr);
+ nsContainerFrame* immediateParent = insertion.mParentFrame;
+ if (!immediateParent)
+ immediateParent = this;
+
+ // Find a most preferred accesskey which should be returned.
+ nsIFrame* foundMenu = nullptr;
+ size_t foundIndex = accessKeys.NoIndex;
+ nsIFrame* currFrame = immediateParent->PrincipalChildList().FirstChild();
+
+ while (currFrame) {
+ nsIContent* current = currFrame->GetContent();
+
+ // See if it's a menu item.
+ if (nsXULPopupManager::IsValidMenuItem(current, false)) {
+ // Get the shortcut attribute.
+ nsAutoString shortcutKey;
+ current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey);
+ if (!shortcutKey.IsEmpty()) {
+ ToLowerCase(shortcutKey);
+ const char16_t* start = shortcutKey.BeginReading();
+ const char16_t* end = shortcutKey.EndReading();
+ uint32_t ch = UTF16CharEnumerator::NextChar(&start, end);
+ size_t index = accessKeys.IndexOf(ch);
+ if (index != accessKeys.NoIndex &&
+ (foundIndex == accessKeys.NoIndex || index < foundIndex)) {
+ foundMenu = currFrame;
+ foundIndex = index;
+ }
+ }
+ }
+ currFrame = currFrame->GetNextSibling();
+ }
+ if (foundMenu) {
+ return do_QueryFrame(foundMenu);
+ }
+
+ // didn't find a matching menu item
+#ifdef XP_WIN
+ // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
+ if (mIsActive) {
+ nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
+ if (soundInterface)
+ soundInterface->Beep();
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
+ if (popup)
+ pm->HidePopup(popup->GetContent(), true, true, true, false);
+ }
+
+ SetCurrentMenuItem(nullptr);
+ SetActive(false);
+
+#endif // #ifdef XP_WIN
+
+ return nullptr;
+}
+
+/* virtual */ nsMenuFrame*
+nsMenuBarFrame::GetCurrentMenuItem()
+{
+ return mCurrentMenu;
+}
+
+NS_IMETHODIMP
+nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
+{
+ if (mCurrentMenu == aMenuItem)
+ return NS_OK;
+
+ if (mCurrentMenu)
+ mCurrentMenu->SelectMenu(false);
+
+ if (aMenuItem)
+ aMenuItem->SelectMenu(true);
+
+ mCurrentMenu = aMenuItem;
+
+ return NS_OK;
+}
+
+void
+nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
+{
+ mCurrentMenu->SelectMenu(false);
+ mCurrentMenu = nullptr;
+}
+
+class nsMenuBarSwitchMenu : public Runnable
+{
+public:
+ nsMenuBarSwitchMenu(nsIContent* aMenuBar,
+ nsIContent *aOldMenu,
+ nsIContent *aNewMenu,
+ bool aSelectFirstItem)
+ : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu),
+ mSelectFirstItem(aSelectFirstItem)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm)
+ return NS_ERROR_UNEXPECTED;
+
+ // if switching from one menu to another, set a flag so that the call to
+ // HidePopup doesn't deactivate the menubar when the first menu closes.
+ nsMenuBarFrame* menubar = nullptr;
+ if (mOldMenu && mNewMenu) {
+ menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame());
+ if (menubar)
+ menubar->SetStayActive(true);
+ }
+
+ if (mOldMenu) {
+ nsWeakFrame weakMenuBar(menubar);
+ pm->HidePopup(mOldMenu, false, false, false, false);
+ // clear the flag again
+ if (mNewMenu && weakMenuBar.IsAlive())
+ menubar->SetStayActive(false);
+ }
+
+ if (mNewMenu)
+ pm->ShowMenu(mNewMenu, mSelectFirstItem, false);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIContent> mMenuBar;
+ nsCOMPtr<nsIContent> mOldMenu;
+ nsCOMPtr<nsIContent> mNewMenu;
+ bool mSelectFirstItem;
+};
+
+NS_IMETHODIMP
+nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
+ bool aSelectFirstItem,
+ bool aFromKey)
+{
+ if (mCurrentMenu == aMenuItem)
+ return NS_OK;
+
+ // check if there's an open context menu, we ignore this
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && pm->HasContextMenu(nullptr))
+ return NS_OK;
+
+ nsIContent* aOldMenu = nullptr;
+ nsIContent* aNewMenu = nullptr;
+
+ // Unset the current child.
+ bool wasOpen = false;
+ if (mCurrentMenu) {
+ wasOpen = mCurrentMenu->IsOpen();
+ mCurrentMenu->SelectMenu(false);
+ if (wasOpen) {
+ nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup();
+ if (popupFrame)
+ aOldMenu = popupFrame->GetContent();
+ }
+ }
+
+ // set to null first in case the IsAlive check below returns false
+ mCurrentMenu = nullptr;
+
+ // Set the new child.
+ if (aMenuItem) {
+ nsCOMPtr<nsIContent> content = aMenuItem->GetContent();
+ aMenuItem->SelectMenu(true);
+ mCurrentMenu = aMenuItem;
+ if (wasOpen && !aMenuItem->IsDisabled())
+ aNewMenu = content;
+ }
+
+ // use an event so that hiding and showing can be done synchronously, which
+ // avoids flickering
+ nsCOMPtr<nsIRunnable> event =
+ new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
+ return NS_DispatchToCurrentThread(event);
+}
+
+nsMenuFrame*
+nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent)
+{
+ if (!mCurrentMenu)
+ return nullptr;
+
+ if (mCurrentMenu->IsOpen())
+ return mCurrentMenu->Enter(aEvent);
+
+ return mCurrentMenu;
+}
+
+bool
+nsMenuBarFrame::MenuClosed()
+{
+ SetActive(false);
+ if (!mIsActive && mCurrentMenu) {
+ mCurrentMenu->SelectMenu(false);
+ mCurrentMenu = nullptr;
+ return true;
+ }
+ return false;
+}
+
+void
+nsMenuBarFrame::InstallKeyboardNavigator()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->SetActiveMenuBar(this, true);
+}
+
+void
+nsMenuBarFrame::RemoveKeyboardNavigator()
+{
+ if (!mIsActive) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->SetActiveMenuBar(this, false);
+ }
+}
+
+void
+nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->SetActiveMenuBar(this, false);
+
+ mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
+ mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
+ mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
+ mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
+
+ mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
+ mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
+ mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
+
+ mTarget->RemoveEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
+
+ mMenuBarListener->OnDestroyMenuBarFrame();
+ mMenuBarListener = nullptr;
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
diff --git a/layout/xul/nsMenuBarFrame.h b/layout/xul/nsMenuBarFrame.h
new file mode 100644
index 000000000..676463c50
--- /dev/null
+++ b/layout/xul/nsMenuBarFrame.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsMenuBarFrame
+//
+
+#ifndef nsMenuBarFrame_h__
+#define nsMenuBarFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+#include "nsCOMPtr.h"
+#include "nsBoxFrame.h"
+#include "nsMenuFrame.h"
+#include "nsMenuBarListener.h"
+#include "nsMenuParent.h"
+
+class nsIContent;
+
+nsIFrame* NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsMenuBarFrame final : public nsBoxFrame, public nsMenuParent
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMenuBarFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsMenuBarFrame(nsStyleContext* aContext);
+
+ // nsMenuParent interface
+ virtual nsMenuFrame* GetCurrentMenuItem() override;
+ NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) override;
+ virtual void CurrentMenuIsBeingDestroyed() override;
+ NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem,
+ bool aSelectFirstItem,
+ bool aFromKey) override;
+
+ NS_IMETHOD SetActive(bool aActiveFlag) override;
+
+ virtual bool IsMenuBar() override { return true; }
+ virtual bool IsContextMenu() override { return false; }
+ virtual bool IsActive() override { return mIsActive; }
+ virtual bool IsMenu() override { return false; }
+ virtual bool IsOpen() override { return true; } // menubars are considered always open
+
+ bool IsMenuOpen() { return mCurrentMenu && mCurrentMenu->IsOpen(); }
+
+ void InstallKeyboardNavigator();
+ void RemoveKeyboardNavigator();
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void LockMenuUntilClosed(bool aLock) override {}
+ virtual bool IsMenuLocked() override { return false; }
+
+// Non-interface helpers
+
+ // The 'stay active' flag is set when navigating from one top-level menu
+ // to another, to prevent the menubar from deactivating and submenus from
+ // firing extra DOMMenuItemActive events.
+ bool GetStayActive() { return mStayActive; }
+ void SetStayActive(bool aStayActive) { mStayActive = aStayActive; }
+
+ // Called when a menu on the menu bar is clicked on. Returns a menu if one
+ // needs to be closed.
+ nsMenuFrame* ToggleMenuActiveState();
+
+ bool IsActiveByKeyboard() { return mActiveByKeyboard; }
+ void SetActiveByKeyboard() { mActiveByKeyboard = true; }
+
+ // indicate that a menu on the menubar was closed. Returns true if the caller
+ // may deselect the menuitem.
+ virtual bool MenuClosed() override;
+
+ // Called when Enter is pressed while the menubar is focused. If the current
+ // menu is open, let the child handle the key.
+ nsMenuFrame* Enter(mozilla::WidgetGUIEvent* aEvent);
+
+ // Used to handle ALT+key combos
+ nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent);
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // Override bogus IsFrameOfType in nsBoxFrame.
+ if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced))
+ return false;
+ return nsBoxFrame::IsFrameOfType(aFlags);
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("MenuBar"), aResult);
+ }
+#endif
+
+protected:
+ RefPtr<nsMenuBarListener> mMenuBarListener; // The listener that tells us about key and mouse events.
+
+ // flag that is temporarily set when switching from one menu on the menubar to another
+ // to indicate that the menubar should not be deactivated.
+ bool mStayActive;
+
+ bool mIsActive; // Whether or not the menu bar is active (a menu item is highlighted or shown).
+
+ // whether the menubar was made active via the keyboard.
+ bool mActiveByKeyboard;
+
+ // The current menu that is active (highlighted), which may not be open. This will
+ // be null if no menu is active.
+ nsMenuFrame* mCurrentMenu;
+
+ mozilla::dom::EventTarget* mTarget;
+
+}; // class nsMenuBarFrame
+
+#endif
diff --git a/layout/xul/nsMenuBarListener.cpp b/layout/xul/nsMenuBarListener.cpp
new file mode 100644
index 000000000..0fce497b8
--- /dev/null
+++ b/layout/xul/nsMenuBarListener.cpp
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuBarListener.h"
+#include "nsMenuBarFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIDOMEvent.h"
+
+// Drag & Drop, Clipboard
+#include "nsIServiceManager.h"
+#include "nsWidgetsCID.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIContent.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+
+#include "nsContentUtils.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+
+/*
+ * nsMenuBarListener implementation
+ */
+
+NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener)
+
+////////////////////////////////////////////////////////////////////////
+
+int32_t nsMenuBarListener::mAccessKey = -1;
+Modifiers nsMenuBarListener::mAccessKeyMask = 0;
+bool nsMenuBarListener::mAccessKeyFocuses = false;
+
+nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar)
+ :mAccessKeyDown(false), mAccessKeyDownCanceled(false)
+{
+ mMenuBarFrame = aMenuBar;
+}
+
+////////////////////////////////////////////////////////////////////////
+nsMenuBarListener::~nsMenuBarListener()
+{
+}
+
+void
+nsMenuBarListener::OnDestroyMenuBarFrame()
+{
+ mMenuBarFrame = nullptr;
+}
+
+void
+nsMenuBarListener::InitializeStatics()
+{
+ Preferences::AddBoolVarCache(&mAccessKeyFocuses,
+ "ui.key.menuAccessKeyFocuses");
+}
+
+nsresult
+nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey)
+{
+ if (!aAccessKey)
+ return NS_ERROR_INVALID_POINTER;
+ InitAccessKey();
+ *aAccessKey = mAccessKey;
+ return NS_OK;
+}
+
+void nsMenuBarListener::InitAccessKey()
+{
+ if (mAccessKey >= 0)
+ return;
+
+ // Compiled-in defaults, in case we can't get LookAndFeel --
+ // mac doesn't have menu shortcuts, other platforms use alt.
+#ifdef XP_MACOSX
+ mAccessKey = 0;
+ mAccessKeyMask = 0;
+#else
+ mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
+ mAccessKeyMask = MODIFIER_ALT;
+#endif
+
+ // Get the menu access key value from prefs, overriding the default:
+ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey);
+ if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT)
+ mAccessKeyMask = MODIFIER_SHIFT;
+ else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL)
+ mAccessKeyMask = MODIFIER_CONTROL;
+ else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT)
+ mAccessKeyMask = MODIFIER_ALT;
+ else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META)
+ mAccessKeyMask = MODIFIER_META;
+ else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN)
+ mAccessKeyMask = MODIFIER_OS;
+}
+
+void
+nsMenuBarListener::ToggleMenuActiveState()
+{
+ nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState();
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && closemenu) {
+ nsMenuPopupFrame* popupFrame = closemenu->GetPopup();
+ if (popupFrame)
+ pm->HidePopup(popupFrame->GetContent(), false, false, true, false);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult
+nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent)
+{
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ InitAccessKey();
+
+ //handlers shouldn't be triggered by non-trusted events.
+ bool trustedEvent = false;
+ aKeyEvent->GetIsTrusted(&trustedEvent);
+
+ if (!trustedEvent) {
+ return NS_OK;
+ }
+
+ if (mAccessKey && mAccessKeyFocuses)
+ {
+ bool defaultPrevented = false;
+ aKeyEvent->GetDefaultPrevented(&defaultPrevented);
+
+ // On a press of the ALT key by itself, we toggle the menu's
+ // active/inactive state.
+ // Get the ascii key code.
+ uint32_t theChar;
+ keyEvent->GetKeyCode(&theChar);
+
+ if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled &&
+ (int32_t)theChar == mAccessKey)
+ {
+ // The access key was down and is now up, and no other
+ // keys were pressed in between.
+ bool toggleMenuActiveState = true;
+ if (!mMenuBarFrame->IsActive()) {
+ // First, close all existing popups because other popups shouldn't
+ // handle key events when menubar is active and IME should be
+ // disabled.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->Rollup(0, false, nullptr, nullptr);
+ }
+ // If menubar active state is changed or the menubar is destroyed
+ // during closing the popups, we should do nothing anymore.
+ toggleMenuActiveState = !Destroyed() && !mMenuBarFrame->IsActive();
+ }
+ if (toggleMenuActiveState) {
+ if (!mMenuBarFrame->IsActive()) {
+ mMenuBarFrame->SetActiveByKeyboard();
+ }
+ ToggleMenuActiveState();
+ }
+ }
+ mAccessKeyDown = false;
+ mAccessKeyDownCanceled = false;
+
+ bool active = !Destroyed() && mMenuBarFrame->IsActive();
+ if (active) {
+ aKeyEvent->StopPropagation();
+ aKeyEvent->PreventDefault();
+ return NS_OK; // I am consuming event
+ }
+ }
+
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult
+nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
+{
+ // if event has already been handled, bail
+ if (aKeyEvent) {
+ bool eventHandled = false;
+ aKeyEvent->GetDefaultPrevented(&eventHandled);
+ if (eventHandled) {
+ return NS_OK; // don't consume event
+ }
+ }
+
+ //handlers shouldn't be triggered by non-trusted events.
+ bool trustedEvent = false;
+ if (aKeyEvent) {
+ aKeyEvent->GetIsTrusted(&trustedEvent);
+ }
+
+ if (!trustedEvent) {
+ return NS_OK;
+ }
+
+ InitAccessKey();
+
+ if (mAccessKey)
+ {
+ // If accesskey handling was forwarded to a child process, wait for
+ // the mozaccesskeynotfound event before handling accesskeys.
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (!nativeKeyEvent ||
+ (nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+ uint32_t keyCode, charCode;
+ keyEvent->GetKeyCode(&keyCode);
+ keyEvent->GetCharCode(&charCode);
+
+ bool hasAccessKeyCandidates = charCode != 0;
+ if (!hasAccessKeyCandidates) {
+ if (nativeKeyEvent) {
+ AutoTArray<uint32_t, 10> keys;
+ nativeKeyEvent->GetAccessKeyCandidates(keys);
+ hasAccessKeyCandidates = !keys.IsEmpty();
+ }
+ }
+
+ // Cancel the access key flag unless we are pressing the access key.
+ if (keyCode != (uint32_t)mAccessKey) {
+ mAccessKeyDownCanceled = true;
+ }
+
+ if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) {
+ // Do shortcut navigation.
+ // A letter was pressed. We want to see if a shortcut gets matched. If
+ // so, we'll know the menu got activated.
+ nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent);
+ if (result) {
+ mMenuBarFrame->SetActiveByKeyboard();
+ mMenuBarFrame->SetActive(true);
+ result->OpenMenu(true);
+
+ // The opened menu will listen next keyup event.
+ // Therefore, we should clear the keydown flags here.
+ mAccessKeyDown = mAccessKeyDownCanceled = false;
+
+ aKeyEvent->StopPropagation();
+ aKeyEvent->PreventDefault();
+ }
+ }
+#ifndef XP_MACOSX
+ // Also need to handle F10 specially on Non-Mac platform.
+ else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
+ if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
+ // The F10 key just went down by itself or with ctrl pressed.
+ // In Windows, both of these activate the menu bar.
+ mMenuBarFrame->SetActiveByKeyboard();
+ ToggleMenuActiveState();
+
+ if (mMenuBarFrame->IsActive()) {
+#ifdef MOZ_WIDGET_GTK
+ // In GTK, this also opens the first menu.
+ mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true);
+#endif
+ aKeyEvent->StopPropagation();
+ aKeyEvent->PreventDefault();
+ }
+ }
+ }
+#endif // !XP_MACOSX
+ }
+
+ return NS_OK;
+}
+
+bool
+nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent)
+{
+ InitAccessKey();
+ // No other modifiers are allowed to be down except for Shift.
+ uint32_t modifiers = GetModifiersForAccessKey(aKeyEvent);
+
+ return (mAccessKeyMask != MODIFIER_SHIFT &&
+ (modifiers & mAccessKeyMask) &&
+ (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0);
+}
+
+Modifiers
+nsMenuBarListener::GetModifiersForAccessKey(nsIDOMKeyEvent* aKeyEvent)
+{
+ WidgetInputEvent* inputEvent =
+ aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
+ MOZ_ASSERT(inputEvent);
+
+ static const Modifiers kPossibleModifiersForAccessKey =
+ (MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META |
+ MODIFIER_OS);
+ return (inputEvent->mModifiers & kPossibleModifiersForAccessKey);
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult
+nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent)
+{
+ InitAccessKey();
+
+ //handlers shouldn't be triggered by non-trusted events.
+ bool trustedEvent = false;
+ if (aKeyEvent) {
+ aKeyEvent->GetIsTrusted(&trustedEvent);
+ }
+
+ if (!trustedEvent)
+ return NS_OK;
+
+ if (mAccessKey && mAccessKeyFocuses)
+ {
+ bool defaultPrevented = false;
+ aKeyEvent->GetDefaultPrevented(&defaultPrevented);
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+ uint32_t theChar;
+ keyEvent->GetKeyCode(&theChar);
+
+ // No other modifiers can be down.
+ // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US
+ // enhanced 102-key keyboards if we don't check this.
+ bool isAccessKeyDownEvent =
+ ((theChar == (uint32_t)mAccessKey) &&
+ (GetModifiersForAccessKey(keyEvent) & ~mAccessKeyMask) == 0);
+
+ if (!mAccessKeyDown) {
+ // If accesskey isn't being pressed and the key isn't the accesskey,
+ // ignore the event.
+ if (!isAccessKeyDownEvent) {
+ return NS_OK;
+ }
+
+ // Otherwise, accept the accesskey state.
+ mAccessKeyDown = true;
+ // If default is prevented already, cancel the access key down.
+ mAccessKeyDownCanceled = defaultPrevented;
+ return NS_OK;
+ }
+
+ // If the pressed accesskey was canceled already or the event was
+ // consumed already, ignore the event.
+ if (mAccessKeyDownCanceled || defaultPrevented) {
+ return NS_OK;
+ }
+
+ // Some key other than the access key just went down,
+ // so we won't activate the menu bar when the access key is released.
+ mAccessKeyDownCanceled = !isAccessKeyDownEvent;
+ }
+
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMenuBarListener::Blur(nsIDOMEvent* aEvent)
+{
+ if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) {
+ ToggleMenuActiveState();
+ }
+ // Reset the accesskey state because we cannot receive the keyup event for
+ // the pressing accesskey.
+ mAccessKeyDown = false;
+ mAccessKeyDownCanceled = false;
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult
+nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent)
+{
+ // NOTE: MouseDown method listens all phases
+
+ // Even if the mousedown event is canceled, it means the user don't want
+ // to activate the menu. Therefore, we need to record it at capturing (or
+ // target) phase.
+ if (mAccessKeyDown) {
+ mAccessKeyDownCanceled = true;
+ }
+
+ uint16_t phase = 0;
+ nsresult rv = aMouseEvent->GetEventPhase(&phase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Don't do anything at capturing phase, any behavior should be cancelable.
+ if (phase == nsIDOMEvent::CAPTURING_PHASE) {
+ return NS_OK;
+ }
+
+ if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive())
+ ToggleMenuActiveState();
+
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMenuBarListener::Fullscreen(nsIDOMEvent* aEvent)
+{
+ if (mMenuBarFrame->IsActive()) {
+ ToggleMenuActiveState();
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult
+nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ // If the menu bar is collapsed, don't do anything.
+ if (!mMenuBarFrame->StyleVisibility()->IsVisible()) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("keyup")) {
+ return KeyUp(aEvent);
+ }
+ if (eventType.EqualsLiteral("keydown")) {
+ return KeyDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("keypress")) {
+ return KeyPress(aEvent);
+ }
+ if (eventType.EqualsLiteral("mozaccesskeynotfound")) {
+ return KeyPress(aEvent);
+ }
+ if (eventType.EqualsLiteral("blur")) {
+ return Blur(aEvent);
+ }
+ if (eventType.EqualsLiteral("mousedown")) {
+ return MouseDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("MozDOMFullscreen:Entered")) {
+ return Fullscreen(aEvent);
+ }
+
+ NS_ABORT();
+
+ return NS_OK;
+}
diff --git a/layout/xul/nsMenuBarListener.h b/layout/xul/nsMenuBarListener.h
new file mode 100644
index 000000000..3656c89e2
--- /dev/null
+++ b/layout/xul/nsMenuBarListener.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsMenuBarListener_h__
+#define nsMenuBarListener_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIDOMEventListener.h"
+
+// X.h defines KeyPress
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+class nsMenuBarFrame;
+class nsIDOMKeyEvent;
+
+/** editor Implementation of the DragListener interface
+ */
+class nsMenuBarListener final : public nsIDOMEventListener
+{
+public:
+ /** default constructor
+ */
+ explicit nsMenuBarListener(nsMenuBarFrame* aMenuBar);
+
+ static void InitializeStatics();
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+ nsresult KeyUp(nsIDOMEvent* aMouseEvent);
+ nsresult KeyDown(nsIDOMEvent* aMouseEvent);
+ nsresult KeyPress(nsIDOMEvent* aMouseEvent);
+ nsresult Blur(nsIDOMEvent* aEvent);
+ nsresult MouseDown(nsIDOMEvent* aMouseEvent);
+ nsresult Fullscreen(nsIDOMEvent* aEvent);
+
+ static nsresult GetMenuAccessKey(int32_t* aAccessKey);
+
+ NS_DECL_ISUPPORTS
+
+ static bool IsAccessKeyPressed(nsIDOMKeyEvent* event);
+
+ void OnDestroyMenuBarFrame();
+
+protected:
+ /** default destructor
+ */
+ virtual ~nsMenuBarListener();
+
+ static void InitAccessKey();
+
+ static mozilla::Modifiers GetModifiersForAccessKey(nsIDOMKeyEvent* event);
+
+ // This should only be called by the nsMenuBarListener during event dispatch,
+ // thus ensuring that this doesn't get destroyed during the process.
+ void ToggleMenuActiveState();
+
+ bool Destroyed() const { return !mMenuBarFrame; }
+
+ nsMenuBarFrame* mMenuBarFrame; // The menu bar object.
+ // Whether or not the ALT key is currently down.
+ bool mAccessKeyDown;
+ // Whether or not the ALT key down is canceled by other action.
+ bool mAccessKeyDownCanceled;
+ static bool mAccessKeyFocuses; // Does the access key by itself focus the menubar?
+ static int32_t mAccessKey; // See nsIDOMKeyEvent.h for sample values
+ static mozilla::Modifiers mAccessKeyMask;// Modifier mask for the access key
+};
+
+
+#endif
diff --git a/layout/xul/nsMenuFrame.cpp b/layout/xul/nsMenuFrame.cpp
new file mode 100644
index 000000000..ea968fab5
--- /dev/null
+++ b/layout/xul/nsMenuFrame.cpp
@@ -0,0 +1,1514 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGkAtoms.h"
+#include "nsHTMLParts.h"
+#include "nsMenuFrame.h"
+#include "nsBoxFrame.h"
+#include "nsIContent.h"
+#include "nsIAtom.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsStyleContext.h"
+#include "nsCSSRendering.h"
+#include "nsNameSpaceManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsMenuBarFrame.h"
+#include "nsIDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIComponentManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsIScrollableFrame.h"
+#include "nsBindingManager.h"
+#include "nsIServiceManager.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIStringBundle.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsIReflowCallback.h"
+#include "nsISound.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+#define NS_MENU_POPUP_LIST_INDEX 0
+
+#if defined(XP_WIN)
+#define NSCONTEXTMENUISMOUSEUP 1
+#endif
+
+NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PopupListProperty)
+
+// This global flag indicates that a menu just opened or closed and is used
+// to ignore the mousemove and mouseup events that would fire on the menu after
+// the mousedown occurred.
+static int32_t gMenuJustOpenedOrClosed = false;
+
+const int32_t kBlinkDelay = 67; // milliseconds
+
+// this class is used for dispatching menu activation events asynchronously.
+class nsMenuActivateEvent : public Runnable
+{
+public:
+ nsMenuActivateEvent(nsIContent *aMenu,
+ nsPresContext* aPresContext,
+ bool aIsActivate)
+ : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsAutoString domEventToFire;
+
+ if (mIsActivate) {
+ // Highlight the menu.
+ mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
+ NS_LITERAL_STRING("true"), true);
+ // The menuactivated event is used by accessibility to track the user's
+ // movements through menus
+ domEventToFire.AssignLiteral("DOMMenuItemActive");
+ }
+ else {
+ // Unhighlight the menu.
+ mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
+ domEventToFire.AssignLiteral("DOMMenuItemInactive");
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(mMenu, mPresContext, nullptr);
+ event->InitEvent(domEventToFire, true, true);
+
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event,
+ mPresContext, nullptr);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIContent> mMenu;
+ RefPtr<nsPresContext> mPresContext;
+ bool mIsActivate;
+};
+
+class nsMenuAttributeChangedEvent : public Runnable
+{
+public:
+ nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr)
+ : mFrame(aFrame), mAttr(aAttr)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
+ NS_ENSURE_STATE(frame);
+ if (mAttr == nsGkAtoms::checked) {
+ frame->UpdateMenuSpecialState();
+ } else if (mAttr == nsGkAtoms::acceltext) {
+ // someone reset the accelText attribute,
+ // so clear the bit that says *we* set it
+ frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
+ frame->BuildAcceleratorText(true);
+ }
+ else if (mAttr == nsGkAtoms::key) {
+ frame->BuildAcceleratorText(true);
+ } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
+ frame->UpdateMenuType();
+ }
+ return NS_OK;
+ }
+protected:
+ nsWeakFrame mFrame;
+ nsCOMPtr<nsIAtom> mAttr;
+};
+
+//
+// NS_NewMenuFrame and NS_NewMenuItemFrame
+//
+// Wrappers for creating a new menu popup container
+//
+nsIFrame*
+NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsMenuFrame* it = new (aPresShell) nsMenuFrame(aContext);
+ it->SetIsMenu(true);
+ return it;
+}
+
+nsIFrame*
+NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsMenuFrame* it = new (aPresShell) nsMenuFrame(aContext);
+ it->SetIsMenu(false);
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
+
+NS_QUERYFRAME_HEAD(nsMenuFrame)
+ NS_QUERYFRAME_ENTRY(nsMenuFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+nsMenuFrame::nsMenuFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext),
+ mIsMenu(false),
+ mChecked(false),
+ mIgnoreAccelTextChange(false),
+ mType(eMenuType_Normal),
+ mBlinkState(0)
+{
+}
+
+nsMenuParent*
+nsMenuFrame::GetMenuParent() const
+{
+ nsContainerFrame* parent = GetParent();
+ for (; parent; parent = parent->GetParent()) {
+ nsMenuPopupFrame* popup = do_QueryFrame(parent);
+ if (popup) {
+ return popup;
+ }
+ nsMenuBarFrame* menubar = do_QueryFrame(parent);
+ if (menubar) {
+ return menubar;
+ }
+ }
+ return nullptr;
+}
+
+class nsASyncMenuInitialization final : public nsIReflowCallback
+{
+public:
+ explicit nsASyncMenuInitialization(nsIFrame* aFrame)
+ : mWeakFrame(aFrame)
+ {
+ }
+
+ virtual bool ReflowFinished() override
+ {
+ bool shouldFlush = false;
+ nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame());
+ if (menu) {
+ menu->UpdateMenuType();
+ shouldFlush = true;
+ }
+ delete this;
+ return shouldFlush;
+ }
+
+ virtual void ReflowCallbackCanceled() override
+ {
+ delete this;
+ }
+
+ nsWeakFrame mWeakFrame;
+};
+
+void
+nsMenuFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // Set up a mediator which can be used for callbacks on this frame.
+ mTimerMediator = new nsMenuTimerMediator(this);
+
+ BuildAcceleratorText(false);
+ nsIReflowCallback* cb = new nsASyncMenuInitialization(this);
+ PresContext()->PresShell()->PostReflowCallback(cb);
+}
+
+const nsFrameList&
+nsMenuFrame::GetChildList(ChildListID aListID) const
+{
+ if (kPopupList == aListID) {
+ nsFrameList* list = GetPopupList();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ return nsBoxFrame::GetChildList(aListID);
+}
+
+void
+nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const
+{
+ nsBoxFrame::GetChildLists(aLists);
+ nsFrameList* list = GetPopupList();
+ if (list) {
+ list->AppendIfNonempty(aLists, kPopupList);
+ }
+}
+
+nsMenuPopupFrame*
+nsMenuFrame::GetPopup()
+{
+ nsFrameList* popupList = GetPopupList();
+ return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) :
+ nullptr;
+}
+
+nsFrameList*
+nsMenuFrame::GetPopupList() const
+{
+ if (!HasPopup()) {
+ return nullptr;
+ }
+ nsFrameList* prop = Properties().Get(PopupListProperty());
+ NS_ASSERTION(prop && prop->GetLength() == 1 &&
+ prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame,
+ "popup list should have exactly one nsMenuPopupFrame");
+ return prop;
+}
+
+void
+nsMenuFrame::DestroyPopupList()
+{
+ NS_ASSERTION(HasPopup(), "huh?");
+ nsFrameList* prop = Properties().Remove(PopupListProperty());
+ NS_ASSERTION(prop && prop->IsEmpty(),
+ "popup list must exist and be empty when destroying");
+ RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
+ prop->Delete(PresContext()->PresShell());
+}
+
+void
+nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList)
+{
+ for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get());
+ if (popupFrame) {
+ // Remove the frame from the list and store it in a nsFrameList* property.
+ aFrameList.RemoveFrame(popupFrame);
+ nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame);
+ Properties().Set(PopupListProperty(), popupList);
+ AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
+ break;
+ }
+ }
+}
+
+void
+nsMenuFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (aListID == kPrincipalList || aListID == kPopupList) {
+ NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
+ SetPopupFrame(aChildList);
+ }
+ nsBoxFrame::SetInitialChildList(aListID, aChildList);
+}
+
+void
+nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // Kill our timer if one is active. This is not strictly necessary as
+ // the pointer to this frame will be cleared from the mediator, but
+ // this is done for added safety.
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ }
+
+ StopBlinking();
+
+ // Null out the pointer to this frame in the mediator wrapper so that it
+ // doesn't try to interact with a deallocated frame.
+ mTimerMediator->ClearFrame();
+
+ // if the menu content is just being hidden, it may be made visible again
+ // later, so make sure to clear the highlighting.
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false);
+
+ // are we our menu parent's current menu item?
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent && menuParent->GetCurrentMenuItem() == this) {
+ // yes; tell it that we're going away
+ menuParent->CurrentMenuIsBeingDestroyed();
+ }
+
+ nsFrameList* popupList = GetPopupList();
+ if (popupList) {
+ popupList->DestroyFramesFrom(aDestructRoot);
+ DestroyPopupList();
+ }
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!aBuilder->IsForEventDelivery()) {
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
+
+ WrapListsInRedirector(aBuilder, set, aLists);
+}
+
+nsresult
+nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent && menuParent->IsMenuLocked()) {
+ return NS_OK;
+ }
+
+ nsWeakFrame weakFrame(this);
+ if (*aEventStatus == nsEventStatus_eIgnore)
+ *aEventStatus = nsEventStatus_eConsumeDoDefault;
+
+ // If a menu just opened, ignore the mouseup event that might occur after a
+ // the mousedown event that opened it. However, if a different mousedown
+ // event occurs, just clear this flag.
+ if (gMenuJustOpenedOrClosed) {
+ if (aEvent->mMessage == eMouseDown) {
+ gMenuJustOpenedOrClosed = false;
+ } else if (aEvent->mMessage == eMouseUp) {
+ return NS_OK;
+ }
+ }
+
+ bool onmenu = IsOnMenu();
+
+ if (aEvent->mMessage == eKeyPress && !IsDisabled()) {
+ WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
+ uint32_t keyCode = keyEvent->mKeyCode;
+#ifdef XP_MACOSX
+ // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
+ if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
+ (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
+
+ // When pressing space, don't open the menu if performing an incremental search.
+ if (keyEvent->mCharCode != ' ' ||
+ !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ OpenMenu(false);
+ }
+ }
+#else
+ // On other platforms, toggle menulist on unmodified F4 or Alt arrow
+ if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
+ ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ ToggleMenuState();
+ }
+#endif
+ }
+ else if (aEvent->mMessage == eMouseDown &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
+ !IsDisabled() && IsMenu()) {
+ // The menu item was selected. Bring up the menu.
+ // We have children.
+ // Don't prevent the default action here, since that will also cancel
+ // potential drag starts.
+ if (!menuParent || menuParent->IsMenuBar()) {
+ ToggleMenuState();
+ }
+ else {
+ if (!IsOpen()) {
+ menuParent->ChangeMenuItem(this, false, false);
+ OpenMenu(false);
+ }
+ }
+ }
+ else if (
+#ifndef NSCONTEXTMENUISMOUSEUP
+ (aEvent->mMessage == eMouseUp &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) &&
+#else
+ aEvent->mMessage == eContextMenu &&
+#endif
+ onmenu && !IsMenu() && !IsDisabled()) {
+ // if this menu is a context menu it accepts right-clicks...fire away!
+ // Make sure we cancel default processing of the context menu event so
+ // that it doesn't bubble and get seen again by the popuplistener and show
+ // another context menu.
+ //
+ // Furthermore (there's always more, isn't there?), on some platforms (win32
+ // being one of them) we get the context menu event on a mouse up while
+ // on others we get it on a mouse down. For the ones where we get it on a
+ // mouse down, we must continue listening for the right button up event to
+ // dismiss the menu.
+ if (menuParent->IsContextMenu()) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ Execute(aEvent);
+ }
+ }
+ else if (aEvent->mMessage == eMouseUp &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
+ !IsMenu() && !IsDisabled()) {
+ // Execute the execute event handler.
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ Execute(aEvent);
+ }
+ else if (aEvent->mMessage == eMouseOut) {
+ // Kill our timer if one is active.
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ // Deactivate the menu.
+ if (menuParent) {
+ bool onmenubar = menuParent->IsMenuBar();
+ if (!(onmenubar && menuParent->IsActive())) {
+ if (IsMenu() && !onmenubar && IsOpen()) {
+ // Submenus don't get closed up immediately.
+ }
+ else if (this == menuParent->GetCurrentMenuItem()
+#ifdef XP_WIN
+ && GetParentMenuListType() == eNotMenuList
+#endif
+ ) {
+ menuParent->ChangeMenuItem(nullptr, false, false);
+ }
+ }
+ }
+ }
+ else if (aEvent->mMessage == eMouseMove &&
+ (onmenu || (menuParent && menuParent->IsMenuBar()))) {
+ if (gMenuJustOpenedOrClosed) {
+ gMenuJustOpenedOrClosed = false;
+ return NS_OK;
+ }
+
+ if (IsDisabled() && GetParentMenuListType() != eNotMenuList) {
+ return NS_OK;
+ }
+
+ // Let the menu parent know we're the new item.
+ menuParent->ChangeMenuItem(this, false, false);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
+ NS_ENSURE_TRUE(menuParent, NS_OK);
+
+ // we need to check if we really became the current menu
+ // item or not
+ nsMenuFrame *realCurrentItem = menuParent->GetCurrentMenuItem();
+ if (realCurrentItem != this) {
+ // we didn't (presumably because a context menu was active)
+ return NS_OK;
+ }
+
+ // Hovering over a menu in a popup should open it without a need for a click.
+ // A timer is used so that it doesn't open if the user moves the mouse quickly
+ // past the menu. This conditional check ensures that only menus have this
+ // behaviour
+ if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !menuParent->IsMenuBar()) {
+ int32_t menuDelay =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
+
+ // We're a menu, we're built, we're closed, and no timer has been kicked off.
+ mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuFrame::ToggleMenuState()
+{
+ if (IsOpen())
+ CloseMenu(false);
+ else
+ OpenMenu(false);
+}
+
+void
+nsMenuFrame::PopupOpened()
+{
+ nsWeakFrame weakFrame(this);
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
+ NS_LITERAL_STRING("true"), true);
+ if (!weakFrame.IsAlive())
+ return;
+
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent) {
+ menuParent->SetActive(true);
+ // Make sure the current menu which is being toggled on
+ // the menubar is highlighted
+ menuParent->SetCurrentMenuItem(this);
+ }
+}
+
+void
+nsMenuFrame::PopupClosed(bool aDeselectMenu)
+{
+ nsWeakFrame weakFrame(this);
+ nsContentUtils::AddScriptRunner(
+ new nsUnsetAttrRunnable(mContent, nsGkAtoms::open));
+ if (!weakFrame.IsAlive())
+ return;
+
+ // if the popup is for a menu on a menubar, inform menubar to deactivate
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent && menuParent->MenuClosed()) {
+ if (aDeselectMenu) {
+ SelectMenu(false);
+ } else {
+ // We are not deselecting the parent menu while closing the popup, so send
+ // a DOMMenuItemActive event to the menu to indicate that the menu is
+ // becoming active again.
+ nsMenuFrame *current = menuParent->GetCurrentMenuItem();
+ if (current) {
+ // However, if the menu is a descendant on a menubar, and the menubar
+ // has the 'stay active' flag set, it means that the menubar is switching
+ // to another toplevel menu entirely (for example from Edit to View), so
+ // don't fire the DOMMenuItemActive event or else we'll send extraneous
+ // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected
+ // the old menu, so it doesn't need to happen again here, and the new
+ // menu can be selected right away.
+ nsIFrame* parent = current;
+ while (parent) {
+ nsMenuBarFrame* menubar = do_QueryFrame(parent);
+ if (menubar && menubar->GetStayActive())
+ return;
+
+ parent = parent->GetParent();
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ new nsMenuActivateEvent(current->GetContent(),
+ PresContext(), true);
+ NS_DispatchToCurrentThread(event);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMenuFrame::SelectMenu(bool aActivateFlag)
+{
+ if (mContent) {
+ // When a menu opens a submenu, the mouse will often be moved onto a
+ // sibling before moving onto an item within the submenu, causing the
+ // parent to become deselected. We need to ensure that the parent menu
+ // is reselected when an item in the submenu is selected, so navigate up
+ // from the item to its popup, and then to the popup above that.
+ if (aActivateFlag) {
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ nsMenuPopupFrame* menupopup = do_QueryFrame(parent);
+ if (menupopup) {
+ // a menu is always the direct parent of a menupopup
+ nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent());
+ if (menu) {
+ // a popup however is not necessarily the direct parent of a menu
+ nsIFrame* popupParent = menu->GetParent();
+ while (popupParent) {
+ menupopup = do_QueryFrame(popupParent);
+ if (menupopup) {
+ menupopup->SetCurrentMenuItem(menu);
+ break;
+ }
+ popupParent = popupParent->GetParent();
+ }
+ }
+ break;
+ }
+ parent = parent->GetParent();
+ }
+ }
+
+ // cancel the close timer if selecting a menu within the popup to be closed
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsMenuParent* menuParent = GetMenuParent();
+ pm->CancelMenuTimer(menuParent);
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag);
+ NS_DispatchToCurrentThread(event);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) {
+ // Reset the flag so that only one change is ignored.
+ mIgnoreAccelTextChange = false;
+ return NS_OK;
+ }
+
+ if (aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::acceltext ||
+ aAttribute == nsGkAtoms::key ||
+ aAttribute == nsGkAtoms::type ||
+ aAttribute == nsGkAtoms::name) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsMenuAttributeChangedEvent(this, aAttribute);
+ nsContentUtils::AddScriptRunner(event);
+ }
+ return NS_OK;
+}
+
+nsIContent*
+nsMenuFrame::GetAnchor()
+{
+ mozilla::dom::Element* anchor = nullptr;
+
+ nsAutoString id;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
+ if (!id.IsEmpty()) {
+ nsIDocument* doc = mContent->OwnerDoc();
+
+ anchor =
+ doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
+ if (!anchor) {
+ anchor = doc->GetElementById(id);
+ }
+ }
+
+ // Always return the menu's content if the anchor wasn't set or wasn't found.
+ return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
+}
+
+void
+nsMenuFrame::OpenMenu(bool aSelectFirstItem)
+{
+ if (!mContent)
+ return;
+
+ gMenuJustOpenedOrClosed = true;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->KillMenuTimer();
+ // This opens the menu asynchronously
+ pm->ShowMenu(mContent, aSelectFirstItem, true);
+ }
+}
+
+void
+nsMenuFrame::CloseMenu(bool aDeselectMenu)
+{
+ gMenuJustOpenedOrClosed = true;
+
+ // Close the menu asynchronously
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && HasPopup())
+ pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false);
+}
+
+bool
+nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways)
+{
+ nsAutoString sizedToPopup;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup);
+ return sizedToPopup.EqualsLiteral("always") ||
+ (!aRequireAlways && sizedToPopup.EqualsLiteral("pref"));
+}
+
+nsSize
+nsMenuFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize size = nsBoxFrame::GetXULMinSize(aBoxLayoutState);
+ DISPLAY_MIN_SIZE(this, size);
+
+ if (IsSizedToPopup(mContent, true))
+ SizeToPopup(aBoxLayoutState, size);
+
+ return size;
+}
+
+NS_IMETHODIMP
+nsMenuFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ // lay us out
+ nsresult rv = nsBoxFrame::DoXULLayout(aState);
+
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (popupFrame) {
+ bool sizeToPopup = IsSizedToPopup(mContent, false);
+ popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
+ }
+
+ return rv;
+}
+
+#ifdef DEBUG_LAYOUT
+nsresult
+nsMenuFrame::SetXULDebug(nsBoxLayoutState& aState, bool aDebug)
+{
+ // see if our state matches the given debug state
+ bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
+ bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
+
+ // if it doesn't then tell each child below us the new debug state
+ if (debugChanged)
+ {
+ nsBoxFrame::SetXULDebug(aState, aDebug);
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (popupFrame)
+ SetXULDebug(aState, popupFrame, aDebug);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuFrame::SetXULDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug)
+{
+ if (!aList)
+ return NS_OK;
+
+ while (aList) {
+ if (aList->IsXULBoxFrame())
+ aList->SetXULDebug(aState, aDebug);
+
+ aList = aList->GetNextSibling();
+ }
+
+ return NS_OK;
+}
+#endif
+
+//
+// Enter
+//
+// Called when the user hits the <Enter>/<Return> keys or presses the
+// shortcut key. If this is a leaf item, the item's action will be executed.
+// In either case, do nothing if the item is disabled.
+//
+nsMenuFrame*
+nsMenuFrame::Enter(WidgetGUIEvent* aEvent)
+{
+ if (IsDisabled()) {
+#ifdef XP_WIN
+ // behavior on Windows - close the popup chain
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
+ if (popup)
+ pm->HidePopup(popup->GetContent(), true, true, true, false);
+ }
+ }
+#endif // #ifdef XP_WIN
+ // this menu item was disabled - exit
+ return nullptr;
+ }
+
+ if (!IsOpen()) {
+ // The enter key press applies to us.
+ nsMenuParent* menuParent = GetMenuParent();
+ if (!IsMenu() && menuParent)
+ Execute(aEvent); // Execute our event handler
+ else
+ return this;
+ }
+
+ return nullptr;
+}
+
+bool
+nsMenuFrame::IsOpen()
+{
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ return popupFrame && popupFrame->IsOpen();
+}
+
+bool
+nsMenuFrame::IsMenu()
+{
+ return mIsMenu;
+}
+
+nsMenuListType
+nsMenuFrame::GetParentMenuListType()
+{
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent && menuParent->IsMenu()) {
+ nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
+ nsIFrame* parentMenu = popupFrame->GetParent();
+ if (parentMenu) {
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
+ if (menulist) {
+ bool isEditable = false;
+ menulist->GetEditable(&isEditable);
+ return isEditable ? eEditableMenuList : eReadonlyMenuList;
+ }
+ }
+ }
+ return eNotMenuList;
+}
+
+nsresult
+nsMenuFrame::Notify(nsITimer* aTimer)
+{
+ // Our timer has fired.
+ if (aTimer == mOpenTimer.get()) {
+ mOpenTimer = nullptr;
+
+ nsMenuParent* menuParent = GetMenuParent();
+ if (!IsOpen() && menuParent) {
+ // make sure we didn't open a context menu in the meantime
+ // (i.e. the user right-clicked while hovering over a submenu).
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ if ((!pm->HasContextMenu(nullptr) || menuParent->IsContextMenu()) &&
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
+ nsGkAtoms::_true, eCaseMatters)) {
+ OpenMenu(false);
+ }
+ }
+ }
+ } else if (aTimer == mBlinkTimer) {
+ switch (mBlinkState++) {
+ case 0:
+ NS_ASSERTION(false, "Blink timer fired while not blinking");
+ StopBlinking();
+ break;
+ case 1:
+ {
+ // Turn the highlight back on and wait for a while before closing the menu.
+ nsWeakFrame weakFrame(this);
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
+ NS_LITERAL_STRING("true"), true);
+ if (weakFrame.IsAlive()) {
+ aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+ break;
+ default: {
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent) {
+ menuParent->LockMenuUntilClosed(false);
+ }
+ PassMenuCommandEventToPopupManager();
+ StopBlinking();
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsMenuFrame::IsDisabled()
+{
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+void
+nsMenuFrame::UpdateMenuType()
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ strings, eCaseMatters)) {
+ case 0: mType = eMenuType_Checkbox; break;
+ case 1:
+ mType = eMenuType_Radio;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName);
+ break;
+
+ default:
+ if (mType != eMenuType_Normal) {
+ nsWeakFrame weakFrame(this);
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+ }
+ mType = eMenuType_Normal;
+ break;
+ }
+ UpdateMenuSpecialState();
+}
+
+/* update checked-ness for type="checkbox" and type="radio" */
+void
+nsMenuFrame::UpdateMenuSpecialState()
+{
+ bool newChecked =
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters);
+ if (newChecked == mChecked) {
+ /* checked state didn't change */
+
+ if (mType != eMenuType_Radio)
+ return; // only Radio possibly cares about other kinds of change
+
+ if (!mChecked || mGroupName.IsEmpty())
+ return; // no interesting change
+ } else {
+ mChecked = newChecked;
+ if (mType != eMenuType_Radio || !mChecked)
+ /*
+ * Unchecking something requires no further changes, and only
+ * menuRadio has to do additional work when checked.
+ */
+ return;
+ }
+
+ /*
+ * If we get this far, we're type=radio, and:
+ * - our name= changed, or
+ * - we went from checked="false" to checked="true"
+ */
+
+ /*
+ * Behavioural note:
+ * If we're checked and renamed _into_ an existing radio group, we are
+ * made the new checked item, and we unselect the previous one.
+ *
+ * The only other reasonable behaviour would be to check for another selected
+ * item in that group. If found, unselect ourselves, otherwise we're the
+ * selected item. That, however, would be a lot more work, and I don't think
+ * it's better at all.
+ */
+
+ /* walk siblings, looking for the other checked item with the same name */
+ // get the first sibling in this menu popup. This frame may be it, and if we're
+ // being called at creation time, this frame isn't yet in the parent's child list.
+ // All I'm saying is that this may fail, but it's most likely alright.
+ nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(GetParent(), nullptr, true);
+ nsIFrame* sib = firstMenuItem;
+ while (sib) {
+ nsMenuFrame* menu = do_QueryFrame(sib);
+ if (sib != this) {
+ if (menu && menu->GetMenuType() == eMenuType_Radio &&
+ menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) {
+ /* uncheck the old item */
+ sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ true);
+ /* XXX in DEBUG, check to make sure that there aren't two checked items */
+ return;
+ }
+ }
+ sib = nsXULPopupManager::GetNextMenuItem(GetParent(), menu, true);
+ if (sib == firstMenuItem) {
+ break;
+ }
+ }
+}
+
+void
+nsMenuFrame::BuildAcceleratorText(bool aNotify)
+{
+ nsAutoString accelText;
+
+ if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText);
+ if (!accelText.IsEmpty())
+ return;
+ }
+ // accelText is definitely empty here.
+
+ // Now we're going to compute the accelerator text, so remember that we did.
+ AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
+
+ // If anything below fails, just leave the accelerator text blank.
+ nsWeakFrame weakFrame(this);
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // See if we have a key node and use that instead.
+ nsAutoString keyValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
+ if (keyValue.IsEmpty())
+ return;
+
+ // Turn the document into a DOM document so we can use getElementById
+ nsIDocument *document = mContent->GetUncomposedDoc();
+ if (!document)
+ return;
+
+ //XXXsmaug If mContent is in shadow dom, should we use
+ // ShadowRoot::GetElementById()?
+ nsIContent *keyElement = document->GetElementById(keyValue);
+ if (!keyElement) {
+#ifdef DEBUG
+ nsAutoString label;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
+ nsAutoString msg = NS_LITERAL_STRING("Key '") +
+ keyValue +
+ NS_LITERAL_STRING("' of menu item '") +
+ label +
+ NS_LITERAL_STRING("' could not be found");
+ NS_WARNING(NS_ConvertUTF16toUTF8(msg).get());
+#endif
+ return;
+ }
+
+ // get the string to display as accelerator text
+ // check the key element's attributes in this order:
+ // |keytext|, |key|, |keycode|
+ nsAutoString accelString;
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString);
+
+ if (accelString.IsEmpty()) {
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString);
+
+ if (!accelString.IsEmpty()) {
+ ToUpperCase(accelString);
+ } else {
+ nsAutoString keyCode;
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode);
+ ToUpperCase(keyCode);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
+ getter_AddRefs(bundle));
+
+ if (NS_SUCCEEDED(rv) && bundle) {
+ nsXPIDLString keyName;
+ rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
+ if (keyName)
+ accelString = keyName;
+ }
+ }
+
+ // nothing usable found, bail
+ if (accelString.IsEmpty())
+ return;
+ }
+ }
+
+ nsAutoString modifiers;
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
+
+ char* str = ToNewCString(modifiers);
+ char* newStr;
+ char* token = nsCRT::strtok(str, ", \t", &newStr);
+
+ nsAutoString shiftText;
+ nsAutoString altText;
+ nsAutoString metaText;
+ nsAutoString controlText;
+ nsAutoString osText;
+ nsAutoString modifierSeparator;
+
+ nsContentUtils::GetShiftText(shiftText);
+ nsContentUtils::GetAltText(altText);
+ nsContentUtils::GetMetaText(metaText);
+ nsContentUtils::GetControlText(controlText);
+ nsContentUtils::GetOSText(osText);
+ nsContentUtils::GetModifierSeparatorText(modifierSeparator);
+
+ while (token) {
+
+ if (PL_strcmp(token, "shift") == 0)
+ accelText += shiftText;
+ else if (PL_strcmp(token, "alt") == 0)
+ accelText += altText;
+ else if (PL_strcmp(token, "meta") == 0)
+ accelText += metaText;
+ else if (PL_strcmp(token, "os") == 0)
+ accelText += osText;
+ else if (PL_strcmp(token, "control") == 0)
+ accelText += controlText;
+ else if (PL_strcmp(token, "accel") == 0) {
+ switch (WidgetInputEvent::AccelModifier()) {
+ case MODIFIER_META:
+ accelText += metaText;
+ break;
+ case MODIFIER_OS:
+ accelText += osText;
+ break;
+ case MODIFIER_ALT:
+ accelText += altText;
+ break;
+ case MODIFIER_CONTROL:
+ accelText += controlText;
+ break;
+ default:
+ MOZ_CRASH(
+ "Handle the new result of WidgetInputEvent::AccelModifier()");
+ break;
+ }
+ }
+
+ accelText += modifierSeparator;
+
+ token = nsCRT::strtok(newStr, ", \t", &newStr);
+ }
+
+ free(str);
+
+ accelText += accelString;
+
+ mIgnoreAccelTextChange = true;
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ mIgnoreAccelTextChange = false;
+}
+
+void
+nsMenuFrame::Execute(WidgetGUIEvent* aEvent)
+{
+ // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
+ bool needToFlipChecked = false;
+ if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
+ needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters);
+ }
+
+ nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
+ if (sound)
+ sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
+
+ StartBlinking(aEvent, needToFlipChecked);
+}
+
+bool
+nsMenuFrame::ShouldBlink()
+{
+ int32_t shouldBlink =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0);
+ if (!shouldBlink)
+ return false;
+
+ // Don't blink in editable menulists.
+ if (GetParentMenuListType() == eEditableMenuList)
+ return false;
+
+ return true;
+}
+
+void
+nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked)
+{
+ StopBlinking();
+ CreateMenuCommandEvent(aEvent, aFlipChecked);
+
+ if (!ShouldBlink()) {
+ PassMenuCommandEventToPopupManager();
+ return;
+ }
+
+ // Blink off.
+ nsWeakFrame weakFrame(this);
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
+ if (!weakFrame.IsAlive())
+ return;
+
+ nsMenuParent* menuParent = GetMenuParent();
+ if (menuParent) {
+ // Make this menu ignore events from now on.
+ menuParent->LockMenuUntilClosed(true);
+ }
+
+ // Set up a timer to blink back on.
+ mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
+ mBlinkState = 1;
+}
+
+void
+nsMenuFrame::StopBlinking()
+{
+ mBlinkState = 0;
+ if (mBlinkTimer) {
+ mBlinkTimer->Cancel();
+ mBlinkTimer = nullptr;
+ }
+ mDelayedMenuCommandEvent = nullptr;
+}
+
+void
+nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked)
+{
+ // Create a trusted event if the triggering event was trusted, or if
+ // we're called from chrome code (since at least one of our caller
+ // passes in a null event).
+ bool isTrusted = aEvent ? aEvent->IsTrusted() :
+ nsContentUtils::IsCallerChrome();
+
+ bool shift = false, control = false, alt = false, meta = false;
+ WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr;
+ if (inputEvent) {
+ shift = inputEvent->IsShift();
+ control = inputEvent->IsControl();
+ alt = inputEvent->IsAlt();
+ meta = inputEvent->IsMeta();
+ }
+
+ // Because the command event is firing asynchronously, a flag is needed to
+ // indicate whether user input is being handled. This ensures that a popup
+ // window won't get blocked.
+ bool userinput = EventStateManager::IsHandlingUserInput();
+
+ mDelayedMenuCommandEvent =
+ new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta,
+ userinput, aFlipChecked);
+}
+
+void
+nsMenuFrame::PassMenuCommandEventToPopupManager()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ nsMenuParent* menuParent = GetMenuParent();
+ if (pm && menuParent && mDelayedMenuCommandEvent) {
+ pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent);
+ }
+ mDelayedMenuCommandEvent = nullptr;
+}
+
+void
+nsMenuFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ nsFrameList* popupList = GetPopupList();
+ if (popupList && popupList->FirstChild() == aOldFrame) {
+ popupList->RemoveFirstChild();
+ aOldFrame->Destroy();
+ DestroyPopupList();
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ return;
+ }
+ nsBoxFrame::RemoveFrame(aListID, aOldFrame);
+}
+
+void
+nsMenuFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
+ SetPopupFrame(aFrameList);
+ if (HasPopup()) {
+#ifdef DEBUG_LAYOUT
+ nsBoxLayoutState state(PresContext());
+ SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
+#endif
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+
+ if (aFrameList.IsEmpty())
+ return;
+
+ if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
+ aPrevFrame = nullptr;
+ }
+
+ nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+}
+
+void
+nsMenuFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
+ SetPopupFrame(aFrameList);
+ if (HasPopup()) {
+
+#ifdef DEBUG_LAYOUT
+ nsBoxLayoutState state(PresContext());
+ SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
+#endif
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+
+ if (aFrameList.IsEmpty())
+ return;
+
+ nsBoxFrame::AppendFrames(aListID, aFrameList);
+}
+
+bool
+nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
+{
+ if (!IsXULCollapsed()) {
+ bool widthSet, heightSet;
+ nsSize tmpSize(-1, 0);
+ nsIFrame::AddXULPrefSize(this, tmpSize, widthSet, heightSet);
+ if (!widthSet && GetXULFlex() == 0) {
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (!popupFrame)
+ return false;
+ tmpSize = popupFrame->GetXULPrefSize(aState);
+
+ // Produce a size such that:
+ // (1) the menu and its popup can be the same width
+ // (2) there's enough room in the menu for the content and its
+ // border-padding
+ // (3) there's enough room in the popup for the content and its
+ // scrollbar
+ nsMargin borderPadding;
+ GetXULBorderAndPadding(borderPadding);
+
+ // if there is a scroll frame, add the desired width of the scrollbar as well
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->PrincipalChildList().FirstChild());
+ nscoord scrollbarWidth = 0;
+ if (scrollFrame) {
+ scrollbarWidth =
+ scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
+ }
+
+ aSize.width =
+ tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsSize
+nsMenuFrame::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ nsSize size = nsBoxFrame::GetXULPrefSize(aState);
+ DISPLAY_PREF_SIZE(this, size);
+
+ // If we are using sizetopopup="always" then
+ // nsBoxFrame will already have enforced the minimum size
+ if (!IsSizedToPopup(mContent, true) &&
+ IsSizedToPopup(mContent, false) &&
+ SizeToPopup(aState, size)) {
+ // We now need to ensure that size is within the min - max range.
+ nsSize minSize = nsBoxFrame::GetXULMinSize(aState);
+ nsSize maxSize = GetXULMaxSize(aState);
+ size = BoundsCheck(minSize, size, maxSize);
+ }
+
+ return size;
+}
+
+NS_IMETHODIMP
+nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
+{
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (!popupFrame)
+ return NS_ERROR_FAILURE;
+
+ nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
+ if (!menuFrame) {
+ *aResult = nullptr;
+ }
+ else {
+ nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent()));
+ *aResult = elt;
+ NS_IF_ADDREF(*aResult);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
+{
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (!popupFrame)
+ return NS_ERROR_FAILURE;
+
+ if (!aChild) {
+ // Remove the current selection
+ popupFrame->ChangeMenuItem(nullptr, false, false);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
+
+ nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame());
+ if (menu)
+ popupFrame->ChangeMenuItem(menu, false, false);
+ return NS_OK;
+}
+
+nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame()
+{
+ nsMenuPopupFrame* popupFrame = GetPopup();
+ if (!popupFrame)
+ return nullptr;
+ nsIFrame* childFrame = popupFrame->PrincipalChildList().FirstChild();
+ if (childFrame)
+ return popupFrame->GetScrollFrame(childFrame);
+ return nullptr;
+}
+
+// nsMenuTimerMediator implementation.
+NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback)
+
+/**
+ * Constructs a wrapper around an nsMenuFrame.
+ * @param aFrame nsMenuFrame to create a wrapper around.
+ */
+nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) :
+ mFrame(aFrame)
+{
+ NS_ASSERTION(mFrame, "Must have frame");
+}
+
+nsMenuTimerMediator::~nsMenuTimerMediator()
+{
+}
+
+/**
+ * Delegates the notification to the contained frame if it has not been destroyed.
+ * @param aTimer Timer which initiated the callback.
+ * @return NS_ERROR_FAILURE if the frame has been destroyed.
+ */
+NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
+{
+ if (!mFrame)
+ return NS_ERROR_FAILURE;
+
+ return mFrame->Notify(aTimer);
+}
+
+/**
+ * Clear the pointer to the contained nsMenuFrame. This should be called
+ * when the contained nsMenuFrame is destroyed.
+ */
+void nsMenuTimerMediator::ClearFrame()
+{
+ mFrame = nullptr;
+}
diff --git a/layout/xul/nsMenuFrame.h b/layout/xul/nsMenuFrame.h
new file mode 100644
index 000000000..1941ec69e
--- /dev/null
+++ b/layout/xul/nsMenuFrame.h
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsMenuFrame
+//
+
+#ifndef nsMenuFrame_h__
+#define nsMenuFrame_h__
+
+#include "nsIAtom.h"
+#include "nsCOMPtr.h"
+
+#include "nsBoxFrame.h"
+#include "nsFrameList.h"
+#include "nsGkAtoms.h"
+#include "nsMenuParent.h"
+#include "nsXULPopupManager.h"
+#include "nsITimer.h"
+#include "mozilla/Attributes.h"
+
+nsIFrame* NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsIContent;
+
+#define NS_STATE_ACCELTEXT_IS_DERIVED NS_STATE_BOX_CHILD_RESERVED
+
+// the type of menuitem
+enum nsMenuType {
+ // a normal menuitem where a command is carried out when activated
+ eMenuType_Normal = 0,
+ // a menuitem with a checkmark that toggles when activated
+ eMenuType_Checkbox = 1,
+ // a radio menuitem where only one of it and its siblings with the same
+ // name attribute can be checked at a time
+ eMenuType_Radio = 2
+};
+
+enum nsMenuListType {
+ eNotMenuList, // not a menulist
+ eReadonlyMenuList, // <menulist/>
+ eEditableMenuList // <menulist editable="true"/>
+};
+
+class nsMenuFrame;
+
+/**
+ * nsMenuTimerMediator is a wrapper around an nsMenuFrame which can be safely
+ * passed to timers. The class is reference counted unlike the underlying
+ * nsMenuFrame, so that it will exist as long as the timer holds a reference
+ * to it. The callback is delegated to the contained nsMenuFrame as long as
+ * the contained nsMenuFrame has not been destroyed.
+ */
+class nsMenuTimerMediator final : public nsITimerCallback
+{
+public:
+ explicit nsMenuTimerMediator(nsMenuFrame* aFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ void ClearFrame();
+
+private:
+ ~nsMenuTimerMediator();
+
+ // Pointer to the wrapped frame.
+ nsMenuFrame* mFrame;
+};
+
+class nsMenuFrame final : public nsBoxFrame
+{
+public:
+ explicit nsMenuFrame(nsStyleContext* aContext);
+
+ NS_DECL_QUERYFRAME_TARGET(nsMenuFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+#ifdef DEBUG_LAYOUT
+ virtual nsresult SetXULDebug(nsBoxLayoutState& aState, bool aDebug) override;
+#endif
+
+ // The following methods are all overridden so that the menupopup
+ // can be stored in a separate list, so that it doesn't impact reflow of the
+ // actual menu item at all.
+ virtual const nsFrameList& GetChildList(ChildListID aList) const override;
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ // Overridden to prevent events from going to children of the menu.
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ // this method can destroy the frame
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual nsIAtom* GetType() const override { return nsGkAtoms::menuFrame; }
+
+ NS_IMETHOD SelectMenu(bool aActivateFlag);
+
+ virtual nsIScrollableFrame* GetScrollTargetFrame() override;
+
+ // Retrieve the element that the menu should be anchored to. By default this is
+ // the menu itself. However, the anchor attribute may refer to the value of an
+ // anonid within the menu's binding, or, if not found, the id of an element in
+ // the document.
+ nsIContent* GetAnchor();
+
+ /**
+ * NOTE: OpenMenu will open the menu asynchronously.
+ */
+ void OpenMenu(bool aSelectFirstItem);
+ // CloseMenu closes the menu asynchronously
+ void CloseMenu(bool aDeselectMenu);
+
+ bool IsChecked() { return mChecked; }
+
+ NS_IMETHOD GetActiveChild(nsIDOMElement** aResult);
+ NS_IMETHOD SetActiveChild(nsIDOMElement* aChild);
+
+ // called when the Enter key is pressed while the menuitem is the current
+ // one in its parent popup. This will carry out the command attached to
+ // the menuitem. If the menu should be opened, this frame will be returned,
+ // otherwise null will be returned.
+ nsMenuFrame* Enter(mozilla::WidgetGUIEvent* aEvent);
+
+ // Return the nearest menu bar or menupopup ancestor frame.
+ nsMenuParent* GetMenuParent() const;
+
+ const nsAString& GetRadioGroupName() { return mGroupName; }
+ nsMenuType GetMenuType() { return mType; }
+ nsMenuPopupFrame* GetPopup();
+
+ /**
+ * @return true if this frame has a popup child frame.
+ */
+ bool HasPopup() const
+ {
+ return (GetStateBits() & NS_STATE_MENU_HAS_POPUP_LIST) != 0;
+ }
+
+
+ // nsMenuFrame methods
+
+ bool IsOnMenuBar() const
+ {
+ nsMenuParent* menuParent = GetMenuParent();
+ return menuParent && menuParent->IsMenuBar();
+ }
+ bool IsOnActiveMenuBar() const
+ {
+ nsMenuParent* menuParent = GetMenuParent();
+ return menuParent && menuParent->IsMenuBar() && menuParent->IsActive();
+ }
+ virtual bool IsOpen();
+ virtual bool IsMenu();
+ nsMenuListType GetParentMenuListType();
+ bool IsDisabled();
+ void ToggleMenuState();
+
+ // indiciate that the menu's popup has just been opened, so that the menu
+ // can update its open state. This method modifies the open attribute on
+ // the menu, so the frames could be gone after this call.
+ void PopupOpened();
+ // indiciate that the menu's popup has just been closed, so that the menu
+ // can update its open state. The menu should be unhighlighted if
+ // aDeselectedMenu is true. This method modifies the open attribute on
+ // the menu, so the frames could be gone after this call.
+ void PopupClosed(bool aDeselectMenu);
+
+ // returns true if this is a menu on another menu popup. A menu is a submenu
+ // if it has a parent popup or menupopup.
+ bool IsOnMenu() const
+ {
+ nsMenuParent* menuParent = GetMenuParent();
+ return menuParent && menuParent->IsMenu();
+ }
+ void SetIsMenu(bool aIsMenu) { mIsMenu = aIsMenu; }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("Menu"), aResult);
+ }
+#endif
+
+ static bool IsSizedToPopup(nsIContent* aContent, bool aRequireAlways);
+
+protected:
+ friend class nsMenuTimerMediator;
+ friend class nsASyncMenuInitialization;
+ friend class nsMenuAttributeChangedEvent;
+
+ /**
+ * Initialize the popup list to the first popup frame within
+ * aChildList. Removes the popup, if any, from aChildList.
+ */
+ void SetPopupFrame(nsFrameList& aChildList);
+
+ /**
+ * Get the popup frame list from the frame property.
+ * @return the property value if it exists, nullptr otherwise.
+ */
+ nsFrameList* GetPopupList() const;
+
+ /**
+ * Destroy the popup list property. The list must exist and be empty.
+ */
+ void DestroyPopupList();
+
+ // Update the menu's type (normal, checkbox, radio).
+ // This method can destroy the frame.
+ void UpdateMenuType();
+ // Update the checked state of the menu, and for radios, clear any other
+ // checked items. This method can destroy the frame.
+ void UpdateMenuSpecialState();
+
+ // Examines the key node and builds the accelerator.
+ void BuildAcceleratorText(bool aNotify);
+
+ // Called to execute our command handler. This method can destroy the frame.
+ void Execute(mozilla::WidgetGUIEvent *aEvent);
+
+ // This method can destroy the frame
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+ virtual ~nsMenuFrame() { }
+
+ bool SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize);
+
+ bool ShouldBlink();
+ void StartBlinking(mozilla::WidgetGUIEvent* aEvent, bool aFlipChecked);
+ void StopBlinking();
+ void CreateMenuCommandEvent(mozilla::WidgetGUIEvent* aEvent,
+ bool aFlipChecked);
+ void PassMenuCommandEventToPopupManager();
+
+protected:
+#ifdef DEBUG_LAYOUT
+ nsresult SetXULDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug);
+#endif
+ nsresult Notify(nsITimer* aTimer);
+
+ bool mIsMenu; // Whether or not we can even have children or not.
+ bool mChecked; // are we checked?
+ bool mIgnoreAccelTextChange; // temporarily set while determining the accelerator key
+ nsMenuType mType;
+
+ // Reference to the mediator which wraps this frame.
+ RefPtr<nsMenuTimerMediator> mTimerMediator;
+
+ nsCOMPtr<nsITimer> mOpenTimer;
+ nsCOMPtr<nsITimer> mBlinkTimer;
+
+ uint8_t mBlinkState; // 0: not blinking, 1: off, 2: on
+ RefPtr<nsXULMenuCommandEvent> mDelayedMenuCommandEvent;
+
+ nsString mGroupName;
+
+}; // class nsMenuFrame
+
+#endif
diff --git a/layout/xul/nsMenuParent.h b/layout/xul/nsMenuParent.h
new file mode 100644
index 000000000..19c2dcd7c
--- /dev/null
+++ b/layout/xul/nsMenuParent.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuParent_h___
+#define nsMenuParent_h___
+
+class nsMenuFrame;
+
+/*
+ * nsMenuParent is an interface implemented by nsMenuBarFrame and nsMenuPopupFrame
+ * as both serve as parent frames to nsMenuFrame.
+ *
+ * Don't implement this interface on other classes unless you also fix up references,
+ * as this interface is directly cast to and from nsMenuBarFrame and nsMenuPopupFrame.
+ */
+
+class nsMenuParent {
+
+public:
+ // returns the menu frame of the currently active item within the menu
+ virtual nsMenuFrame *GetCurrentMenuItem() = 0;
+ // sets the currently active menu frame.
+ NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) = 0;
+ // indicate that the current menu frame is being destroyed, so clear the
+ // current menu item
+ virtual void CurrentMenuIsBeingDestroyed() = 0;
+ // deselects the current item and closes its popup if any, then selects the
+ // new item aMenuItem. For a menubar, if another menu is already open, the
+ // new menu aMenuItem is opened. In this case, if aSelectFirstItem is true,
+ // select the first item in it. For menupopups, the menu is not opened and
+ // the aSelectFirstItem argument is not used. The aFromKey argument indicates
+ // that the keyboard was used to navigate to the new menu item.
+ NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem,
+ bool aSelectFirstItem,
+ bool aFromKey) = 0;
+
+ // returns true if the menupopup is open. For menubars, returns false.
+ virtual bool IsOpen() = 0;
+ // returns true if the menubar is currently active. For menupopups, returns false.
+ virtual bool IsActive() = 0;
+ // returns true if this is a menubar. If false, it is a popup
+ virtual bool IsMenuBar() = 0;
+ // returns true if this is a menu, which has a tag of menupopup or popup.
+ // Otherwise, this returns false
+ virtual bool IsMenu() = 0;
+ // returns true if this is a context menu
+ virtual bool IsContextMenu() = 0;
+
+ // indicate that the menubar should become active or inactive
+ NS_IMETHOD SetActive(bool aActiveFlag) = 0;
+
+ // notify that the menu has been closed and any active state should be
+ // cleared. This should return true if the menu should be deselected
+ // by the caller.
+ virtual bool MenuClosed() = 0;
+
+ // Lock this menu and its parents until they're closed or unlocked.
+ // A menu being "locked" means that all events inside it that would change the
+ // selected menu item should be ignored.
+ // This is used when closing the popup is delayed because of a blink or fade
+ // animation.
+ virtual void LockMenuUntilClosed(bool aLock) = 0;
+ virtual bool IsMenuLocked() = 0;
+};
+
+#endif
+
diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp
new file mode 100644
index 000000000..6e706e49c
--- /dev/null
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -0,0 +1,2442 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuPopupFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIAtom.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsCSSRendering.h"
+#include "nsNameSpaceManager.h"
+#include "nsViewManager.h"
+#include "nsWidgetsCID.h"
+#include "nsMenuFrame.h"
+#include "nsMenuBarFrame.h"
+#include "nsPopupSetFrame.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMScreen.h"
+#include "nsIPresShell.h"
+#include "nsFrameManager.h"
+#include "nsIDocument.h"
+#include "nsRect.h"
+#include "nsIComponentManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsIScrollableFrame.h"
+#include "nsIRootBox.h"
+#include "nsIDocShell.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsPIWindowRoot.h"
+#include "nsIReflowCallback.h"
+#include "nsBindingManager.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIBaseWindow.h"
+#include "nsISound.h"
+#include "nsIScreenManager.h"
+#include "nsIServiceManager.h"
+#include "nsThemeConstants.h"
+#include "nsTransitionManager.h"
+#include "nsDisplayList.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PopupBoxObject.h"
+#include <algorithm>
+
+using namespace mozilla;
+using mozilla::dom::PopupBoxObject;
+
+int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
+
+DOMTimeStamp nsMenuPopupFrame::sLastKeyTime = 0;
+
+// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
+// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
+// need to find a good place to put them together.
+// if someone changes one, please also change the other.
+uint32_t nsMenuPopupFrame::sTimeoutOfIncrementalSearch = 1000;
+
+const char* kPrefIncrementalSearchTimeout =
+ "ui.menu.incremental_search.timeout";
+
+// NS_NewMenuPopupFrame
+//
+// Wrapper for creating a new menu popup container
+//
+nsIFrame*
+NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMenuPopupFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
+
+NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
+ NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+//
+// nsMenuPopupFrame ctor
+//
+nsMenuPopupFrame::nsMenuPopupFrame(nsStyleContext* aContext)
+ :nsBoxFrame(aContext),
+ mCurrentMenu(nullptr),
+ mPrefSize(-1, -1),
+ mLastClientOffset(0, 0),
+ mPopupType(ePopupTypePanel),
+ mPopupState(ePopupClosed),
+ mPopupAlignment(POPUPALIGNMENT_NONE),
+ mPopupAnchor(POPUPALIGNMENT_NONE),
+ mPosition(POPUPPOSITION_UNKNOWN),
+ mConsumeRollupEvent(PopupBoxObject::ROLLUP_DEFAULT),
+ mFlip(FlipType_Default),
+ mIsOpenChanged(false),
+ mIsContextMenu(false),
+ mAdjustOffsetForContextMenu(false),
+ mGeneratedChildren(false),
+ mMenuCanOverlapOSBar(false),
+ mShouldAutoPosition(true),
+ mInContentShell(true),
+ mIsMenuLocked(false),
+ mMouseTransparent(false),
+ mHFlip(false),
+ mVFlip(false),
+ mAnchorType(MenuPopupAnchorType_Node)
+{
+ // the preference name is backwards here. True means that the 'top' level is
+ // the default, and false means that the 'parent' level is the default.
+ if (sDefaultLevelIsTop >= 0)
+ return;
+ sDefaultLevelIsTop =
+ Preferences::GetBool("ui.panel.default_level_parent", false);
+ Preferences::AddUintVarCache(&sTimeoutOfIncrementalSearch,
+ kPrefIncrementalSearchTimeout, 1000);
+} // ctor
+
+void
+nsMenuPopupFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
+ // look&feel object
+ mMenuCanOverlapOSBar =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0;
+
+ CreatePopupView();
+
+ // XXX Hack. The popup's view should float above all other views,
+ // so we use the nsView::SetFloating() to tell the view manager
+ // about that constraint.
+ nsView* ourView = GetView();
+ nsViewManager* viewManager = ourView->GetViewManager();
+ viewManager->SetViewFloating(ourView, true);
+
+ mPopupType = ePopupTypePanel;
+ nsIDocument* doc = aContent->OwnerDoc();
+ int32_t namespaceID;
+ nsCOMPtr<nsIAtom> tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID);
+ if (namespaceID == kNameSpaceID_XUL) {
+ if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup)
+ mPopupType = ePopupTypeMenu;
+ else if (tag == nsGkAtoms::tooltip)
+ mPopupType = ePopupTypeTooltip;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
+ if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ mInContentShell = false;
+ }
+
+ // To improve performance, create the widget for the popup only if it is not
+ // a leaf. Leaf popups such as menus will create their widgets later when
+ // the popup opens.
+ if (!IsLeaf() && !ourView->HasWidget()) {
+ CreateWidgetForView(ourView);
+ }
+
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
+ aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ nsIRootBox* rootBox =
+ nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox) {
+ rootBox->SetDefaultTooltip(aContent);
+ }
+ }
+
+ AddStateBits(NS_FRAME_IN_POPUP);
+}
+
+bool
+nsMenuPopupFrame::IsNoAutoHide() const
+{
+ // Panels with noautohide="true" don't hide when the mouse is clicked
+ // outside of them, or when another application is made active. Non-autohide
+ // panels cannot be used in content windows.
+ return (!mInContentShell && mPopupType == ePopupTypePanel &&
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide,
+ nsGkAtoms::_true, eIgnoreCase));
+}
+
+nsPopupLevel
+nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const
+{
+ // The popup level is determined as follows, in this order:
+ // 1. non-panels (menus and tooltips) are always topmost
+ // 2. any specified level attribute
+ // 3. if a titlebar attribute is set, use the 'floating' level
+ // 4. if this is a noautohide panel, use the 'parent' level
+ // 5. use the platform-specific default level
+
+ // If this is not a panel, this is always a top-most popup.
+ if (mPopupType != ePopupTypePanel)
+ return ePopupLevelTop;
+
+ // If the level attribute has been set, use that.
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level,
+ strings, eCaseMatters)) {
+ case 0:
+ return ePopupLevelTop;
+ case 1:
+ return ePopupLevelParent;
+ case 2:
+ return ePopupLevelFloating;
+ }
+
+ // Panels with titlebars most likely want to be floating popups.
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar))
+ return ePopupLevelFloating;
+
+ // If this panel is a noautohide panel, the default is the parent level.
+ if (aIsNoAutoHide)
+ return ePopupLevelParent;
+
+ // Otherwise, the result depends on the platform.
+ return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent;
+}
+
+void
+nsMenuPopupFrame::EnsureWidget()
+{
+ nsView* ourView = GetView();
+ if (!ourView->HasWidget()) {
+ NS_ASSERTION(!mGeneratedChildren && !PrincipalChildList().FirstChild(),
+ "Creating widget for MenuPopupFrame with children");
+ CreateWidgetForView(ourView);
+ }
+}
+
+nsresult
+nsMenuPopupFrame::CreateWidgetForView(nsView* aView)
+{
+ // Create a widget for ourselves.
+ nsWidgetInitData widgetData;
+ widgetData.mWindowType = eWindowType_popup;
+ widgetData.mBorderStyle = eBorderStyle_default;
+ widgetData.clipSiblings = true;
+ widgetData.mPopupHint = mPopupType;
+ widgetData.mNoAutoHide = IsNoAutoHide();
+
+ if (!mInContentShell) {
+ // A drag popup may be used for non-static translucent drag feedback
+ if (mPopupType == ePopupTypePanel &&
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::drag, eIgnoreCase)) {
+ widgetData.mIsDragPopup = true;
+ }
+
+ // If mousethrough="always" is set directly on the popup, then the widget
+ // should ignore mouse events, passing them through to the content behind.
+ mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS;
+ widgetData.mMouseTransparent = mMouseTransparent;
+ }
+
+ nsAutoString title;
+ if (mContent && widgetData.mNoAutoHide) {
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar,
+ nsGkAtoms::normal, eCaseMatters)) {
+ widgetData.mBorderStyle = eBorderStyle_title;
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close,
+ nsGkAtoms::_true, eCaseMatters)) {
+ widgetData.mBorderStyle =
+ static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close);
+ }
+ }
+ }
+
+ nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this);
+ nsIContent* parentContent = GetContent()->GetParent();
+ nsIAtom *tag = nullptr;
+ if (parentContent && parentContent->IsXULElement())
+ tag = parentContent->NodeInfo()->NameAtom();
+ widgetData.mSupportTranslucency = mode == eTransparencyTransparent;
+ widgetData.mDropShadow = !(mode == eTransparencyTransparent || tag == nsGkAtoms::menulist);
+ widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide);
+
+ // panels which have a parent level need a parent widget. This allows them to
+ // always appear in front of the parent window but behind other windows that
+ // should be in front of it.
+ nsCOMPtr<nsIWidget> parentWidget;
+ if (widgetData.mPopupLevel != ePopupLevelTop) {
+ nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
+ if (!dsti)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ dsti->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner));
+ if (baseWindow)
+ baseWindow->GetMainWidget(getter_AddRefs(parentWidget));
+ }
+
+ nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget,
+ true, true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsIWidget* widget = aView->GetWidget();
+ widget->SetTransparencyMode(mode);
+ widget->SetWindowShadowStyle(GetShadowStyle());
+
+ // most popups don't have a title so avoid setting the title if there isn't one
+ if (!title.IsEmpty()) {
+ widget->SetTitle(title);
+ }
+
+ return NS_OK;
+}
+
+uint8_t
+nsMenuPopupFrame::GetShadowStyle()
+{
+ uint8_t shadow = StyleUIReset()->mWindowShadow;
+ if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT)
+ return shadow;
+
+ switch (StyleDisplay()->mAppearance) {
+ case NS_THEME_TOOLTIP:
+ return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
+ case NS_THEME_MENUPOPUP:
+ return NS_STYLE_WINDOW_SHADOW_MENU;
+ }
+ return NS_STYLE_WINDOW_SHADOW_DEFAULT;
+}
+
+NS_IMETHODIMP nsXULPopupShownEvent::Run()
+{
+ nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
+ // Set the state to visible if the popup is still open.
+ if (popup && popup->IsOpen()) {
+ popup->SetPopupState(ePopupShown);
+ }
+
+ WidgetMouseEvent event(true, eXULPopupShown, nullptr,
+ WidgetMouseEvent::eReal);
+ return EventDispatcher::Dispatch(mPopup, mPresContext, &event);
+}
+
+NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
+ nsCOMPtr<nsIDOMEventTarget> eventTarget;
+ aEvent->GetTarget(getter_AddRefs(eventTarget));
+ // Ignore events not targeted at the popup itself (ie targeted at
+ // descendants):
+ if (!SameCOMIdentity(mPopup, eventTarget)) {
+ return NS_OK;
+ }
+ if (popup) {
+ // ResetPopupShownDispatcher will delete the reference to this, so keep
+ // another one until Run is finished.
+ RefPtr<nsXULPopupShownEvent> event = this;
+ // Only call Run if it the dispatcher was assigned. This avoids calling the
+ // Run method if the transitionend event fires multiple times.
+ if (popup->ClearPopupShownDispatcher()) {
+ return Run();
+ }
+ }
+
+ CancelListener();
+ return NS_OK;
+}
+
+void nsXULPopupShownEvent::CancelListener()
+{
+ mPopup->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent, Runnable, nsIDOMEventListener);
+
+void
+nsMenuPopupFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ // unless the list is empty, indicate that children have been generated.
+ if (aListID == kPrincipalList && aChildList.NotEmpty()) {
+ mGeneratedChildren = true;
+ }
+ nsBoxFrame::SetInitialChildList(aListID, aChildList);
+}
+
+bool
+nsMenuPopupFrame::IsLeaf() const
+{
+ if (mGeneratedChildren)
+ return false;
+
+ if (mPopupType != ePopupTypeMenu) {
+ // any panel with a type attribute, such as the autocomplete popup,
+ // is always generated right away.
+ return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type);
+ }
+
+ // menu popups generate their child frames lazily only when opened, so
+ // behave like a leaf frame. However, generate child frames normally if
+ // the parent menu has a sizetopopup attribute. In this case the size of
+ // the parent menu is dependent on the size of the popup, so the frames
+ // need to exist in order to calculate this size.
+ nsIContent* parentContent = mContent->GetParent();
+ return (parentContent &&
+ !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
+}
+
+void
+nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+ nsIFrame* aAnchor, bool aSizedToPopup)
+{
+ if (!mGeneratedChildren)
+ return;
+
+ SchedulePaint();
+
+ bool shouldPosition = true;
+ bool isOpen = IsOpen();
+ if (!isOpen) {
+ // if the popup is not open, only do layout while showing or if the menu
+ // is sized to the popup
+ shouldPosition = (mPopupState == ePopupShowing || mPopupState == ePopupPositioning);
+ if (!shouldPosition && !aSizedToPopup) {
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW);
+ return;
+ }
+ }
+
+ // if the popup has just been opened, make sure the scrolled window is at 0,0
+ // Don't scroll menulists as they will scroll to their selected item on their own.
+ if (mIsOpenChanged && !IsMenuList()) {
+ nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this));
+ if (scrollframe) {
+ nsWeakFrame weakFrame(this);
+ scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+ }
+
+ // get the preferred, minimum and maximum size. If the menu is sized to the
+ // popup, then the popup's width is the menu's width.
+ nsSize prefSize = GetXULPrefSize(aState);
+ nsSize minSize = GetXULMinSize(aState);
+ nsSize maxSize = GetXULMaxSize(aState);
+
+ if (aSizedToPopup) {
+ prefSize.width = aParentMenu->GetRect().width;
+ }
+ prefSize = BoundsCheck(minSize, prefSize, maxSize);
+
+ // if the size changed then set the bounds to be the preferred size
+ bool sizeChanged = (mPrefSize != prefSize);
+ if (sizeChanged) {
+ SetXULBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
+ mPrefSize = prefSize;
+ }
+
+ bool needCallback = false;
+ if (shouldPosition) {
+ SetPopupPosition(aAnchor, false, aSizedToPopup, mPopupState == ePopupPositioning);
+ needCallback = true;
+ }
+
+ nsRect bounds(GetRect());
+ XULLayout(aState);
+
+ // if the width or height changed, readjust the popup position. This is a
+ // special case for tooltips where the preferred height doesn't include the
+ // real height for its inline element, but does once it is laid out.
+ // This is bug 228673 which doesn't have a simple fix.
+ bool rePosition = shouldPosition && (mPosition == POPUPPOSITION_SELECTION);
+ if (!aParentMenu) {
+ nsSize newsize = GetSize();
+ if (newsize.width > bounds.width || newsize.height > bounds.height) {
+ // the size after layout was larger than the preferred size,
+ // so set the preferred size accordingly
+ mPrefSize = newsize;
+ if (isOpen) {
+ rePosition = true;
+ needCallback = true;
+ }
+ }
+ }
+
+ if (rePosition) {
+ SetPopupPosition(aAnchor, false, aSizedToPopup, false);
+ }
+
+ nsPresContext* pc = PresContext();
+ nsView* view = GetView();
+
+ if (sizeChanged) {
+ // If the size of the popup changed, apply any size constraints.
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ SetSizeConstraints(pc, widget, minSize, maxSize);
+ }
+ }
+
+ if (isOpen) {
+ nsViewManager* viewManager = view->GetViewManager();
+ nsRect rect = GetRect();
+ rect.x = rect.y = 0;
+ viewManager->ResizeView(view, rect);
+
+ if (mPopupState == ePopupOpening) {
+ mPopupState = ePopupVisible;
+ }
+
+ viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
+ nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0);
+ }
+
+ // finally, if the popup just opened, send a popupshown event
+ if (mIsOpenChanged) {
+ mIsOpenChanged = false;
+
+ // Make sure the current selection in a menulist is visible.
+ if (IsMenuList() && mCurrentMenu) {
+ EnsureMenuItemIsVisible(mCurrentMenu);
+ }
+
+#ifndef MOZ_WIDGET_GTK
+ // If the animate attribute is set to open, check for a transition and wait
+ // for it to finish before firing the popupshown event.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate,
+ nsGkAtoms::open, eCaseMatters) &&
+ nsLayoutUtils::HasCurrentTransitions(this)) {
+ mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
+ mPopupShownDispatcher, false, false);
+ return;
+ }
+#endif
+
+ // If there are no transitions, fire the popupshown event right away.
+ nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
+ NS_DispatchToCurrentThread(event);
+ }
+
+ if (needCallback && !mReflowCallbackData.mPosted) {
+ pc->PresShell()->PostReflowCallback(this);
+ mReflowCallbackData.MarkPosted(aAnchor, aSizedToPopup);
+ }
+}
+
+bool
+nsMenuPopupFrame::ReflowFinished()
+{
+ SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup, false);
+
+ mReflowCallbackData.Clear();
+
+ return false;
+}
+
+void
+nsMenuPopupFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackData.Clear();
+}
+
+bool
+nsMenuPopupFrame::IsMenuList()
+{
+ nsIFrame* parentMenu = GetParent();
+ if (!parentMenu) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
+ return menulist != nullptr;
+}
+
+nsIContent*
+nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame)
+{
+ while (aMenuPopupFrame) {
+ if (aMenuPopupFrame->mTriggerContent)
+ return aMenuPopupFrame->mTriggerContent;
+
+ // check up the menu hierarchy until a popup with a trigger node is found
+ nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent());
+ if (!menuFrame)
+ break;
+
+ nsMenuParent* parentPopup = menuFrame->GetMenuParent();
+ if (!parentPopup || !parentPopup->IsMenu())
+ break;
+
+ aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
+ }
+
+ return nullptr;
+}
+
+void
+nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
+ const nsAString& aAlign)
+{
+ mTriggerContent = nullptr;
+
+ if (aAnchor.EqualsLiteral("topleft"))
+ mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
+ else if (aAnchor.EqualsLiteral("topright"))
+ mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
+ else if (aAnchor.EqualsLiteral("bottomleft"))
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
+ else if (aAnchor.EqualsLiteral("bottomright"))
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
+ else if (aAnchor.EqualsLiteral("leftcenter"))
+ mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
+ else if (aAnchor.EqualsLiteral("rightcenter"))
+ mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
+ else if (aAnchor.EqualsLiteral("topcenter"))
+ mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
+ else if (aAnchor.EqualsLiteral("bottomcenter"))
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
+ else
+ mPopupAnchor = POPUPALIGNMENT_NONE;
+
+ if (aAlign.EqualsLiteral("topleft"))
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ else if (aAlign.EqualsLiteral("topright"))
+ mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
+ else if (aAlign.EqualsLiteral("bottomleft"))
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
+ else if (aAlign.EqualsLiteral("bottomright"))
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
+ else
+ mPopupAlignment = POPUPALIGNMENT_NONE;
+
+ mPosition = POPUPPOSITION_UNKNOWN;
+}
+
+void
+nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
+ nsIContent* aTriggerContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ MenuPopupAnchorType aAnchorType,
+ bool aAttributesOverride)
+{
+ EnsureWidget();
+
+ mPopupState = ePopupShowing;
+ mAnchorContent = aAnchorContent;
+ mTriggerContent = aTriggerContent;
+ mXPos = aXPos;
+ mYPos = aYPos;
+ mAdjustOffsetForContextMenu = false;
+ mVFlip = false;
+ mHFlip = false;
+ mAlignmentOffset = 0;
+
+ mAnchorType = aAnchorType;
+
+ // if aAttributesOverride is true, then the popupanchor, popupalign and
+ // position attributes on the <popup> override those values passed in.
+ // If false, those attributes are only used if the values passed in are empty
+ if (aAnchorContent || aAnchorType == MenuPopupAnchorType_Rect) {
+ nsAutoString anchor, align, position, flip;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip);
+
+ if (aAttributesOverride) {
+ // if the attributes are set, clear the offset position. Otherwise,
+ // the offset is used to adjust the position from the anchor point
+ if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty())
+ position.Assign(aPosition);
+ else
+ mXPos = mYPos = 0;
+ }
+ else if (!aPosition.IsEmpty()) {
+ position.Assign(aPosition);
+ }
+
+ if (flip.EqualsLiteral("none")) {
+ mFlip = FlipType_None;
+ } else if (flip.EqualsLiteral("both")) {
+ mFlip = FlipType_Both;
+ } else if (flip.EqualsLiteral("slide")) {
+ mFlip = FlipType_Slide;
+ }
+
+ position.CompressWhitespace();
+ int32_t spaceIdx = position.FindChar(' ');
+ // if there is a space in the position, assume it is the anchor and
+ // alignment as two separate tokens.
+ if (spaceIdx >= 0) {
+ InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
+ }
+ else if (position.EqualsLiteral("before_start")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
+ mPosition = POPUPPOSITION_BEFORESTART;
+ }
+ else if (position.EqualsLiteral("before_end")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
+ mPosition = POPUPPOSITION_BEFOREEND;
+ }
+ else if (position.EqualsLiteral("after_start")) {
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ mPosition = POPUPPOSITION_AFTERSTART;
+ }
+ else if (position.EqualsLiteral("after_end")) {
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
+ mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
+ mPosition = POPUPPOSITION_AFTEREND;
+ }
+ else if (position.EqualsLiteral("start_before")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
+ mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
+ mPosition = POPUPPOSITION_STARTBEFORE;
+ }
+ else if (position.EqualsLiteral("start_after")) {
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
+ mPosition = POPUPPOSITION_STARTAFTER;
+ }
+ else if (position.EqualsLiteral("end_before")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ mPosition = POPUPPOSITION_ENDBEFORE;
+ }
+ else if (position.EqualsLiteral("end_after")) {
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
+ mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
+ mPosition = POPUPPOSITION_ENDAFTER;
+ }
+ else if (position.EqualsLiteral("overlap")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ mPosition = POPUPPOSITION_OVERLAP;
+ }
+ else if (position.EqualsLiteral("after_pointer")) {
+ mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ mPosition = POPUPPOSITION_AFTERPOINTER;
+ // XXXndeakin this is supposed to anchor vertically after, but with the
+ // horizontal position as the mouse pointer.
+ mYPos += 21;
+ }
+ else if (position.EqualsLiteral("selection")) {
+ mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
+ mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
+ mPosition = POPUPPOSITION_SELECTION;
+ }
+ else {
+ InitPositionFromAnchorAlign(anchor, align);
+ }
+ }
+
+ mScreenRect = nsIntRect(-1, -1, 0, 0);
+
+ if (aAttributesOverride) {
+ // Use |left| and |top| dimension attributes to position the popup if
+ // present, as they may have been persisted.
+ nsAutoString left, top;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
+
+ nsresult err;
+ if (!left.IsEmpty()) {
+ int32_t x = left.ToInteger(&err);
+ if (NS_SUCCEEDED(err))
+ mScreenRect.x = x;
+ }
+ if (!top.IsEmpty()) {
+ int32_t y = top.ToInteger(&err);
+ if (NS_SUCCEEDED(err))
+ mScreenRect.y = y;
+ }
+ }
+}
+
+void
+nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu)
+{
+ EnsureWidget();
+
+ mPopupState = ePopupShowing;
+ mAnchorContent = nullptr;
+ mTriggerContent = aTriggerContent;
+ mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
+ mXPos = 0;
+ mYPos = 0;
+ mFlip = FlipType_Default;
+ mPopupAnchor = POPUPALIGNMENT_NONE;
+ mPopupAlignment = POPUPALIGNMENT_NONE;
+ mPosition = POPUPPOSITION_UNKNOWN;
+ mIsContextMenu = aIsContextMenu;
+ mAdjustOffsetForContextMenu = aIsContextMenu;
+ mAnchorType = MenuPopupAnchorType_Point;
+}
+
+void
+nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
+ const nsAString& aPosition,
+ const nsIntRect& aRect,
+ bool aAttributesOverride)
+{
+ InitializePopup(nullptr, aTriggerContent, aPosition, 0, 0,
+ MenuPopupAnchorType_Rect, aAttributesOverride);
+ mScreenRect = aRect;
+}
+
+void
+nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
+ nsAString& aAnchor,
+ nsAString& aAlign,
+ int32_t aXPos, int32_t aYPos)
+{
+ EnsureWidget();
+
+ mPopupState = ePopupShowing;
+ mAdjustOffsetForContextMenu = false;
+ mFlip = FlipType_Default;
+
+ // this popup opening function is provided for backwards compatibility
+ // only. It accepts either coordinates or an anchor and alignment value
+ // but doesn't use both together.
+ if (aXPos == -1 && aYPos == -1) {
+ mAnchorContent = aAnchorContent;
+ mAnchorType = MenuPopupAnchorType_Node;
+ mScreenRect = nsIntRect(-1, -1, 0, 0);
+ mXPos = 0;
+ mYPos = 0;
+ InitPositionFromAnchorAlign(aAnchor, aAlign);
+ }
+ else {
+ mAnchorContent = nullptr;
+ mAnchorType = MenuPopupAnchorType_Point;
+ mPopupAnchor = POPUPALIGNMENT_NONE;
+ mPopupAlignment = POPUPALIGNMENT_NONE;
+ mPosition = POPUPPOSITION_UNKNOWN;
+ mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
+ mXPos = aXPos;
+ mYPos = aYPos;
+ }
+}
+
+void
+nsMenuPopupFrame::ShowPopup(bool aIsContextMenu)
+{
+ mIsContextMenu = aIsContextMenu;
+
+ InvalidateFrameSubtree();
+
+ if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) {
+ mPopupState = ePopupOpening;
+ mIsOpenChanged = true;
+
+ // Clear mouse capture when a popup is opened.
+ if (mPopupType == ePopupTypeMenu) {
+ EventStateManager* activeESM =
+ static_cast<EventStateManager*>(
+ EventStateManager::GetActiveEventStateManager());
+ if (activeESM) {
+ EventStateManager::ClearGlobalActiveContent(activeESM);
+ }
+
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+
+ nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
+ if (menuFrame) {
+ nsWeakFrame weakFrame(this);
+ menuFrame->PopupOpened();
+ if (!weakFrame.IsAlive())
+ return;
+ }
+
+ // do we need an actual reflow here?
+ // is SetPopupPosition all that is needed?
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ if (mPopupType == ePopupTypeMenu) {
+ nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
+ if (sound)
+ sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
+ }
+ }
+
+ mShouldAutoPosition = true;
+}
+
+void
+nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState)
+{
+ NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
+ "popup being set to unexpected state");
+
+ ClearPopupShownDispatcher();
+
+ // don't hide the popup when it isn't open
+ if (mPopupState == ePopupClosed || mPopupState == ePopupShowing ||
+ mPopupState == ePopupPositioning)
+ return;
+
+ // clear the trigger content if the popup is being closed. But don't clear
+ // it if the popup is just being made invisible as a popuphiding or command
+ // event may want to retrieve it.
+ if (aNewState == ePopupClosed) {
+ // if the popup had a trigger node set, clear the global window popup node
+ // as well
+ if (mTriggerContent) {
+ nsIDocument* doc = mContent->GetUncomposedDoc();
+ if (doc) {
+ if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
+ nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
+ if (root) {
+ root->SetPopupNode(nullptr);
+ }
+ }
+ }
+ }
+ mTriggerContent = nullptr;
+ mAnchorContent = nullptr;
+ }
+
+ // when invisible and about to be closed, HidePopup has already been called,
+ // so just set the new state to closed and return
+ if (mPopupState == ePopupInvisible) {
+ if (aNewState == ePopupClosed)
+ mPopupState = ePopupClosed;
+ return;
+ }
+
+ mPopupState = aNewState;
+
+ if (IsMenu())
+ SetCurrentMenuItem(nullptr);
+
+ mIncrementalString.Truncate();
+
+ LockMenuUntilClosed(false);
+
+ mIsOpenChanged = false;
+ mCurrentMenu = nullptr; // make sure no current menu is set
+ mHFlip = mVFlip = false;
+
+ nsView* view = GetView();
+ nsViewManager* viewManager = view->GetViewManager();
+ viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
+
+ FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent);
+
+ // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
+ // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
+ // This code may not the best solution, but we can leave it here until we find the better approach.
+ NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
+ EventStates state = mContent->AsElement()->State();
+
+ if (state.HasState(NS_EVENT_STATE_HOVER)) {
+ EventStateManager* esm = PresContext()->EventStateManager();
+ esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+ }
+
+ nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
+ if (menuFrame) {
+ menuFrame->PopupClosed(aDeselectMenu);
+ }
+}
+
+uint32_t
+nsMenuPopupFrame::GetXULLayoutFlags()
+{
+ return NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetRootViewForPopup
+// Retrieves the view for the popup widget that contains the given frame.
+// If the given frame is not contained by a popup widget, return the
+// root view of the root viewmanager.
+nsView*
+nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
+{
+ nsView* view = aStartFrame->GetClosestView();
+ NS_ASSERTION(view, "frame must have a closest view!");
+ while (view) {
+ // Walk up the view hierarchy looking for a view whose widget has a
+ // window type of eWindowType_popup - in other words a popup window
+ // widget. If we find one, this is the view we want.
+ nsIWidget* widget = view->GetWidget();
+ if (widget && widget->WindowType() == eWindowType_popup) {
+ return view;
+ }
+
+ nsView* temp = view->GetParent();
+ if (!temp) {
+ // Otherwise, we've walked all the way up to the root view and not
+ // found a view for a popup window widget. Just return the root view.
+ return view;
+ }
+ view = temp;
+ }
+
+ return nullptr;
+}
+
+nsPoint
+nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
+ FlipStyle& aHFlip, FlipStyle& aVFlip)
+{
+ // flip the anchor and alignment for right-to-left
+ int8_t popupAnchor(mPopupAnchor);
+ int8_t popupAlign(mPopupAlignment);
+ if (IsDirectionRTL()) {
+ // no need to flip the centered anchor types vertically
+ if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
+ popupAnchor = -popupAnchor;
+ }
+ popupAlign = -popupAlign;
+ }
+
+ nsRect originalAnchorRect(anchorRect);
+
+ // first, determine at which corner of the anchor the popup should appear
+ nsPoint pnt;
+ switch (popupAnchor) {
+ case POPUPALIGNMENT_LEFTCENTER:
+ pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
+ anchorRect.y = pnt.y;
+ anchorRect.height = 0;
+ break;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
+ anchorRect.y = pnt.y;
+ anchorRect.height = 0;
+ break;
+ case POPUPALIGNMENT_TOPCENTER:
+ pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
+ anchorRect.x = pnt.x;
+ anchorRect.width = 0;
+ break;
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
+ anchorRect.x = pnt.x;
+ anchorRect.width = 0;
+ break;
+ case POPUPALIGNMENT_TOPRIGHT:
+ pnt = anchorRect.TopRight();
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ pnt = anchorRect.BottomLeft();
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ pnt = anchorRect.BottomRight();
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ default:
+ pnt = anchorRect.TopLeft();
+ break;
+ }
+
+ // If the alignment is on the right edge of the popup, move the popup left
+ // by the width. Similarly, if the alignment is on the bottom edge of the
+ // popup, move the popup up by the height. In addition, account for the
+ // margins of the popup on the edge on which it is aligned.
+ nsMargin margin(0, 0, 0, 0);
+ StyleMargin()->GetMargin(margin);
+ switch (popupAlign) {
+ case POPUPALIGNMENT_TOPRIGHT:
+ pnt.MoveBy(-mRect.width - margin.right, margin.top);
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ default:
+ pnt.MoveBy(margin.left, margin.top);
+ break;
+ }
+
+ // If we aligning to the selected item in the popup, adjust the vertical
+ // position by the height of the menulist label and the selected item's
+ // position.
+ if (mPosition == POPUPPOSITION_SELECTION) {
+ MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT ||
+ popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT);
+ MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT ||
+ popupAlign == POPUPALIGNMENT_TOPRIGHT);
+
+ nsIFrame* selectedItemFrame = GetSelectedItemForAlignment();
+ if (selectedItemFrame) {
+ pnt.y -= originalAnchorRect.height + selectedItemFrame->GetRect().y;
+ }
+ }
+
+ // Flipping horizontally is allowed as long as the popup is above or below
+ // the anchor. This will happen if both the anchor and alignment are top or
+ // both are bottom, but different values. Similarly, flipping vertically is
+ // allowed if the popup is to the left or right of the anchor. In this case,
+ // the values of the constants are such that both must be positive or both
+ // must be negative. A special case, used for overlap, allows flipping
+ // vertically as well.
+ // If we are flipping in both directions, we want to set a flip style both
+ // horizontally and vertically. However, we want to flip on the inside edge
+ // of the anchor. Consider the example of a typical dropdown menu.
+ // Vertically, we flip the popup on the outside edges of the anchor menu,
+ // however horizontally, we want to to use the inside edges so the popup
+ // still appears underneath the anchor menu instead of floating off the
+ // side of the menu.
+ switch (popupAnchor) {
+ case POPUPALIGNMENT_LEFTCENTER:
+ case POPUPALIGNMENT_RIGHTCENTER:
+ aHFlip = FlipStyle_Outside;
+ aVFlip = FlipStyle_Inside;
+ break;
+ case POPUPALIGNMENT_TOPCENTER:
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ aHFlip = FlipStyle_Inside;
+ aVFlip = FlipStyle_Outside;
+ break;
+ default:
+ {
+ FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
+ aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
+ if (((popupAnchor > 0) == (popupAlign > 0)) ||
+ (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
+ aVFlip = FlipStyle_Outside;
+ else
+ aVFlip = anchorEdge;
+ break;
+ }
+ }
+
+ return pnt;
+}
+
+nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment()
+{
+ // This method adjusts a menulist's popup such that the selected item is under the cursor, aligned
+ // with the menulist label.
+ nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(mAnchorContent);
+ if (!select) {
+ // If there isn't an anchor, then try just getting the parent of the popup.
+ select = do_QueryInterface(mContent->GetParent());
+ if (!select) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ select->GetSelectedItem(getter_AddRefs(item));
+
+ nsCOMPtr<nsIContent> selectedElement = do_QueryInterface(item);
+ return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr;
+}
+
+nscoord
+nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
+ nscoord aScreenBegin, nscoord aScreenEnd,
+ nscoord *aOffset)
+{
+ // The popup may be positioned such that either the left/top or bottom/right
+ // is outside the screen - but never both.
+ nscoord newPos =
+ std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
+ *aOffset = newPos - aScreenPoint;
+ aScreenPoint = newPos;
+ return std::min(aSize, aScreenEnd - aScreenPoint);
+}
+
+nscoord
+nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
+ nscoord aScreenBegin, nscoord aScreenEnd,
+ nscoord aAnchorBegin, nscoord aAnchorEnd,
+ nscoord aMarginBegin, nscoord aMarginEnd,
+ nscoord aOffsetForContextMenu, FlipStyle aFlip,
+ bool aEndAligned, bool* aFlipSide)
+{
+ // The flip side argument will be set to true if there wasn't room and we
+ // flipped to the opposite side.
+ *aFlipSide = false;
+
+ // all of the coordinates used here are in app units relative to the screen
+ nscoord popupSize = aSize;
+ if (aScreenPoint < aScreenBegin) {
+ // at its current position, the popup would extend past the left or top
+ // edge of the screen, so it will have to be moved or resized.
+ if (aFlip) {
+ // for inside flips, we flip on the opposite side of the anchor
+ nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
+ nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
+
+ // check whether there is more room to the left and right (or top and
+ // bottom) of the anchor and put the popup on the side with more room.
+ if (startpos - aScreenBegin >= aScreenEnd - endpos) {
+ aScreenPoint = aScreenBegin;
+ popupSize = startpos - aScreenPoint - aMarginEnd;
+ *aFlipSide = !aEndAligned;
+ }
+ else {
+ // If the newly calculated position is different than the existing
+ // position, flip such that the popup is to the right or bottom of the
+ // anchor point instead . However, when flipping use the same margin
+ // size.
+ nscoord newScreenPoint = endpos + aMarginEnd;
+ if (newScreenPoint != aScreenPoint) {
+ *aFlipSide = aEndAligned;
+ aScreenPoint = newScreenPoint;
+ // check if the new position is still off the right or bottom edge of
+ // the screen. If so, resize the popup.
+ if (aScreenPoint + aSize > aScreenEnd) {
+ popupSize = aScreenEnd - aScreenPoint;
+ }
+ }
+ }
+ }
+ else {
+ aScreenPoint = aScreenBegin;
+ }
+ }
+ else if (aScreenPoint + aSize > aScreenEnd) {
+ // at its current position, the popup would extend past the right or
+ // bottom edge of the screen, so it will have to be moved or resized.
+ if (aFlip) {
+ // for inside flips, we flip on the opposite side of the anchor
+ nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
+ nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
+
+ // check whether there is more room to the left and right (or top and
+ // bottom) of the anchor and put the popup on the side with more room.
+ if (aScreenEnd - endpos >= startpos - aScreenBegin) {
+ *aFlipSide = aEndAligned;
+ if (mIsContextMenu) {
+ aScreenPoint = aScreenEnd - aSize;
+ }
+ else {
+ aScreenPoint = endpos + aMarginBegin;
+ popupSize = aScreenEnd - aScreenPoint;
+ }
+ }
+ else {
+ // if the newly calculated position is different than the existing
+ // position, we flip such that the popup is to the left or top of the
+ // anchor point instead.
+ nscoord newScreenPoint = startpos - aSize - aMarginBegin - std::max(aOffsetForContextMenu, 0);
+ if (newScreenPoint != aScreenPoint) {
+ *aFlipSide = !aEndAligned;
+ aScreenPoint = newScreenPoint;
+
+ // check if the new position is still off the left or top edge of the
+ // screen. If so, resize the popup.
+ if (aScreenPoint < aScreenBegin) {
+ aScreenPoint = aScreenBegin;
+ if (!mIsContextMenu) {
+ popupSize = startpos - aScreenPoint - aMarginBegin;
+ }
+ }
+ }
+ }
+ }
+ else {
+ aScreenPoint = aScreenEnd - aSize;
+ }
+ }
+
+ // Make sure that the point is within the screen boundaries and that the
+ // size isn't off the edge of the screen. This can happen when a large
+ // positive or negative margin is used.
+ if (aScreenPoint < aScreenBegin) {
+ aScreenPoint = aScreenBegin;
+ }
+ if (aScreenPoint > aScreenEnd) {
+ aScreenPoint = aScreenEnd - aSize;
+ }
+
+ // If popupSize ended up being negative, or the original size was actually
+ // smaller than the calculated popup size, just use the original size instead.
+ if (popupSize <= 0 || aSize < popupSize) {
+ popupSize = aSize;
+ }
+ return std::min(popupSize, aScreenEnd - aScreenPoint);
+}
+
+nsresult
+nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup, bool aNotify)
+{
+ if (!mShouldAutoPosition)
+ return NS_OK;
+
+ // If this is due to a move, return early if the popup hasn't been laid out
+ // yet. On Windows, this can happen when using a drag popup before it opens.
+ if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
+ return NS_OK;
+ }
+
+ nsPresContext* presContext = PresContext();
+ nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
+ NS_ASSERTION(rootFrame->GetView() && GetView() &&
+ rootFrame->GetView() == GetView()->GetParent(),
+ "rootFrame's view is not our view's parent???");
+
+ // For anchored popups, the anchor rectangle. For non-anchored popups, the
+ // size will be 0.
+ nsRect anchorRect;
+
+ // Width of the parent, used when aSizedToPopup is true.
+ int32_t parentWidth = 0;
+
+ bool anchored = IsAnchored();
+ if (anchored || aSizedToPopup) {
+ // In order to deal with transforms, we need the root prescontext:
+ nsPresContext* rootPresContext = presContext->GetRootPresContext();
+
+ // If we can't reach a root pres context, don't bother continuing:
+ if (!rootPresContext) {
+ return NS_OK;
+ }
+
+ // If anchored to a rectangle, use that rectangle. Otherwise, determine the
+ // rectangle from the anchor.
+ if (mAnchorType == MenuPopupAnchorType_Rect) {
+ anchorRect = ToAppUnits(mScreenRect, presContext->AppUnitsPerCSSPixel());
+ }
+ else {
+ // if the frame is not specified, use the anchor node passed to OpenPopup. If
+ // that wasn't specified either, use the root frame. Note that mAnchorContent
+ // might be a different document so its presshell must be used.
+ if (!aAnchorFrame) {
+ if (mAnchorContent) {
+ aAnchorFrame = mAnchorContent->GetPrimaryFrame();
+ }
+
+ if (!aAnchorFrame) {
+ aAnchorFrame = rootFrame;
+ if (!aAnchorFrame)
+ return NS_OK;
+ }
+ }
+
+ // And then we need its root frame for a reference
+ nsIFrame* referenceFrame = rootPresContext->FrameManager()->GetRootFrame();
+
+ // the dimensions of the anchor
+ nsRect parentRect = aAnchorFrame->GetRectRelativeToSelf();
+ // Relative to the root
+ anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame,
+ parentRect,
+ referenceFrame);
+ // Relative to the screen
+ anchorRect.MoveBy(referenceFrame->GetScreenRectInAppUnits().TopLeft());
+
+ // In its own app units
+ anchorRect =
+ anchorRect.ScaleToOtherAppUnitsRoundOut(rootPresContext->AppUnitsPerDevPixel(),
+ presContext->AppUnitsPerDevPixel());
+ }
+
+ // The width is needed when aSizedToPopup is true
+ parentWidth = anchorRect.width;
+ }
+
+ // Set the popup's size to the preferred size. Below, this size will be
+ // adjusted to fit on the screen or within the content area. If the anchor
+ // is sized to the popup, use the anchor's width instead of the preferred
+ // width. The preferred size should already be set by the parent frame.
+ NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
+ "preferred size of popup not set");
+ mRect.width = aSizedToPopup ? parentWidth : mPrefSize.width;
+ mRect.height = mPrefSize.height;
+
+ // If we're anchoring to a rect, and the rect is smaller than the preferred size
+ // of the popup, change its width accordingly.
+ if (mAnchorType == MenuPopupAnchorType_Rect &&
+ parentWidth < mPrefSize.width) {
+ mRect.width = mPrefSize.width;
+ }
+
+ // the screen position in app units where the popup should appear
+ nsPoint screenPoint;
+
+ // indicators of whether the popup should be flipped or resized.
+ FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
+
+ nsMargin margin(0, 0, 0, 0);
+ StyleMargin()->GetMargin(margin);
+
+ // the screen rectangle of the root frame, in dev pixels.
+ nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
+
+ nsDeviceContext* devContext = presContext->DeviceContext();
+ nsPoint offsetForContextMenu;
+
+ bool isNoAutoHide = IsNoAutoHide();
+ nsPopupLevel popupLevel = PopupLevel(isNoAutoHide);
+
+ if (anchored) {
+ // if we are anchored, there are certain things we don't want to do when
+ // repositioning the popup to fit on the screen, such as end up positioned
+ // over the anchor, for instance a popup appearing over the menu label.
+ // When doing this reposition, we want to move the popup to the side with
+ // the most room. The combination of anchor and alignment dictate if we
+ // readjust above/below or to the left/right.
+ if (mAnchorContent || mAnchorType == MenuPopupAnchorType_Rect) {
+ // move the popup according to the anchor and alignment. This will also
+ // tell us which axis the popup is flush against in case we have to move
+ // it around later. The AdjustPositionForAnchorAlign method accounts for
+ // the popup's margin.
+ screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
+ }
+ else {
+ // with no anchor, the popup is positioned relative to the root frame
+ anchorRect = rootScreenRect;
+ screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top);
+ }
+
+ // mXPos and mYPos specify an additonal offset passed to OpenPopup that
+ // should be added to the position. We also add the offset to the anchor
+ // pos so a later flip/resize takes the offset into account.
+ nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos);
+ if (IsDirectionRTL()) {
+ screenPoint.x -= anchorXOffset;
+ anchorRect.x -= anchorXOffset;
+ } else {
+ screenPoint.x += anchorXOffset;
+ anchorRect.x += anchorXOffset;
+ }
+ nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos);
+ screenPoint.y += anchorYOffset;
+ anchorRect.y += anchorYOffset;
+
+ // If this is a noautohide popup, set the screen coordinates of the popup.
+ // This way, the popup stays at the location where it was opened even when
+ // the window is moved. Popups at the parent level follow the parent
+ // window as it is moved and remained anchored, so we want to maintain the
+ // anchoring instead.
+ if (isNoAutoHide &&
+ (popupLevel != ePopupLevelParent || mAnchorType == MenuPopupAnchorType_Rect)) {
+ // Account for the margin that will end up being added to the screen coordinate
+ // the next time SetPopupPosition is called.
+ mAnchorType = MenuPopupAnchorType_Point;
+ mScreenRect.x = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left);
+ mScreenRect.y = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top);
+ }
+ }
+ else {
+ // The popup is positioned at a screen coordinate.
+ // First convert the screen position in mScreenRect from CSS pixels into
+ // device pixels, ignoring any zoom as mScreenRect holds unzoomed screen
+ // coordinates.
+ int32_t factor = devContext->AppUnitsPerDevPixelAtUnitFullZoom();
+
+ // Depending on the platform, context menus should be offset by varying amounts
+ // to ensure that they don't appear directly where the cursor is. Otherwise,
+ // it is too easy to have the context menu close up again.
+ if (mAdjustOffsetForContextMenu) {
+ nsPoint offsetForContextMenuDev;
+ offsetForContextMenuDev.x = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
+ LookAndFeel::eIntID_ContextMenuOffsetHorizontal)) / factor;
+ offsetForContextMenuDev.y = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
+ LookAndFeel::eIntID_ContextMenuOffsetVertical)) / factor;
+ offsetForContextMenu.x = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.x);
+ offsetForContextMenu.y = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.y);
+ }
+
+ // next, convert into app units accounting for the zoom
+ screenPoint.x = presContext->DevPixelsToAppUnits(
+ nsPresContext::CSSPixelsToAppUnits(mScreenRect.x) / factor);
+ screenPoint.y = presContext->DevPixelsToAppUnits(
+ nsPresContext::CSSPixelsToAppUnits(mScreenRect.y) / factor);
+ anchorRect = nsRect(screenPoint, nsSize(0, 0));
+
+ // add the margins on the popup
+ screenPoint.MoveBy(margin.left + offsetForContextMenu.x,
+ margin.top + offsetForContextMenu.y);
+
+#ifdef XP_MACOSX
+ // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically
+ if (mPopupType == ePopupTypeTooltip) {
+ vFlip = FlipStyle_Outside;
+ } else {
+ hFlip = FlipStyle_Outside;
+ }
+#else
+ // Other OS screen positioned popups can be flipped vertically but never horizontally
+ vFlip = FlipStyle_Outside;
+#endif // #ifdef XP_MACOSX
+ }
+
+ // If a panel is being moved or has flip="none", don't constrain or flip it. But always do this for
+ // content shells, so that the popup doesn't extend outside the containing frame.
+ if (mInContentShell || (mFlip != FlipType_None &&
+ (!aIsMove || mPopupType != ePopupTypePanel))) {
+ int32_t appPerDev = presContext->AppUnitsPerDevPixel();
+ LayoutDeviceIntRect anchorRectDevPix =
+ LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect, appPerDev);
+ LayoutDeviceIntRect rootScreenRectDevPix =
+ LayoutDeviceIntRect::FromAppUnitsToNearest(rootScreenRect, appPerDev);
+ LayoutDeviceIntRect screenRectDevPix =
+ GetConstraintRect(anchorRectDevPix, rootScreenRectDevPix, popupLevel);
+ nsRect screenRect =
+ LayoutDeviceIntRect::ToAppUnits(screenRectDevPix, appPerDev);
+
+ // Ensure that anchorRect is on screen.
+ anchorRect = anchorRect.Intersect(screenRect);
+
+ // shrink the the popup down if it is larger than the screen size
+ if (mRect.width > screenRect.width)
+ mRect.width = screenRect.width;
+ if (mRect.height > screenRect.height)
+ mRect.height = screenRect.height;
+
+ // at this point the anchor (anchorRect) is within the available screen
+ // area (screenRect) and the popup is known to be no larger than the screen.
+
+ // We might want to "slide" an arrow if the panel is of the correct type -
+ // but we can only slide on one axis - the other axis must be "flipped or
+ // resized" as normal.
+ bool slideHorizontal = false, slideVertical = false;
+ if (mFlip == FlipType_Slide) {
+ int8_t position = GetAlignmentPosition();
+ slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
+ position <= POPUPPOSITION_AFTEREND;
+ slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
+ position <= POPUPPOSITION_ENDAFTER;
+ }
+
+ // Next, check if there is enough space to show the popup at full size when
+ // positioned at screenPoint. If not, flip the popups to the opposite side
+ // of their anchor point, or resize them as necessary.
+ bool endAligned = IsDirectionRTL() ?
+ mPopupAlignment == POPUPALIGNMENT_TOPLEFT || mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT :
+ mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
+ if (slideHorizontal) {
+ mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x,
+ screenRect.XMost(), &mAlignmentOffset);
+ } else {
+ mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
+ screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
+ margin.left, margin.right, offsetForContextMenu.x, hFlip,
+ endAligned, &mHFlip);
+ }
+
+ endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
+ mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
+ if (slideVertical) {
+ mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y,
+ screenRect.YMost(), &mAlignmentOffset);
+ } else {
+ mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
+ screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
+ margin.top, margin.bottom, offsetForContextMenu.y, vFlip,
+ endAligned, &mVFlip);
+ }
+
+ NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
+ screenPoint.x + mRect.width <= screenRect.XMost() &&
+ screenPoint.y + mRect.height <= screenRect.YMost(),
+ "Popup is offscreen");
+ }
+
+ // snap the popup's position in screen coordinates to device pixels,
+ // see bug 622507, bug 961431
+ screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x);
+ screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y);
+
+ // determine the x and y position of the view by subtracting the desired
+ // screen position from the screen position of the root frame.
+ nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft();
+
+ nsView* view = GetView();
+ NS_ASSERTION(view, "popup with no view");
+
+ // Offset the position by the width and height of the borders and titlebar.
+ // Even though GetClientOffset should return (0, 0) when there is no
+ // titlebar or borders, we skip these calculations anyway for non-panels
+ // to save time since they will never have a titlebar.
+ nsIWidget* widget = view->GetWidget();
+ if (mPopupType == ePopupTypePanel && widget) {
+ mLastClientOffset = widget->GetClientOffset();
+ viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x);
+ viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y);
+ }
+
+ presContext->GetPresShell()->GetViewManager()->
+ MoveViewTo(view, viewPoint.x, viewPoint.y);
+
+ // Now that we've positioned the view, sync up the frame's origin.
+ nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
+
+ if (aSizedToPopup) {
+ nsBoxLayoutState state(PresContext());
+ // XXXndeakin can parentSize.width still extend outside?
+ SetXULBounds(state, mRect);
+ }
+
+ // If the popup is in the positioned state or if it is shown and the position
+ // or size changed, dispatch a popuppositioned event if the popup wants it.
+ nsIntRect newRect(screenPoint.x, screenPoint.y, mRect.width, mRect.height);
+ if (mPopupState == ePopupPositioning ||
+ (mPopupState == ePopupShown && !newRect.IsEqualEdges(mUsedScreenRect))) {
+ mUsedScreenRect = newRect;
+ if (aNotify) {
+ nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsMenuFrame*
+nsMenuPopupFrame::GetCurrentMenuItem()
+{
+ return mCurrentMenu;
+}
+
+LayoutDeviceIntRect
+nsMenuPopupFrame::GetConstraintRect(const LayoutDeviceIntRect& aAnchorRect,
+ const LayoutDeviceIntRect& aRootScreenRect,
+ nsPopupLevel aPopupLevel)
+{
+ LayoutDeviceIntRect screenRectPixels;
+
+ // determine the available screen space. It will be reduced by the OS chrome
+ // such as menubars. It addition, for content shells, it will be the area of
+ // the content rather than the screen.
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
+ if (sm) {
+ // for content shells, get the screen where the root frame is located.
+ // This is because we need to constrain the content to this content area,
+ // so we should use the same screen. Otherwise, use the screen where the
+ // anchor is located.
+ DesktopToLayoutDeviceScale scale =
+ PresContext()->DeviceContext()->GetDesktopToDeviceScale();
+ DesktopRect rect =
+ (mInContentShell ? aRootScreenRect : aAnchorRect) / scale;
+ int32_t width = std::max(1, NSToIntRound(rect.width));
+ int32_t height = std::max(1, NSToIntRound(rect.height));
+ sm->ScreenForRect(rect.x, rect.y, width, height, getter_AddRefs(screen));
+ if (screen) {
+ // Non-top-level popups (which will always be panels)
+ // should never overlap the OS bar:
+ bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop;
+ // get the total screen area if the popup is allowed to overlap it.
+ if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell)
+ screen->GetRect(&screenRectPixels.x, &screenRectPixels.y,
+ &screenRectPixels.width, &screenRectPixels.height);
+ else
+ screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y,
+ &screenRectPixels.width, &screenRectPixels.height);
+ }
+ }
+
+ if (mInContentShell) {
+ // for content shells, clip to the client area rather than the screen area
+ screenRectPixels.IntersectRect(screenRectPixels, aRootScreenRect);
+ }
+ else if (!mOverrideConstraintRect.IsEmpty()) {
+ LayoutDeviceIntRect overrideConstrainRect =
+ LayoutDeviceIntRect::FromAppUnitsToNearest(mOverrideConstraintRect,
+ PresContext()->AppUnitsPerDevPixel());
+ // This is currently only used for <select> elements where we want to constrain
+ // vertically to the screen but not horizontally, so do the intersection and then
+ // reset the horizontal values.
+ screenRectPixels.IntersectRect(screenRectPixels, overrideConstrainRect);
+ screenRectPixels.x = overrideConstrainRect.x;
+ screenRectPixels.width = overrideConstrainRect.width;
+ }
+
+ return screenRectPixels;
+}
+
+void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide,
+ int8_t aVerticalSide,
+ LayoutDeviceIntPoint& aChange)
+{
+ int8_t popupAlign(mPopupAlignment);
+ if (IsDirectionRTL()) {
+ popupAlign = -popupAlign;
+ }
+
+ if (aHorizontalSide == (mHFlip ? NS_SIDE_RIGHT : NS_SIDE_LEFT)) {
+ if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) {
+ aChange.x = 0;
+ }
+ }
+ else if (aHorizontalSide == (mHFlip ? NS_SIDE_LEFT : NS_SIDE_RIGHT)) {
+ if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
+ aChange.x = 0;
+ }
+ }
+
+ if (aVerticalSide == (mVFlip ? NS_SIDE_BOTTOM : NS_SIDE_TOP)) {
+ if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) {
+ aChange.y = 0;
+ }
+ }
+ else if (aVerticalSide == (mVFlip ? NS_SIDE_TOP : NS_SIDE_BOTTOM)) {
+ if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
+ aChange.y = 0;
+ }
+ }
+}
+
+ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks()
+{
+ // If the popup has explicitly set a consume mode, honor that.
+ if (mConsumeRollupEvent != PopupBoxObject::ROLLUP_DEFAULT) {
+ return (mConsumeRollupEvent == PopupBoxObject::ROLLUP_CONSUME) ?
+ ConsumeOutsideClicks_True : ConsumeOutsideClicks_ParentOnly;
+ }
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return ConsumeOutsideClicks_True;
+ }
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
+ nsGkAtoms::_false, eCaseMatters)) {
+ return ConsumeOutsideClicks_ParentOnly;
+ }
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
+ nsGkAtoms::never, eCaseMatters)) {
+ return ConsumeOutsideClicks_Never;
+ }
+
+ nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
+ if (parentContent) {
+ dom::NodeInfo *ni = parentContent->NodeInfo();
+ if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) {
+ return ConsumeOutsideClicks_True; // Consume outside clicks for combo boxes on all platforms
+ }
+#if defined(XP_WIN)
+ // Don't consume outside clicks for menus in Windows
+ if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::splitmenu, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
+ ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
+ (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::menu, eCaseMatters) ||
+ parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::menuButton, eCaseMatters)))) {
+ return ConsumeOutsideClicks_Never;
+ }
+#endif
+ if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) {
+ // Don't consume outside clicks for autocomplete widget
+ if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::autocomplete, eCaseMatters)) {
+ return ConsumeOutsideClicks_Never;
+ }
+ }
+ }
+
+ return ConsumeOutsideClicks_True;
+}
+
+// XXXroc this is megalame. Fossicking around for a frame of the right
+// type is a recipe for disaster in the long term.
+nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart)
+{
+ if (!aStart)
+ return nullptr;
+
+ // try start frame and siblings
+ nsIFrame* currFrame = aStart;
+ do {
+ nsIScrollableFrame* sf = do_QueryFrame(currFrame);
+ if (sf)
+ return sf;
+ currFrame = currFrame->GetNextSibling();
+ } while (currFrame);
+
+ // try children
+ currFrame = aStart;
+ do {
+ nsIFrame* childFrame = currFrame->PrincipalChildList().FirstChild();
+ nsIScrollableFrame* sf = GetScrollFrame(childFrame);
+ if (sf)
+ return sf;
+ currFrame = currFrame->GetNextSibling();
+ } while (currFrame);
+
+ return nullptr;
+}
+
+void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
+{
+ if (aMenuItem) {
+ aMenuItem->PresContext()->PresShell()->ScrollFrameRectIntoView(
+ aMenuItem,
+ nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
+ nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
+ }
+}
+
+void nsMenuPopupFrame::ChangeByPage(bool aIsUp)
+{
+ // Only scroll by page within menulists.
+ if (!IsMenuList()) {
+ return;
+ }
+
+ nsMenuFrame* newMenu = nullptr;
+ nsIFrame* currentMenu = mCurrentMenu;
+ if (!currentMenu) {
+ // If there is no current menu item, get the first item. When moving up,
+ // just use this as the newMenu and leave currentMenu null so that no
+ // check for a later element is performed. When moving down, set currentMenu
+ // so that we look for one page down from the first item.
+ newMenu = nsXULPopupManager::GetNextMenuItem(this, nullptr, true);
+ if (!aIsUp) {
+ currentMenu = newMenu;
+ }
+ }
+
+ if (currentMenu) {
+ nscoord scrollHeight = mRect.height;
+ nsIScrollableFrame *scrollframe = GetScrollFrame(this);
+ if (scrollframe) {
+ scrollHeight = scrollframe->GetScrollPortRect().height;
+ }
+
+ // Get the position of the current item and add or subtract one popup's
+ // height to or from it.
+ nscoord targetPosition = aIsUp ? currentMenu->GetRect().YMost() - scrollHeight :
+ currentMenu->GetRect().y + scrollHeight;
+
+ // Indicates that the last visible child was a valid menuitem.
+ bool lastWasValid = false;
+
+ // Look for the next child which is just past the target position. This child
+ // will need to be selected.
+ while (currentMenu) {
+ // Only consider menu frames.
+ nsMenuFrame* menuFrame = do_QueryFrame(currentMenu);
+ if (menuFrame &&
+ nsXULPopupManager::IsValidMenuItem(menuFrame->GetContent(), true)) {
+
+ // If the right position was found, break out. Otherwise, look for another item.
+ if ((!aIsUp && currentMenu->GetRect().YMost() > targetPosition) ||
+ (aIsUp && currentMenu->GetRect().y < targetPosition)) {
+
+ // If the last visible child was not a valid menuitem or was disabled,
+ // use this as the menu to select, skipping over any non-valid items at
+ // the edge of the page.
+ if (!lastWasValid) {
+ newMenu = menuFrame;
+ }
+
+ break;
+ }
+
+ // Assign this item to newMenu. This item will be selected in case we
+ // don't find any more.
+ lastWasValid = true;
+ newMenu = menuFrame;
+ }
+ else {
+ lastWasValid = false;
+ }
+
+ currentMenu = aIsUp ? currentMenu->GetPrevSibling() :
+ currentMenu->GetNextSibling();
+ }
+ }
+
+ // Select the new menuitem.
+ if (newMenu) {
+ ChangeMenuItem(newMenu, false, true);
+ }
+}
+
+NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
+{
+ if (mCurrentMenu == aMenuItem)
+ return NS_OK;
+
+ if (mCurrentMenu) {
+ mCurrentMenu->SelectMenu(false);
+ }
+
+ if (aMenuItem) {
+ EnsureMenuItemIsVisible(aMenuItem);
+ aMenuItem->SelectMenu(true);
+ }
+
+ mCurrentMenu = aMenuItem;
+
+ return NS_OK;
+}
+
+void
+nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
+{
+ mCurrentMenu = nullptr;
+}
+
+NS_IMETHODIMP
+nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
+ bool aSelectFirstItem,
+ bool aFromKey)
+{
+ if (mCurrentMenu == aMenuItem)
+ return NS_OK;
+
+ // When a context menu is open, the current menu is locked, and no change
+ // to the menu is allowed.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!mIsContextMenu && pm && pm->HasContextMenu(this))
+ return NS_OK;
+
+ // Unset the current child.
+ if (mCurrentMenu) {
+ mCurrentMenu->SelectMenu(false);
+ nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
+ if (popup) {
+ if (mCurrentMenu->IsOpen()) {
+ if (pm)
+ pm->HidePopupAfterDelay(popup);
+ }
+ }
+ }
+
+ // Set the new child.
+ if (aMenuItem) {
+ EnsureMenuItemIsVisible(aMenuItem);
+ aMenuItem->SelectMenu(true);
+
+ // On Windows, a menulist should update its value whenever navigation was
+ // done by the keyboard.
+#ifdef XP_WIN
+ if (aFromKey && IsOpen() && IsMenuList()) {
+ // Fire a command event as the new item, but we don't want to close
+ // the menu, blink it, or update any other state of the menuitem. The
+ // command event will cause the item to be selected.
+ nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), /* aTrusted = */ true,
+ nullptr, PresContext()->PresShell(),
+ false, false, false, false);
+ }
+#endif
+ }
+
+ mCurrentMenu = aMenuItem;
+
+ return NS_OK;
+}
+
+nsMenuFrame*
+nsMenuPopupFrame::Enter(WidgetGUIEvent* aEvent)
+{
+ mIncrementalString.Truncate();
+
+ // Give it to the child.
+ if (mCurrentMenu)
+ return mCurrentMenu->Enter(aEvent);
+
+ return nullptr;
+}
+
+nsMenuFrame*
+nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction)
+{
+ uint32_t charCode, keyCode;
+ aKeyEvent->GetCharCode(&charCode);
+ aKeyEvent->GetKeyCode(&keyCode);
+
+ doAction = false;
+
+ // Enumerate over our list of frames.
+ auto insertion = PresContext()->PresShell()->
+ FrameConstructor()->GetInsertionPoint(GetContent(), nullptr);
+ nsContainerFrame* immediateParent = insertion.mParentFrame;
+ if (!immediateParent)
+ immediateParent = this;
+
+ uint32_t matchCount = 0, matchShortcutCount = 0;
+ bool foundActive = false;
+ bool isShortcut;
+ nsMenuFrame* frameBefore = nullptr;
+ nsMenuFrame* frameAfter = nullptr;
+ nsMenuFrame* frameShortcut = nullptr;
+
+ nsIContent* parentContent = mContent->GetParent();
+
+ bool isMenu = parentContent &&
+ !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL);
+
+ DOMTimeStamp keyTime;
+ aKeyEvent->AsEvent()->GetTimeStamp(&keyTime);
+
+ if (charCode == 0) {
+ if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) {
+ if (!isMenu && !mIncrementalString.IsEmpty()) {
+ mIncrementalString.SetLength(mIncrementalString.Length() - 1);
+ return nullptr;
+ }
+ else {
+#ifdef XP_WIN
+ nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
+ if (soundInterface)
+ soundInterface->Beep();
+#endif // #ifdef XP_WIN
+ }
+ }
+ return nullptr;
+ }
+ else {
+ char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
+ if (isMenu) {
+ // Menu supports only first-letter navigation
+ mIncrementalString = uniChar;
+ } else if (IsWithinIncrementalTime(keyTime)) {
+ mIncrementalString.Append(uniChar);
+ } else {
+ // Interval too long, treat as new typing
+ mIncrementalString = uniChar;
+ }
+ }
+
+ // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
+ nsAutoString incrementalString(mIncrementalString);
+ uint32_t charIndex = 1, stringLength = incrementalString.Length();
+ while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
+ charIndex++;
+ }
+ if (charIndex == stringLength) {
+ incrementalString.Truncate(1);
+ stringLength = 1;
+ }
+
+ sLastKeyTime = keyTime;
+
+ // NOTE: If you crashed here due to a bogus |immediateParent| it is
+ // possible that the menu whose shortcut is being looked up has
+ // been destroyed already. One strategy would be to
+ // setTimeout(<func>,0) as detailed in:
+ // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
+ nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(immediateParent, nullptr, true);
+ nsIFrame* currFrame = firstMenuItem;
+
+ int32_t menuAccessKey = -1;
+ nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
+
+ // We start searching from first child. This process is divided into two parts
+ // -- before current and after current -- by the current item
+ while (currFrame) {
+ nsIContent* current = currFrame->GetContent();
+ nsAutoString textKey;
+ if (menuAccessKey >= 0) {
+ // Get the shortcut attribute.
+ current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey);
+ }
+ if (textKey.IsEmpty()) { // No shortcut, try first letter
+ isShortcut = false;
+ current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey);
+ if (textKey.IsEmpty()) // No label, try another attribute (value)
+ current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey);
+ }
+ else
+ isShortcut = true;
+
+ if (StringBeginsWith(textKey, incrementalString,
+ nsCaseInsensitiveStringComparator())) {
+ // mIncrementalString is a prefix of textKey
+ nsMenuFrame* menu = do_QueryFrame(currFrame);
+ if (menu) {
+ // There is one match
+ matchCount++;
+ if (isShortcut) {
+ // There is one shortcut-key match
+ matchShortcutCount++;
+ // Record the matched item. If there is only one matched shortcut item, do it
+ frameShortcut = menu;
+ }
+ if (!foundActive) {
+ // It's a first candidate item located before/on the current item
+ if (!frameBefore)
+ frameBefore = menu;
+ }
+ else {
+ // It's a first candidate item located after the current item
+ if (!frameAfter)
+ frameAfter = menu;
+ }
+ }
+ else
+ return nullptr;
+ }
+
+ // Get the active status
+ if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
+ nsGkAtoms::_true, eCaseMatters)) {
+ foundActive = true;
+ if (stringLength > 1) {
+ // If there is more than one char typed, the current item has highest priority,
+ // otherwise the item next to current has highest priority
+ if (currFrame == frameBefore)
+ return frameBefore;
+ }
+ }
+
+ nsMenuFrame* menu = do_QueryFrame(currFrame);
+ currFrame = nsXULPopupManager::GetNextMenuItem(immediateParent, menu, true);
+ if (currFrame == firstMenuItem)
+ break;
+ }
+
+ doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1));
+
+ if (matchShortcutCount == 1) // We have one matched shortcut item
+ return frameShortcut;
+ if (frameAfter) // If we have matched item after the current, use it
+ return frameAfter;
+ else if (frameBefore) // If we haven't, use the item before the current
+ return frameBefore;
+
+ // If we don't match anything, rollback the last typing
+ mIncrementalString.SetLength(mIncrementalString.Length() - 1);
+
+ // didn't find a matching menu item
+#ifdef XP_WIN
+ // behavior on Windows - this item is in a menu popup off of the
+ // menu bar, so beep and do nothing else
+ if (isMenu) {
+ nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
+ if (soundInterface)
+ soundInterface->Beep();
+ }
+#endif // #ifdef XP_WIN
+
+ return nullptr;
+}
+
+void
+nsMenuPopupFrame::LockMenuUntilClosed(bool aLock)
+{
+ mIsMenuLocked = aLock;
+
+ // Lock / unlock the parent, too.
+ nsMenuFrame* menu = do_QueryFrame(GetParent());
+ if (menu) {
+ nsMenuParent* parentParent = menu->GetMenuParent();
+ if (parentParent) {
+ parentParent->LockMenuUntilClosed(aLock);
+ }
+ }
+}
+
+nsIWidget*
+nsMenuPopupFrame::GetWidget()
+{
+ nsView * view = GetRootViewForPopup(this);
+ if (!view)
+ return nullptr;
+
+ return view->GetWidget();
+}
+
+void
+nsMenuPopupFrame::AttachedDismissalListener()
+{
+ mConsumeRollupEvent = PopupBoxObject::ROLLUP_DEFAULT;
+}
+
+// helpers /////////////////////////////////////////////////////////////
+
+nsresult
+nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top)
+ MoveToAttributePosition();
+
+#ifndef MOZ_GTK2
+ if (aAttribute == nsGkAtoms::noautohide) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->EnableRollup(mContent, !IsNoAutoHide());
+ }
+#endif
+
+ if (aAttribute == nsGkAtoms::label) {
+ // set the label for the titlebar
+ nsView* view = GetView();
+ if (view) {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ nsAutoString title;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
+ if (!title.IsEmpty()) {
+ widget->SetTitle(title);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+void
+nsMenuPopupFrame::MoveToAttributePosition()
+{
+ // Move the widget around when the user sets the |left| and |top| attributes.
+ // Note that this is not the best way to move the widget, as it results in lots
+ // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
+ // PopupBoxObject if possible.
+ nsAutoString left, top;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
+ nsresult err1, err2;
+ mozilla::CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2));
+
+ if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
+ MoveTo(pos, false);
+}
+
+void
+nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (mReflowCallbackData.mPosted) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackData.Clear();
+ }
+
+ nsMenuFrame* menu = do_QueryFrame(GetParent());
+ if (menu) {
+ // clear the open attribute on the parent menu
+ nsContentUtils::AddScriptRunner(
+ new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open));
+ }
+
+ ClearPopupShownDispatcher();
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->PopupDestroyed(this);
+
+ nsIRootBox* rootBox =
+ nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox && rootBox->GetDefaultTooltip() == mContent) {
+ rootBox->SetDefaultTooltip(nullptr);
+ }
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+
+void
+nsMenuPopupFrame::MoveTo(const CSSIntPoint& aPos, bool aUpdateAttrs)
+{
+ nsIWidget* widget = GetWidget();
+ if ((mScreenRect.x == aPos.x && mScreenRect.y == aPos.y) &&
+ (!widget || widget->GetClientOffset() == mLastClientOffset)) {
+ return;
+ }
+
+ // reposition the popup at the specified coordinates. Don't clear the anchor
+ // and position, because the popup can be reset to its anchor position by
+ // using (-1, -1) as coordinates. Subtract off the margin as it will be
+ // added to the position when SetPopupPosition is called.
+ nsMargin margin(0, 0, 0, 0);
+ StyleMargin()->GetMargin(margin);
+
+ // Workaround for bug 788189. See also bug 708278 comment #25 and following.
+ if (mAdjustOffsetForContextMenu) {
+ margin.left += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
+ LookAndFeel::eIntID_ContextMenuOffsetHorizontal));
+ margin.top += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
+ LookAndFeel::eIntID_ContextMenuOffsetVertical));
+ }
+
+ nsPresContext* presContext = PresContext();
+ mAnchorType = MenuPopupAnchorType_Point;
+ mScreenRect.x = aPos.x - presContext->AppUnitsToIntCSSPixels(margin.left);
+ mScreenRect.y = aPos.y - presContext->AppUnitsToIntCSSPixels(margin.top);
+
+ SetPopupPosition(nullptr, true, false, true);
+
+ nsCOMPtr<nsIContent> popup = mContent;
+ if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
+ popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
+ {
+ nsAutoString left, top;
+ left.AppendInt(aPos.x);
+ top.AppendInt(aPos.y);
+ popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
+ popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
+ }
+}
+
+void
+nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aAttributesOverride)
+{
+ NS_ASSERTION(IsVisible(), "popup must be visible to move it");
+
+ nsPopupState oldstate = mPopupState;
+ InitializePopup(aAnchorContent, mTriggerContent, aPosition,
+ aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
+ // InitializePopup changed the state so reset it.
+ mPopupState = oldstate;
+
+ // Pass false here so that flipping and adjusting to fit on the screen happen.
+ SetPopupPosition(nullptr, false, false, true);
+}
+
+bool
+nsMenuPopupFrame::GetAutoPosition()
+{
+ return mShouldAutoPosition;
+}
+
+void
+nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
+{
+ mShouldAutoPosition = aShouldAutoPosition;
+}
+
+void
+nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode)
+{
+ mConsumeRollupEvent = aConsumeMode;
+}
+
+int8_t
+nsMenuPopupFrame::GetAlignmentPosition() const
+{
+ // The code below handles most cases of alignment, anchor and position values. Those that are
+ // not handled just return POPUPPOSITION_UNKNOWN.
+
+ if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER ||
+ mPosition == POPUPPOSITION_SELECTION)
+ return mPosition;
+
+ int8_t position = mPosition;
+
+ if (position == POPUPPOSITION_UNKNOWN) {
+ switch (mPopupAnchor) {
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ?
+ POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART;
+ break;
+ case POPUPALIGNMENT_TOPCENTER:
+ position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
+ POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART;
+ break;
+ case POPUPALIGNMENT_LEFTCENTER:
+ position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
+ POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE;
+ break;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ?
+ POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (mHFlip) {
+ position = POPUPPOSITION_HFLIP(position);
+ }
+
+ if (mVFlip) {
+ position = POPUPPOSITION_VFLIP(position);
+ }
+
+ return position;
+}
+
+/**
+ * KEEP THIS IN SYNC WITH nsContainerFrame::CreateViewForFrame
+ * as much as possible. Until we get rid of views finally...
+ */
+void
+nsMenuPopupFrame::CreatePopupView()
+{
+ if (HasView()) {
+ return;
+ }
+
+ nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager();
+ NS_ASSERTION(nullptr != viewManager, "null view manager");
+
+ // Create a view
+ nsView* parentView = viewManager->GetRootView();
+ nsViewVisibility visibility = nsViewVisibility_kHide;
+ int32_t zIndex = INT32_MAX;
+ bool autoZIndex = false;
+
+ NS_ASSERTION(parentView, "no parent view");
+
+ // Create a view
+ nsView *view = viewManager->CreateView(GetRect(), parentView, visibility);
+ viewManager->SetViewZIndex(view, autoZIndex, zIndex);
+ // XXX put view last in document order until we can do better
+ viewManager->InsertChild(parentView, view, nullptr, true);
+
+ // Remember our view
+ SetView(view);
+
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
+}
diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
new file mode 100644
index 000000000..b32073960
--- /dev/null
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -0,0 +1,628 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsMenuPopupFrame
+//
+
+#ifndef nsMenuPopupFrame_h__
+#define nsMenuPopupFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsMenuFrame.h"
+
+#include "nsBoxFrame.h"
+#include "nsMenuParent.h"
+
+#include "nsITimer.h"
+
+#include "Units.h"
+
+class nsIWidget;
+
+// XUL popups can be in several different states. When opening a popup, the
+// state changes as follows:
+// ePopupClosed - initial state
+// ePopupShowing - during the period when the popupshowing event fires
+// ePopupOpening - between the popupshowing event and being visible. Creation
+// of the child frames, layout and reflow occurs in this
+// state. The popup is stored in the popup manager's list of
+// open popups during this state.
+// ePopupVisible - layout is done and the popup's view and widget are made
+// visible. The popup is visible on screen but may be
+// transitioning. The popupshown event has not yet fired.
+// ePopupShown - the popup has been shown and is fully ready. This state is
+// assigned just before the popupshown event fires.
+// When closing a popup:
+// ePopupHidden - during the period when the popuphiding event fires and
+// the popup is removed.
+// ePopupClosed - the popup's widget is made invisible.
+enum nsPopupState {
+ // state when a popup is not open
+ ePopupClosed,
+ // state from when a popup is requested to be shown to after the
+ // popupshowing event has been fired.
+ ePopupShowing,
+ // state while a popup is waiting to be laid out and positioned
+ ePopupPositioning,
+ // state while a popup is open but the widget is not yet visible
+ ePopupOpening,
+ // state while a popup is visible and waiting for the popupshown event
+ ePopupVisible,
+ // state while a popup is open and visible on screen
+ ePopupShown,
+ // state from when a popup is requested to be hidden to when it is closed.
+ ePopupHiding,
+ // state which indicates that the popup was hidden without firing the
+ // popuphiding or popuphidden events. It is used when executing a menu
+ // command because the menu needs to be hidden before the command event
+ // fires, yet the popuphiding and popuphidden events are fired after. This
+ // state can also occur when the popup is removed because the document is
+ // unloaded.
+ ePopupInvisible
+};
+
+enum ConsumeOutsideClicksResult {
+ ConsumeOutsideClicks_ParentOnly = 0, // Only consume clicks on the parent anchor
+ ConsumeOutsideClicks_True = 1, // Always consume clicks
+ ConsumeOutsideClicks_Never = 2 // Never consume clicks
+};
+
+// How a popup may be flipped. Flipping to the outside edge is like how
+// a submenu would work. The entire popup is flipped to the opposite side
+// of the anchor.
+enum FlipStyle {
+ FlipStyle_None = 0,
+ FlipStyle_Outside = 1,
+ FlipStyle_Inside = 2
+};
+
+// Values for the flip attribute
+enum FlipType {
+ FlipType_Default = 0,
+ FlipType_None = 1, // don't try to flip or translate to stay onscreen
+ FlipType_Both = 2, // flip in both directions
+ FlipType_Slide = 3 // allow the arrow to "slide" instead of resizing
+};
+
+enum MenuPopupAnchorType {
+ MenuPopupAnchorType_Node = 0, // anchored to a node
+ MenuPopupAnchorType_Point = 1, // unanchored and positioned at a screen point
+ MenuPopupAnchorType_Rect = 2, // anchored at a screen rectangle
+};
+
+// values are selected so that the direction can be flipped just by
+// changing the sign
+#define POPUPALIGNMENT_NONE 0
+#define POPUPALIGNMENT_TOPLEFT 1
+#define POPUPALIGNMENT_TOPRIGHT -1
+#define POPUPALIGNMENT_BOTTOMLEFT 2
+#define POPUPALIGNMENT_BOTTOMRIGHT -2
+
+#define POPUPALIGNMENT_LEFTCENTER 16
+#define POPUPALIGNMENT_RIGHTCENTER -16
+#define POPUPALIGNMENT_TOPCENTER 17
+#define POPUPALIGNMENT_BOTTOMCENTER 18
+
+// The constants here are selected so that horizontally and vertically flipping
+// can be easily handled using the two flip macros below.
+#define POPUPPOSITION_UNKNOWN -1
+#define POPUPPOSITION_BEFORESTART 0
+#define POPUPPOSITION_BEFOREEND 1
+#define POPUPPOSITION_AFTERSTART 2
+#define POPUPPOSITION_AFTEREND 3
+#define POPUPPOSITION_STARTBEFORE 4
+#define POPUPPOSITION_ENDBEFORE 5
+#define POPUPPOSITION_STARTAFTER 6
+#define POPUPPOSITION_ENDAFTER 7
+#define POPUPPOSITION_OVERLAP 8
+#define POPUPPOSITION_AFTERPOINTER 9
+#define POPUPPOSITION_SELECTION 10
+
+#define POPUPPOSITION_HFLIP(v) (v ^ 1)
+#define POPUPPOSITION_VFLIP(v) (v ^ 2)
+
+nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsView;
+class nsMenuPopupFrame;
+
+// this class is used for dispatching popupshown events asynchronously.
+class nsXULPopupShownEvent : public mozilla::Runnable,
+ public nsIDOMEventListener
+{
+public:
+ nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext)
+ : mPopup(aPopup), mPresContext(aPresContext)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void CancelListener();
+
+protected:
+ virtual ~nsXULPopupShownEvent() { }
+
+private:
+ nsCOMPtr<nsIContent> mPopup;
+ RefPtr<nsPresContext> mPresContext;
+};
+
+class nsMenuPopupFrame final : public nsBoxFrame, public nsMenuParent,
+ public nsIReflowCallback
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMenuPopupFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsMenuPopupFrame(nsStyleContext* aContext);
+
+ // nsMenuParent interface
+ virtual nsMenuFrame* GetCurrentMenuItem() override;
+ NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) override;
+ virtual void CurrentMenuIsBeingDestroyed() override;
+ NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem,
+ bool aSelectFirstItem,
+ bool aFromKey) override;
+
+ // as popups are opened asynchronously, the popup pending state is used to
+ // prevent multiple requests from attempting to open the same popup twice
+ nsPopupState PopupState() { return mPopupState; }
+ void SetPopupState(nsPopupState aPopupState) { mPopupState = aPopupState; }
+
+ NS_IMETHOD SetActive(bool aActiveFlag) override { return NS_OK; } // We don't care.
+ virtual bool IsActive() override { return false; }
+ virtual bool IsMenuBar() override { return false; }
+
+ /*
+ * When this popup is open, should clicks outside of it be consumed?
+ * Return true if the popup should rollup on an outside click,
+ * but consume that click so it can't be used for anything else.
+ * Return false to allow clicks outside the popup to activate content
+ * even when the popup is open.
+ * ---------------------------------------------------------------------
+ *
+ * Should clicks outside of a popup be eaten?
+ *
+ * Menus Autocomplete Comboboxes
+ * Mac Eat No Eat
+ * Win No No Eat
+ * Unix Eat No Eat
+ *
+ */
+ ConsumeOutsideClicksResult ConsumeOutsideClicks();
+
+ virtual bool IsContextMenu() override { return mIsContextMenu; }
+
+ virtual bool MenuClosed() override { return true; }
+
+ virtual void LockMenuUntilClosed(bool aLock) override;
+ virtual bool IsMenuLocked() override { return mIsMenuLocked; }
+
+ nsIWidget* GetWidget();
+
+ // The dismissal listener gets created and attached to the window.
+ void AttachedDismissalListener();
+
+ // Overridden methods
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ // returns true if the popup is a panel with the noautohide attribute set to
+ // true. These panels do not roll up automatically.
+ bool IsNoAutoHide() const;
+
+ nsPopupLevel PopupLevel() const
+ {
+ return PopupLevel(IsNoAutoHide());
+ }
+
+ void EnsureWidget();
+
+ nsresult CreateWidgetForView(nsView* aView);
+ uint8_t GetShadowStyle();
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual bool IsLeaf() const override;
+
+ // layout, position and display the popup as needed
+ void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+ nsIFrame* aAnchor, bool aSizedToPopup);
+
+ nsView* GetRootViewForPopup(nsIFrame* aStartFrame);
+
+ // Set the position of the popup either relative to the anchor aAnchorFrame
+ // (or the frame for mAnchorContent if aAnchorFrame is null), anchored at a
+ // rectangle, or at a specific point if a screen position is set. The popup
+ // will be adjusted so that it is on screen. If aIsMove is true, then the
+ // popup is being moved, and should not be flipped. If aNotify is true, then
+ // a popuppositioned event is sent.
+ nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove,
+ bool aSizedToPopup, bool aNotify);
+
+ bool HasGeneratedChildren() { return mGeneratedChildren; }
+ void SetGeneratedChildren() { mGeneratedChildren = true; }
+
+ // called when the Enter key is pressed while the popup is open. This will
+ // just pass the call down to the current menu, if any. If a current menu
+ // should be opened as a result, this method should return the frame for
+ // that menu, or null if no menu should be opened. Also, calling Enter will
+ // reset the current incremental search string, calculated in
+ // FindMenuWithShortcut.
+ nsMenuFrame* Enter(mozilla::WidgetGUIEvent* aEvent);
+
+ nsPopupType PopupType() const { return mPopupType; }
+ bool IsMenu() override { return mPopupType == ePopupTypeMenu; }
+ bool IsOpen() override { return mPopupState == ePopupOpening ||
+ mPopupState == ePopupVisible ||
+ mPopupState == ePopupShown; }
+ bool IsVisible() { return mPopupState == ePopupVisible ||
+ mPopupState == ePopupShown; }
+
+ // Return true if the popup is for a menulist.
+ bool IsMenuList();
+
+ bool IsMouseTransparent() { return mMouseTransparent; }
+
+ static nsIContent* GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame);
+ void ClearTriggerContent() { mTriggerContent = nullptr; }
+
+ // returns true if the popup is in a content shell, or false for a popup in
+ // a chrome shell
+ bool IsInContentShell() { return mInContentShell; }
+
+ // the Initialize methods are used to set the anchor position for
+ // each way of opening a popup.
+ void InitializePopup(nsIContent* aAnchorContent,
+ nsIContent* aTriggerContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ MenuPopupAnchorType aAnchorType,
+ bool aAttributesOverride);
+
+ void InitializePopupAtRect(nsIContent* aTriggerContent,
+ const nsAString& aPosition,
+ const nsIntRect& aRect,
+ bool aAttributesOverride);
+
+ /**
+ * @param aIsContextMenu if true, then the popup is
+ * positioned at a slight offset from aXPos/aYPos to ensure the
+ * (presumed) mouse position is not over the menu.
+ */
+ void InitializePopupAtScreen(nsIContent* aTriggerContent,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu);
+
+ void InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
+ nsAString& aAnchor,
+ nsAString& aAlign,
+ int32_t aXPos, int32_t aYPos);
+
+ // indicate that the popup should be opened
+ void ShowPopup(bool aIsContextMenu);
+ // indicate that the popup should be hidden. The new state should either be
+ // ePopupClosed or ePopupInvisible.
+ void HidePopup(bool aDeselectMenu, nsPopupState aNewState);
+
+ // locate and return the menu frame that should be activated for the
+ // supplied key event. If doAction is set to true by this method,
+ // then the menu's action should be carried out, as if the user had pressed
+ // the Enter key. If doAction is false, the menu should just be highlighted.
+ // This method also handles incremental searching in menus so the user can
+ // type the first few letters of an item/s name to select it.
+ nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction);
+
+ void ClearIncrementalString() { mIncrementalString.Truncate(); }
+ static bool IsWithinIncrementalTime(DOMTimeStamp time) {
+ return !sTimeoutOfIncrementalSearch || time - sLastKeyTime <= sTimeoutOfIncrementalSearch;
+ }
+
+ virtual nsIAtom* GetType() const override { return nsGkAtoms::menuPopupFrame; }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("MenuPopup"), aResult);
+ }
+#endif
+
+ void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame);
+
+ void ChangeByPage(bool aIsUp);
+
+ // Move the popup to the screen coordinate |aPos| in CSS pixels.
+ // If aUpdateAttrs is true, and the popup already has left or top attributes,
+ // then those attributes are updated to the new location.
+ // The frame may be destroyed by this method.
+ void MoveTo(const mozilla::CSSIntPoint& aPos, bool aUpdateAttrs);
+
+ void MoveToAnchor(nsIContent* aAnchorContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aAttributesOverride);
+
+ bool GetAutoPosition();
+ void SetAutoPosition(bool aShouldAutoPosition);
+ void SetConsumeRollupEvent(uint32_t aConsumeMode);
+
+ nsIScrollableFrame* GetScrollFrame(nsIFrame* aStart);
+
+ void SetOverrideConstraintRect(mozilla::LayoutDeviceIntRect aRect) {
+ mOverrideConstraintRect = ToAppUnits(aRect, PresContext()->AppUnitsPerCSSPixel());
+ }
+
+ // For a popup that should appear anchored at the given rect, determine
+ // the screen area that it is constrained by. This will be the available
+ // area of the screen the popup should be displayed on. Content popups,
+ // however, will also be constrained by the content area, given by
+ // aRootScreenRect. All coordinates are in app units.
+ // For non-toplevel popups (which will always be panels), we will also
+ // constrain them to the available screen rect, ie they will not fall
+ // underneath the taskbar, dock or other fixed OS elements.
+ // This operates in device pixels.
+ mozilla::LayoutDeviceIntRect
+ GetConstraintRect(const mozilla::LayoutDeviceIntRect& aAnchorRect,
+ const mozilla::LayoutDeviceIntRect& aRootScreenRect,
+ nsPopupLevel aPopupLevel);
+
+ // Determines whether the given edges of the popup may be moved, where
+ // aHorizontalSide and aVerticalSide are one of the NS_SIDE_* constants, or
+ // 0 for no movement in that direction. aChange is the distance to move on
+ // those sides. If will be reset to 0 if the side cannot be adjusted at all
+ // in that direction. For example, a popup cannot be moved if it is anchored
+ // on a particular side.
+ //
+ // Later, when bug 357725 is implemented, we can make this adjust aChange by
+ // the amount that the side can be resized, so that minimums and maximums
+ // can be taken into account.
+ void CanAdjustEdges(int8_t aHorizontalSide,
+ int8_t aVerticalSide,
+ mozilla::LayoutDeviceIntPoint& aChange);
+
+ // Return true if the popup is positioned relative to an anchor.
+ bool IsAnchored() const { return mAnchorType != MenuPopupAnchorType_Point; }
+
+ // Return the anchor if there is one.
+ nsIContent* GetAnchor() const { return mAnchorContent; }
+
+ // Return the screen coordinates in CSS pixels of the popup,
+ // or (-1, -1, 0, 0) if anchored.
+ nsIntRect GetScreenAnchorRect() const { return mScreenRect; }
+
+ mozilla::LayoutDeviceIntPoint GetLastClientOffset() const
+ {
+ return mLastClientOffset;
+ }
+
+ // Return the alignment of the popup
+ int8_t GetAlignmentPosition() const;
+
+ // Return the offset applied to the alignment of the popup
+ nscoord GetAlignmentOffset() const { return mAlignmentOffset; }
+
+ // Clear the mPopupShownDispatcher, remove the listener and return true if
+ // mPopupShownDispatcher was non-null.
+ bool ClearPopupShownDispatcher()
+ {
+ if (mPopupShownDispatcher) {
+ mPopupShownDispatcher->CancelListener();
+ mPopupShownDispatcher = nullptr;
+ return true;
+ }
+
+ return false;
+ }
+
+ void ShowWithPositionedEvent() {
+ mPopupState = ePopupPositioning;
+ mShouldAutoPosition = true;
+ }
+
+ // nsIReflowCallback
+ virtual bool ReflowFinished() override;
+ virtual void ReflowCallbackCanceled() override;
+
+protected:
+
+ // returns the popup's level.
+ nsPopupLevel PopupLevel(bool aIsNoAutoHide) const;
+
+ // redefine to tell the box system not to move the views.
+ virtual uint32_t GetXULLayoutFlags() override;
+
+ void InitPositionFromAnchorAlign(const nsAString& aAnchor,
+ const nsAString& aAlign);
+
+ // return the position where the popup should be, when it should be
+ // anchored at anchorRect. aHFlip and aVFlip will be set if the popup may be
+ // flipped in that direction if there is not enough space available.
+ nsPoint AdjustPositionForAnchorAlign(nsRect& anchorRect,
+ FlipStyle& aHFlip, FlipStyle& aVFlip);
+
+ // For popups that are going to align to their selected item, get the frame of
+ // the selected item.
+ nsIFrame* GetSelectedItemForAlignment();
+
+ // check if the popup will fit into the available space and resize it. This
+ // method handles only one axis at a time so is called twice, once for
+ // horizontal and once for vertical. All arguments are specified for this
+ // one axis. All coordinates are in app units relative to the screen.
+ // aScreenPoint - the point where the popup should appear
+ // aSize - the size of the popup
+ // aScreenBegin - the left or top edge of the screen
+ // aScreenEnd - the right or bottom edge of the screen
+ // aAnchorBegin - the left or top edge of the anchor rectangle
+ // aAnchorEnd - the right or bottom edge of the anchor rectangle
+ // aMarginBegin - the left or top margin of the popup
+ // aMarginEnd - the right or bottom margin of the popup
+ // aOffsetForContextMenu - the additional offset to add for context menus
+ // aFlip - how to flip or resize the popup when there isn't space
+ // aFlipSide - pointer to where current flip mode is stored
+ nscoord FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
+ nscoord aScreenBegin, nscoord aScreenEnd,
+ nscoord aAnchorBegin, nscoord aAnchorEnd,
+ nscoord aMarginBegin, nscoord aMarginEnd,
+ nscoord aOffsetForContextMenu, FlipStyle aFlip,
+ bool aIsOnEnd, bool* aFlipSide);
+
+ // check if the popup can fit into the available space by "sliding" (i.e.,
+ // by having the anchor arrow slide along one axis and only resizing if that
+ // can't provide the requested size). Only one axis can be slid - the other
+ // axis is "flipped" as normal. This method can handle either axis, but is
+ // only called for the sliding axis. All coordinates are in app units
+ // relative to the screen.
+ // aScreenPoint - the point where the popup should appear
+ // aSize - the size of the popup
+ // aScreenBegin - the left or top edge of the screen
+ // aScreenEnd - the right or bottom edge of the screen
+ // aOffset - the amount by which the arrow must be slid such that it is
+ // still aligned with the anchor.
+ // Result is the new size of the popup, which will typically be the same
+ // as aSize, unless aSize is greater than the screen width/height.
+ nscoord SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
+ nscoord aScreenBegin, nscoord aScreenEnd,
+ nscoord *aOffset);
+
+ // Move the popup to the position specified in its |left| and |top| attributes.
+ void MoveToAttributePosition();
+
+ /**
+ * Return whether the popup direction should be RTL.
+ * If the popup has an anchor, its direction is the anchor direction.
+ * Otherwise, its the general direction of the UI.
+ *
+ * Return whether the popup direction should be RTL.
+ */
+ bool IsDirectionRTL() const {
+ return mAnchorContent && mAnchorContent->GetPrimaryFrame()
+ ? mAnchorContent->GetPrimaryFrame()->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL
+ : StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ }
+
+ // Create a popup view for this frame. The view is added a child of the root
+ // view, and is initially hidden.
+ void CreatePopupView();
+
+ nsString mIncrementalString; // for incremental typing navigation
+
+ // the content that the popup is anchored to, if any, which may be in a
+ // different document than the popup.
+ nsCOMPtr<nsIContent> mAnchorContent;
+
+ // the content that triggered the popup, typically the node where the mouse
+ // was clicked. It will be cleared when the popup is hidden.
+ nsCOMPtr<nsIContent> mTriggerContent;
+
+ nsMenuFrame* mCurrentMenu; // The current menu that is active.
+
+ RefPtr<nsXULPopupShownEvent> mPopupShownDispatcher;
+
+ // The popup's screen rectangle in app units.
+ nsIntRect mUsedScreenRect;
+
+ // A popup's preferred size may be different than its actual size stored in
+ // mRect in the case where the popup was resized because it was too large
+ // for the screen. The preferred size mPrefSize holds the full size the popup
+ // would be before resizing. Computations are performed using this size.
+ nsSize mPrefSize;
+
+ // The position of the popup, in CSS pixels.
+ // The screen coordinates, if set to values other than -1,
+ // override mXPos and mYPos.
+ int32_t mXPos;
+ int32_t mYPos;
+ nsIntRect mScreenRect;
+
+ // If the panel prefers to "slide" rather than resize, then the arrow gets
+ // positioned at this offset (along either the x or y axis, depending on
+ // mPosition)
+ nscoord mAlignmentOffset;
+
+ // The value of the client offset of our widget the last time we positioned
+ // ourselves. We store this so that we can detect when it changes but the
+ // position of our widget didn't change.
+ mozilla::LayoutDeviceIntPoint mLastClientOffset;
+
+ nsPopupType mPopupType; // type of popup
+ nsPopupState mPopupState; // open state of the popup
+
+ // popup alignment relative to the anchor node
+ int8_t mPopupAlignment;
+ int8_t mPopupAnchor;
+ int8_t mPosition;
+
+ // One of PopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME
+ uint8_t mConsumeRollupEvent;
+ FlipType mFlip; // Whether to flip
+
+ struct ReflowCallbackData {
+ ReflowCallbackData() :
+ mPosted(false),
+ mAnchor(nullptr),
+ mSizedToPopup(false)
+ {}
+ void MarkPosted(nsIFrame* aAnchor, bool aSizedToPopup) {
+ mPosted = true;
+ mAnchor = aAnchor;
+ mSizedToPopup = aSizedToPopup;
+ }
+ void Clear() {
+ mPosted = false;
+ mAnchor = nullptr;
+ mSizedToPopup = false;
+ }
+ bool mPosted;
+ nsIFrame* mAnchor;
+ bool mSizedToPopup;
+ };
+ ReflowCallbackData mReflowCallbackData;
+
+ bool mIsOpenChanged; // true if the open state changed since the last layout
+ bool mIsContextMenu; // true for context menus
+ // true if we need to offset the popup to ensure it's not under the mouse
+ bool mAdjustOffsetForContextMenu;
+ bool mGeneratedChildren; // true if the contents have been created
+
+ bool mMenuCanOverlapOSBar; // can we appear over the taskbar/menubar?
+ bool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup?
+ bool mInContentShell; // True if the popup is in a content shell
+ bool mIsMenuLocked; // Should events inside this menu be ignored?
+ bool mMouseTransparent; // True if this is a popup is transparent to mouse events
+
+ // the flip modes that were used when the popup was opened
+ bool mHFlip;
+ bool mVFlip;
+
+ // How the popup is anchored.
+ MenuPopupAnchorType mAnchorType;
+
+ nsRect mOverrideConstraintRect;
+
+ static int8_t sDefaultLevelIsTop;
+
+ static DOMTimeStamp sLastKeyTime;
+
+ // If 0, never timed out. Otherwise, the value is in milliseconds.
+ static uint32_t sTimeoutOfIncrementalSearch;
+}; // class nsMenuPopupFrame
+
+#endif
diff --git a/layout/xul/nsPIBoxObject.h b/layout/xul/nsPIBoxObject.h
new file mode 100644
index 000000000..46ec6ea38
--- /dev/null
+++ b/layout/xul/nsPIBoxObject.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPIBoxObject_h___
+#define nsPIBoxObject_h___
+
+#include "nsIBoxObject.h"
+
+// {2b8bb262-1b0f-4572-ba87-5d4ae4954445}
+#define NS_PIBOXOBJECT_IID \
+{ 0x2b8bb262, 0x1b0f, 0x4572, \
+ { 0xba, 0x87, 0x5d, 0x4a, 0xe4, 0x95, 0x44, 0x45 } }
+
+
+class nsIContent;
+
+class nsPIBoxObject : public nsIBoxObject
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIBOXOBJECT_IID)
+
+ virtual nsresult Init(nsIContent* aContent) = 0;
+
+ // Drop the weak ref to the content node as needed
+ virtual void Clear() = 0;
+
+ // The values cached by the implementation of this interface should be
+ // cleared when this method is called.
+ virtual void ClearCachedValues() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIBoxObject, NS_PIBOXOBJECT_IID)
+
+#endif
+
diff --git a/layout/xul/nsPIListBoxObject.h b/layout/xul/nsPIListBoxObject.h
new file mode 100644
index 000000000..c7b9928f0
--- /dev/null
+++ b/layout/xul/nsPIListBoxObject.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPIListBoxObject_h__
+#define nsPIListBoxObject_h__
+
+class nsListBoxBodyFrame;
+
+// fa9549f7-ee09-48fc-89f7-30cceee21c15
+#define NS_PILISTBOXOBJECT_IID \
+{ 0xfa9549f7, 0xee09, 0x48fc, \
+ { 0x89, 0xf7, 0x30, 0xcc, 0xee, 0xe2, 0x1c, 0x15 } }
+
+#include "nsIListBoxObject.h"
+
+class nsPIListBoxObject : public nsIListBoxObject {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PILISTBOXOBJECT_IID)
+ /**
+ * Get the list box body. This will search for it as needed.
+ * If aFlush is false we don't Flush_Frames though.
+ */
+ virtual nsListBoxBodyFrame* GetListBoxBody(bool aFlush) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIListBoxObject, NS_PILISTBOXOBJECT_IID)
+
+#endif // nsPIListBoxObject_h__
diff --git a/layout/xul/nsPopupSetFrame.cpp b/layout/xul/nsPopupSetFrame.cpp
new file mode 100644
index 000000000..a627ee079
--- /dev/null
+++ b/layout/xul/nsPopupSetFrame.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPopupSetFrame.h"
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsBoxLayoutState.h"
+#include "nsIScrollableFrame.h"
+#include "nsIRootBox.h"
+#include "nsMenuPopupFrame.h"
+
+nsIFrame*
+NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsPopupSetFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPopupSetFrame)
+
+void
+nsPopupSetFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // Normally the root box is our grandparent, but in case of wrapping
+ // it can be our great-grandparent.
+ nsIRootBox *rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox) {
+ rootBox->SetPopupSetFrame(this);
+ }
+}
+
+nsIAtom*
+nsPopupSetFrame::GetType() const
+{
+ return nsGkAtoms::popupSetFrame;
+}
+
+void
+nsPopupSetFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ if (aListID == kPopupList) {
+ AddPopupFrameList(aFrameList);
+ return;
+ }
+ nsBoxFrame::AppendFrames(aListID, aFrameList);
+}
+
+void
+nsPopupSetFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ if (aListID == kPopupList) {
+ RemovePopupFrame(aOldFrame);
+ return;
+ }
+ nsBoxFrame::RemoveFrame(aListID, aOldFrame);
+}
+
+void
+nsPopupSetFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ if (aListID == kPopupList) {
+ AddPopupFrameList(aFrameList);
+ return;
+ }
+ nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+}
+
+void
+nsPopupSetFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (aListID == kPopupList) {
+ NS_ASSERTION(mPopupList.IsEmpty(),
+ "SetInitialChildList on non-empty child list");
+ AddPopupFrameList(aChildList);
+ return;
+ }
+ nsBoxFrame::SetInitialChildList(aListID, aChildList);
+}
+
+const nsFrameList&
+nsPopupSetFrame::GetChildList(ChildListID aListID) const
+{
+ if (kPopupList == aListID) {
+ return mPopupList;
+ }
+ return nsBoxFrame::GetChildList(aListID);
+}
+
+void
+nsPopupSetFrame::GetChildLists(nsTArray<ChildList>* aLists) const
+{
+ nsBoxFrame::GetChildLists(aLists);
+ mPopupList.AppendIfNonempty(aLists, kPopupList);
+}
+
+void
+nsPopupSetFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ mPopupList.DestroyFramesFrom(aDestructRoot);
+
+ // Normally the root box is our grandparent, but in case of wrapping
+ // it can be our great-grandparent.
+ nsIRootBox *rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox) {
+ rootBox->SetPopupSetFrame(nullptr);
+ }
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+NS_IMETHODIMP
+nsPopupSetFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ // lay us out
+ nsresult rv = nsBoxFrame::DoXULLayout(aState);
+
+ // lay out all of our currently open popups.
+ for (nsFrameList::Enumerator e(mPopupList); !e.AtEnd(); e.Next()) {
+ nsMenuPopupFrame* popupChild = static_cast<nsMenuPopupFrame*>(e.get());
+ popupChild->LayoutPopup(aState, nullptr, nullptr, false);
+ }
+
+ return rv;
+}
+
+void
+nsPopupSetFrame::RemovePopupFrame(nsIFrame* aPopup)
+{
+ NS_PRECONDITION((aPopup->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ aPopup->GetType() == nsGkAtoms::menuPopupFrame,
+ "removing wrong type of frame in popupset's ::popupList");
+
+ mPopupList.DestroyFrame(aPopup);
+}
+
+void
+nsPopupSetFrame::AddPopupFrameList(nsFrameList& aPopupFrameList)
+{
+#ifdef DEBUG
+ for (nsFrameList::Enumerator e(aPopupFrameList); !e.AtEnd(); e.Next()) {
+ NS_ASSERTION((e.get()->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ e.get()->GetType() == nsGkAtoms::menuPopupFrame,
+ "adding wrong type of frame in popupset's ::popupList");
+ }
+#endif
+ mPopupList.InsertFrames(nullptr, nullptr, aPopupFrameList);
+}
diff --git a/layout/xul/nsPopupSetFrame.h b/layout/xul/nsPopupSetFrame.h
new file mode 100644
index 000000000..1981b996d
--- /dev/null
+++ b/layout/xul/nsPopupSetFrame.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPopupSetFrame_h__
+#define nsPopupSetFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+#include "nsBoxFrame.h"
+
+nsIFrame* NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsPopupSetFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsPopupSetFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext) {}
+
+ ~nsPopupSetFrame() {}
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+
+ virtual const nsFrameList& GetChildList(ChildListID aList) const override;
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+
+ // Used to destroy our popup frames.
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("PopupSet"), aResult);
+ }
+#endif
+
+protected:
+ void AddPopupFrameList(nsFrameList& aPopupFrameList);
+ void RemovePopupFrame(nsIFrame* aPopup);
+
+ nsFrameList mPopupList;
+};
+
+#endif
diff --git a/layout/xul/nsProgressMeterFrame.cpp b/layout/xul/nsProgressMeterFrame.cpp
new file mode 100644
index 000000000..a798d5717
--- /dev/null
+++ b/layout/xul/nsProgressMeterFrame.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// David Hyatt & Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsProgressMeterFrame.h"
+#include "nsCSSRendering.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsCOMPtr.h"
+#include "nsBoxLayoutState.h"
+#include "nsIReflowCallback.h"
+#include "nsContentUtils.h"
+#include "mozilla/Attributes.h"
+
+class nsReflowFrameRunnable : public mozilla::Runnable
+{
+public:
+ nsReflowFrameRunnable(nsIFrame* aFrame,
+ nsIPresShell::IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd);
+
+ NS_DECL_NSIRUNNABLE
+
+ nsWeakFrame mWeakFrame;
+ nsIPresShell::IntrinsicDirty mIntrinsicDirty;
+ nsFrameState mBitToAdd;
+};
+
+nsReflowFrameRunnable::nsReflowFrameRunnable(nsIFrame* aFrame,
+ nsIPresShell::IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd)
+ : mWeakFrame(aFrame),
+ mIntrinsicDirty(aIntrinsicDirty),
+ mBitToAdd(aBitToAdd)
+{
+}
+
+NS_IMETHODIMP
+nsReflowFrameRunnable::Run()
+{
+ if (mWeakFrame.IsAlive()) {
+ mWeakFrame->PresContext()->PresShell()->
+ FrameNeedsReflow(mWeakFrame, mIntrinsicDirty, mBitToAdd);
+ }
+ return NS_OK;
+}
+
+//
+// NS_NewToolbarFrame
+//
+// Creates a new Toolbar frame and returns it
+//
+nsIFrame*
+NS_NewProgressMeterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsProgressMeterFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsProgressMeterFrame)
+
+//
+// nsProgressMeterFrame dstr
+//
+// Cleanup, if necessary
+//
+nsProgressMeterFrame :: ~nsProgressMeterFrame ( )
+{
+}
+
+class nsAsyncProgressMeterInit final : public nsIReflowCallback
+{
+public:
+ explicit nsAsyncProgressMeterInit(nsIFrame* aFrame) : mWeakFrame(aFrame) {}
+
+ virtual bool ReflowFinished() override
+ {
+ bool shouldFlush = false;
+ nsIFrame* frame = mWeakFrame.GetFrame();
+ if (frame) {
+ nsAutoScriptBlocker scriptBlocker;
+ frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::mode, 0);
+ shouldFlush = true;
+ }
+ delete this;
+ return shouldFlush;
+ }
+
+ virtual void ReflowCallbackCanceled() override
+ {
+ delete this;
+ }
+
+ nsWeakFrame mWeakFrame;
+};
+
+NS_IMETHODIMP
+nsProgressMeterFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ if (mNeedsReflowCallback) {
+ nsIReflowCallback* cb = new nsAsyncProgressMeterInit(this);
+ if (cb) {
+ PresContext()->PresShell()->PostReflowCallback(cb);
+ }
+ mNeedsReflowCallback = false;
+ }
+ return nsBoxFrame::DoXULLayout(aState);
+}
+
+nsresult
+nsProgressMeterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Scripts not blocked in nsProgressMeterFrame::AttributeChanged!");
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+ if (NS_OK != rv) {
+ return rv;
+ }
+
+ // did the progress change?
+ bool undetermined = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mode,
+ nsGkAtoms::undetermined, eCaseMatters);
+ if (nsGkAtoms::mode == aAttribute ||
+ (!undetermined &&
+ (nsGkAtoms::value == aAttribute || nsGkAtoms::max == aAttribute))) {
+ nsIFrame* barChild = PrincipalChildList().FirstChild();
+ if (!barChild) return NS_OK;
+ nsIFrame* remainderChild = barChild->GetNextSibling();
+ if (!remainderChild) return NS_OK;
+ nsCOMPtr<nsIContent> remainderContent = remainderChild->GetContent();
+ if (!remainderContent) return NS_OK;
+
+ int32_t flex = 1, maxFlex = 1;
+ if (!undetermined) {
+ nsAutoString value, maxValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxValue);
+
+ nsresult error;
+ flex = value.ToInteger(&error);
+ maxFlex = maxValue.ToInteger(&error);
+ if (NS_FAILED(error) || maxValue.IsEmpty()) {
+ maxFlex = 100;
+ }
+ if (maxFlex < 1) {
+ maxFlex = 1;
+ }
+ if (flex < 0) {
+ flex = 0;
+ }
+ if (flex > maxFlex) {
+ flex = maxFlex;
+ }
+ }
+
+ nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
+ barChild->GetContent(), nsGkAtoms::flex, flex));
+ nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
+ remainderContent, nsGkAtoms::flex, maxFlex - flex));
+ nsContentUtils::AddScriptRunner(new nsReflowFrameRunnable(
+ this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY));
+ }
+ return NS_OK;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsProgressMeterFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ProgressMeter"), aResult);
+}
+#endif
diff --git a/layout/xul/nsProgressMeterFrame.h b/layout/xul/nsProgressMeterFrame.h
new file mode 100644
index 000000000..0a4af7ff4
--- /dev/null
+++ b/layout/xul/nsProgressMeterFrame.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ David Hyatt & Eric D Vaughan.
+
+ An XBL-based progress meter.
+
+ Attributes:
+
+ value: A number between 0% and 100%
+ align: horizontal or vertical
+ mode: determined, undetermined (one shows progress other shows animated candy cane)
+
+**/
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsProgressMeterFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewProgressMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+protected:
+ explicit nsProgressMeterFrame(nsStyleContext* aContext) :
+ nsBoxFrame(aContext), mNeedsReflowCallback(true) {}
+ virtual ~nsProgressMeterFrame();
+
+ bool mNeedsReflowCallback;
+}; // class nsProgressMeterFrame
diff --git a/layout/xul/nsRepeatService.cpp b/layout/xul/nsRepeatService.cpp
new file mode 100644
index 000000000..919763d4d
--- /dev/null
+++ b/layout/xul/nsRepeatService.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsRepeatService.h"
+#include "nsIServiceManager.h"
+
+nsRepeatService* nsRepeatService::gInstance = nullptr;
+
+nsRepeatService::nsRepeatService()
+: mCallback(nullptr), mCallbackData(nullptr)
+{
+}
+
+nsRepeatService::~nsRepeatService()
+{
+ NS_ASSERTION(!mCallback && !mCallbackData, "Callback was not removed before shutdown");
+}
+
+nsRepeatService*
+nsRepeatService::GetInstance()
+{
+ if (!gInstance) {
+ gInstance = new nsRepeatService();
+ NS_IF_ADDREF(gInstance);
+ }
+ return gInstance;
+}
+
+/*static*/ void
+nsRepeatService::Shutdown()
+{
+ NS_IF_RELEASE(gInstance);
+}
+
+void nsRepeatService::Start(Callback aCallback, void* aCallbackData,
+ uint32_t aInitialDelay)
+{
+ NS_PRECONDITION(aCallback != nullptr, "null ptr");
+
+ mCallback = aCallback;
+ mCallbackData = aCallbackData;
+ nsresult rv;
+ mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ mRepeatTimer->InitWithCallback(this, aInitialDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+void nsRepeatService::Stop(Callback aCallback, void* aCallbackData)
+{
+ if (mCallback != aCallback || mCallbackData != aCallbackData)
+ return;
+
+ //printf("Stopping repeat timer\n");
+ if (mRepeatTimer) {
+ mRepeatTimer->Cancel();
+ mRepeatTimer = nullptr;
+ }
+ mCallback = nullptr;
+ mCallbackData = nullptr;
+}
+
+NS_IMETHODIMP nsRepeatService::Notify(nsITimer *timer)
+{
+ // do callback
+ if (mCallback)
+ mCallback(mCallbackData);
+
+ // start timer again.
+ if (mRepeatTimer) {
+ mRepeatTimer->InitWithCallback(this, REPEAT_DELAY, nsITimer::TYPE_ONE_SHOT);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsRepeatService, nsITimerCallback)
diff --git a/layout/xul/nsRepeatService.h b/layout/xul/nsRepeatService.h
new file mode 100644
index 000000000..81321a472
--- /dev/null
+++ b/layout/xul/nsRepeatService.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsRepeatService
+//
+#ifndef nsRepeatService_h__
+#define nsRepeatService_h__
+
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+
+#define INITAL_REPEAT_DELAY 250
+
+#ifdef XP_MACOSX
+#define REPEAT_DELAY 25
+#else
+#define REPEAT_DELAY 50
+#endif
+
+class nsITimer;
+
+class nsRepeatService final : public nsITimerCallback
+{
+public:
+
+ typedef void (* Callback)(void* aData);
+
+ NS_DECL_NSITIMERCALLBACK
+
+ // Start dispatching timer events to the callback. There is no memory
+ // management of aData here; it is the caller's responsibility to call
+ // Stop() before aData's memory is released.
+ void Start(Callback aCallback, void* aData,
+ uint32_t aInitialDelay = INITAL_REPEAT_DELAY);
+ // Stop dispatching timer events to the callback. If the repeat service
+ // is not currently configured with the given callback and data, this
+ // is just ignored.
+ void Stop(Callback aCallback, void* aData);
+
+ static nsRepeatService* GetInstance();
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+
+protected:
+ nsRepeatService();
+ virtual ~nsRepeatService();
+
+private:
+ Callback mCallback;
+ void* mCallbackData;
+ nsCOMPtr<nsITimer> mRepeatTimer;
+ static nsRepeatService* gInstance;
+
+}; // class nsRepeatService
+
+#endif
diff --git a/layout/xul/nsResizerFrame.cpp b/layout/xul/nsResizerFrame.cpp
new file mode 100644
index 000000000..b32b84fe5
--- /dev/null
+++ b/layout/xul/nsResizerFrame.cpp
@@ -0,0 +1,538 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsResizerFrame.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+
+#include "nsPresContext.h"
+#include "nsFrameManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIBaseWindow.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/MouseEvents.h"
+#include "nsContentUtils.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIScreenManager.h"
+#include "mozilla/dom/Element.h"
+#include "nsError.h"
+#include "nsICSSDeclaration.h"
+#include "nsStyledElement.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// NS_NewResizerFrame
+//
+// Creates a new Resizer frame and returns it
+//
+nsIFrame*
+NS_NewResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsResizerFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsResizerFrame)
+
+nsResizerFrame::nsResizerFrame(nsStyleContext* aContext)
+:nsTitleBarFrame(aContext)
+{
+}
+
+nsresult
+nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ nsWeakFrame weakFrame(this);
+ bool doDefault = true;
+
+ switch (aEvent->mMessage) {
+ case eTouchStart:
+ case eMouseDown: {
+ if (aEvent->mClass == eTouchEventClass ||
+ (aEvent->mClass == eMouseEventClass &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton)) {
+ nsCOMPtr<nsIBaseWindow> window;
+ nsIPresShell* presShell = aPresContext->GetPresShell();
+ nsIContent* contentToResize =
+ GetContentToResize(presShell, getter_AddRefs(window));
+ if (contentToResize) {
+ nsIFrame* frameToResize = contentToResize->GetPrimaryFrame();
+ if (!frameToResize)
+ break;
+
+ // cache the content rectangle for the frame to resize
+ // GetScreenRectInAppUnits returns the border box rectangle, so
+ // adjust to get the desired content rectangle.
+ nsRect rect = frameToResize->GetScreenRectInAppUnits();
+ if (frameToResize->StylePosition()->mBoxSizing == StyleBoxSizing::Content) {
+ rect.Deflate(frameToResize->GetUsedBorderAndPadding());
+ }
+
+ mMouseDownRect =
+ LayoutDeviceIntRect::FromAppUnitsToNearest(rect, aPresContext->AppUnitsPerDevPixel());
+ doDefault = false;
+ }
+ else {
+ // If there is no window, then resizing isn't allowed.
+ if (!window)
+ break;
+
+ doDefault = false;
+
+ // ask the widget implementation to begin a resize drag if it can
+ Direction direction = GetDirection();
+ nsresult rv = aEvent->mWidget->BeginResizeDrag(aEvent,
+ direction.mHorizontal, direction.mVertical);
+ // for native drags, don't set the fields below
+ if (rv != NS_ERROR_NOT_IMPLEMENTED)
+ break;
+
+ // if there's no native resize support, we need to do window
+ // resizing ourselves
+ window->GetPositionAndSize(&mMouseDownRect.x, &mMouseDownRect.y,
+ &mMouseDownRect.width, &mMouseDownRect.height);
+ }
+
+ // remember current mouse coordinates
+ LayoutDeviceIntPoint refPoint;
+ if (!GetEventPoint(aEvent, refPoint))
+ return NS_OK;
+ mMouseDownPoint = refPoint + aEvent->mWidget->WidgetToScreenOffset();
+
+ // we're tracking
+ mTrackingMouseMove = true;
+
+ nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED);
+ }
+ }
+ break;
+
+ case eTouchEnd:
+ case eMouseUp: {
+ if (aEvent->mClass == eTouchEventClass ||
+ (aEvent->mClass == eMouseEventClass &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton)) {
+ // we're done tracking.
+ mTrackingMouseMove = false;
+
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+
+ doDefault = false;
+ }
+ }
+ break;
+
+ case eTouchMove:
+ case eMouseMove: {
+ if (mTrackingMouseMove)
+ {
+ nsCOMPtr<nsIBaseWindow> window;
+ nsIPresShell* presShell = aPresContext->GetPresShell();
+ nsCOMPtr<nsIContent> contentToResize =
+ GetContentToResize(presShell, getter_AddRefs(window));
+
+ // check if the returned content really is a menupopup
+ nsMenuPopupFrame* menuPopupFrame = nullptr;
+ if (contentToResize) {
+ menuPopupFrame = do_QueryFrame(contentToResize->GetPrimaryFrame());
+ }
+
+ // both MouseMove and direction are negative when pointing to the
+ // top and left, and positive when pointing to the bottom and right
+
+ // retrieve the offset of the mousemove event relative to the mousedown.
+ // The difference is how much the resize needs to be
+ LayoutDeviceIntPoint refPoint;
+ if (!GetEventPoint(aEvent, refPoint))
+ return NS_OK;
+ LayoutDeviceIntPoint screenPoint =
+ refPoint + aEvent->mWidget->WidgetToScreenOffset();
+ LayoutDeviceIntPoint mouseMove(screenPoint - mMouseDownPoint);
+
+ // Determine which direction to resize by checking the dir attribute.
+ // For windows and menus, ensure that it can be resized in that direction.
+ Direction direction = GetDirection();
+ if (window || menuPopupFrame) {
+ if (menuPopupFrame) {
+ menuPopupFrame->CanAdjustEdges(
+ (direction.mHorizontal == -1) ? NS_SIDE_LEFT : NS_SIDE_RIGHT,
+ (direction.mVertical == -1) ? NS_SIDE_TOP : NS_SIDE_BOTTOM, mouseMove);
+ }
+ }
+ else if (!contentToResize) {
+ break; // don't do anything if there's nothing to resize
+ }
+
+ LayoutDeviceIntRect rect = mMouseDownRect;
+
+ // Check if there are any size constraints on this window.
+ widget::SizeConstraints sizeConstraints;
+ if (window) {
+ nsCOMPtr<nsIWidget> widget;
+ window->GetMainWidget(getter_AddRefs(widget));
+ sizeConstraints = widget->GetSizeConstraints();
+ }
+
+ AdjustDimensions(&rect.x, &rect.width, sizeConstraints.mMinSize.width,
+ sizeConstraints.mMaxSize.width, mouseMove.x, direction.mHorizontal);
+ AdjustDimensions(&rect.y, &rect.height, sizeConstraints.mMinSize.height,
+ sizeConstraints.mMaxSize.height, mouseMove.y, direction.mVertical);
+
+ // Don't allow resizing a window or a popup past the edge of the screen,
+ // so adjust the rectangle to fit within the available screen area.
+ if (window) {
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
+ if (sm) {
+ nsIntRect frameRect = GetScreenRect();
+ // ScreenForRect requires display pixels, so scale from device pix
+ double scale;
+ window->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+ sm->ScreenForRect(NSToIntRound(frameRect.x / scale),
+ NSToIntRound(frameRect.y / scale), 1, 1,
+ getter_AddRefs(screen));
+ if (screen) {
+ LayoutDeviceIntRect screenRect;
+ screen->GetRect(&screenRect.x, &screenRect.y,
+ &screenRect.width, &screenRect.height);
+ rect.IntersectRect(rect, screenRect);
+ }
+ }
+ }
+ else if (menuPopupFrame) {
+ nsRect frameRect = menuPopupFrame->GetScreenRectInAppUnits();
+ nsIFrame* rootFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
+ nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
+
+ nsPopupLevel popupLevel = menuPopupFrame->PopupLevel();
+ int32_t appPerDev = aPresContext->AppUnitsPerDevPixel();
+ LayoutDeviceIntRect screenRect = menuPopupFrame->GetConstraintRect
+ (LayoutDeviceIntRect::FromAppUnitsToNearest(frameRect, appPerDev),
+ // round using ...ToInside as it's better to be a pixel too small
+ // than be too large. If the popup is too large it could get flipped
+ // to the opposite side of the anchor point while resizing.
+ LayoutDeviceIntRect::FromAppUnitsToInside(rootScreenRect, appPerDev),
+ popupLevel);
+ rect.IntersectRect(rect, screenRect);
+ }
+
+ if (contentToResize) {
+ // convert the rectangle into css pixels. When changing the size in a
+ // direction, don't allow the new size to be less that the resizer's
+ // size. This ensures that content isn't resized too small as to make
+ // the resizer invisible.
+ nsRect appUnitsRect = ToAppUnits(rect.ToUnknownRect(), aPresContext->AppUnitsPerDevPixel());
+ if (appUnitsRect.width < mRect.width && mouseMove.x)
+ appUnitsRect.width = mRect.width;
+ if (appUnitsRect.height < mRect.height && mouseMove.y)
+ appUnitsRect.height = mRect.height;
+ nsIntRect cssRect = appUnitsRect.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel());
+
+ LayoutDeviceIntRect oldRect;
+ nsWeakFrame weakFrame(menuPopupFrame);
+ if (menuPopupFrame) {
+ nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget();
+ if (widget)
+ oldRect = widget->GetScreenBounds();
+
+ // convert the new rectangle into outer window coordinates
+ LayoutDeviceIntPoint clientOffset = widget->GetClientOffset();
+ rect.x -= clientOffset.x;
+ rect.y -= clientOffset.y;
+ }
+
+ SizeInfo sizeInfo, originalSizeInfo;
+ sizeInfo.width.AppendInt(cssRect.width);
+ sizeInfo.height.AppendInt(cssRect.height);
+ ResizeContent(contentToResize, direction, sizeInfo, &originalSizeInfo);
+ MaybePersistOriginalSize(contentToResize, originalSizeInfo);
+
+ // Move the popup to the new location unless it is anchored, since
+ // the position shouldn't change. nsMenuPopupFrame::SetPopupPosition
+ // will instead ensure that the popup's position is anchored at the
+ // right place.
+ if (weakFrame.IsAlive() &&
+ (oldRect.x != rect.x || oldRect.y != rect.y) &&
+ (!menuPopupFrame->IsAnchored() ||
+ menuPopupFrame->PopupLevel() != ePopupLevelParent)) {
+
+ CSSPoint cssPos = rect.TopLeft() / aPresContext->CSSToDevPixelScale();
+ menuPopupFrame->MoveTo(RoundedToInt(cssPos), true);
+ }
+ }
+ else {
+ window->SetPositionAndSize(rect.x, rect.y, rect.width, rect.height,
+ nsIBaseWindow::eRepaint); // do the repaint.
+ }
+
+ doDefault = false;
+ }
+ }
+ break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ MouseClicked(mouseEvent);
+ }
+ break;
+ }
+ case eMouseDoubleClick:
+ if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+ nsCOMPtr<nsIBaseWindow> window;
+ nsIPresShell* presShell = aPresContext->GetPresShell();
+ nsIContent* contentToResize =
+ GetContentToResize(presShell, getter_AddRefs(window));
+ if (contentToResize) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(contentToResize->GetPrimaryFrame());
+ if (menuPopupFrame)
+ break; // Don't restore original sizing for menupopup frames until
+ // we handle screen constraints here. (Bug 357725)
+
+ RestoreOriginalSize(contentToResize);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!doDefault)
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+
+ if (doDefault && weakFrame.IsAlive())
+ return nsTitleBarFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+
+ return NS_OK;
+}
+
+nsIContent*
+nsResizerFrame::GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWindow)
+{
+ *aWindow = nullptr;
+
+ nsAutoString elementid;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::element, elementid);
+ if (elementid.IsEmpty()) {
+ // If the resizer is in a popup, resize the popup's widget, otherwise
+ // resize the widget associated with the window.
+ nsIFrame* popup = GetParent();
+ while (popup) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(popup);
+ if (popupFrame) {
+ return popupFrame->GetContent();
+ }
+ popup = popup->GetParent();
+ }
+
+ // don't allow resizing windows in content shells
+ nsCOMPtr<nsIDocShellTreeItem> dsti = aPresShell->GetPresContext()->GetDocShell();
+ if (!dsti || dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ // don't allow resizers in content shells, except for the viewport
+ // scrollbar which doesn't have a parent
+ nsIContent* nonNativeAnon = mContent->FindFirstNonChromeOnlyAccessContent();
+ if (!nonNativeAnon || nonNativeAnon->GetParent()) {
+ return nullptr;
+ }
+ }
+
+ // get the document and the window - should this be cached?
+ if (nsPIDOMWindowOuter* domWindow = aPresShell->GetDocument()->GetWindow()) {
+ nsCOMPtr<nsIDocShell> docShell = domWindow->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (treeOwner) {
+ CallQueryInterface(treeOwner, aWindow);
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ if (elementid.EqualsLiteral("_parent")) {
+ // return the parent, but skip over native anonymous content
+ nsIContent* parent = mContent->GetParent();
+ return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
+ }
+
+ return aPresShell->GetDocument()->GetElementById(elementid);
+}
+
+void
+nsResizerFrame::AdjustDimensions(int32_t* aPos, int32_t* aSize,
+ int32_t aMinSize, int32_t aMaxSize,
+ int32_t aMovement, int8_t aResizerDirection)
+{
+ int32_t oldSize = *aSize;
+
+ *aSize += aResizerDirection * aMovement;
+ // use one as a minimum size or the element could disappear
+ if (*aSize < 1)
+ *aSize = 1;
+
+ // Constrain the size within the minimum and maximum size.
+ *aSize = std::max(aMinSize, std::min(aMaxSize, *aSize));
+
+ // For left and top resizers, the window must be moved left by the same
+ // amount that the window was resized.
+ if (aResizerDirection == -1)
+ *aPos += oldSize - *aSize;
+}
+
+/* static */ void
+nsResizerFrame::ResizeContent(nsIContent* aContent, const Direction& aDirection,
+ const SizeInfo& aSizeInfo, SizeInfo* aOriginalSizeInfo)
+{
+ // for XUL elements, just set the width and height attributes. For
+ // other elements, set style.width and style.height
+ if (aContent->IsXULElement()) {
+ if (aOriginalSizeInfo) {
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width,
+ aOriginalSizeInfo->width);
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height,
+ aOriginalSizeInfo->height);
+ }
+ // only set the property if the element could have changed in that direction
+ if (aDirection.mHorizontal) {
+ aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::width, aSizeInfo.width, true);
+ }
+ if (aDirection.mVertical) {
+ aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::height, aSizeInfo.height, true);
+ }
+ }
+ else {
+ nsCOMPtr<nsStyledElement> inlineStyleContent =
+ do_QueryInterface(aContent);
+ if (inlineStyleContent) {
+ nsICSSDeclaration* decl = inlineStyleContent->Style();
+
+ if (aOriginalSizeInfo) {
+ decl->GetPropertyValue(NS_LITERAL_STRING("width"),
+ aOriginalSizeInfo->width);
+ decl->GetPropertyValue(NS_LITERAL_STRING("height"),
+ aOriginalSizeInfo->height);
+ }
+
+ // only set the property if the element could have changed in that direction
+ if (aDirection.mHorizontal) {
+ nsAutoString widthstr(aSizeInfo.width);
+ if (!widthstr.IsEmpty() &&
+ !Substring(widthstr, widthstr.Length() - 2, 2).EqualsLiteral("px"))
+ widthstr.AppendLiteral("px");
+ decl->SetProperty(NS_LITERAL_STRING("width"), widthstr, EmptyString());
+ }
+ if (aDirection.mVertical) {
+ nsAutoString heightstr(aSizeInfo.height);
+ if (!heightstr.IsEmpty() &&
+ !Substring(heightstr, heightstr.Length() - 2, 2).EqualsLiteral("px"))
+ heightstr.AppendLiteral("px");
+ decl->SetProperty(NS_LITERAL_STRING("height"), heightstr, EmptyString());
+ }
+ }
+ }
+}
+
+/* static */ void
+nsResizerFrame::MaybePersistOriginalSize(nsIContent* aContent,
+ const SizeInfo& aSizeInfo)
+{
+ nsresult rv;
+
+ aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
+ if (rv != NS_PROPTABLE_PROP_NOT_THERE)
+ return;
+
+ nsAutoPtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo));
+ rv = aContent->SetProperty(nsGkAtoms::_moz_original_size, sizeInfo.get(),
+ nsINode::DeleteProperty<nsResizerFrame::SizeInfo>);
+ if (NS_SUCCEEDED(rv))
+ sizeInfo.forget();
+}
+
+/* static */ void
+nsResizerFrame::RestoreOriginalSize(nsIContent* aContent)
+{
+ nsresult rv;
+ SizeInfo* sizeInfo =
+ static_cast<SizeInfo*>(aContent->GetProperty(nsGkAtoms::_moz_original_size,
+ &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?");
+ Direction direction = {1, 1};
+ ResizeContent(aContent, direction, *sizeInfo, nullptr);
+ aContent->DeleteProperty(nsGkAtoms::_moz_original_size);
+}
+
+/* returns a Direction struct containing the horizontal and vertical direction
+ */
+nsResizerFrame::Direction
+nsResizerFrame::GetDirection()
+{
+ static const nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::topleft, &nsGkAtoms::top, &nsGkAtoms::topright,
+ &nsGkAtoms::left, &nsGkAtoms::right,
+ &nsGkAtoms::bottomleft, &nsGkAtoms::bottom, &nsGkAtoms::bottomright,
+ &nsGkAtoms::bottomstart, &nsGkAtoms::bottomend,
+ nullptr};
+
+ static const Direction directions[] =
+ {{-1, -1}, {0, -1}, {1, -1},
+ {-1, 0}, {1, 0},
+ {-1, 1}, {0, 1}, {1, 1},
+ {-1, 1}, {1, 1}
+ };
+
+ if (!GetContent()) {
+ return directions[0]; // default: topleft
+ }
+
+ int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::dir,
+ strings, eCaseMatters);
+ if (index < 0) {
+ return directions[0]; // default: topleft
+ }
+
+ if (index >= 8) {
+ // Directions 8 and higher are RTL-aware directions and should reverse the
+ // horizontal component if RTL.
+ WritingMode wm = GetWritingMode();
+ if (!(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR())) {
+ Direction direction = directions[index];
+ direction.mHorizontal *= -1;
+ return direction;
+ }
+ }
+
+ return directions[index];
+}
+
+void
+nsResizerFrame::MouseClicked(WidgetMouseEvent* aEvent)
+{
+ // Execute the oncommand event handler.
+ nsContentUtils::DispatchXULCommand(mContent, aEvent && aEvent->IsTrusted());
+}
diff --git a/layout/xul/nsResizerFrame.h b/layout/xul/nsResizerFrame.h
new file mode 100644
index 000000000..92656bc76
--- /dev/null
+++ b/layout/xul/nsResizerFrame.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsResizerFrame_h___
+#define nsResizerFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsTitleBarFrame.h"
+
+class nsIBaseWindow;
+
+class nsResizerFrame : public nsTitleBarFrame
+{
+protected:
+ struct Direction {
+ int8_t mHorizontal;
+ int8_t mVertical;
+ };
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ explicit nsResizerFrame(nsStyleContext* aContext);
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void MouseClicked(mozilla::WidgetMouseEvent* aEvent) override;
+
+protected:
+ nsIContent* GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWindow);
+
+ Direction GetDirection();
+
+ /**
+ * Adjust the window position and size in a direction according to the mouse
+ * movement and the resizer direction. The minimum and maximum size is used
+ * to constrain the size.
+ *
+ * @param aPos left or top position
+ * @param aSize width or height
+ * @param aMinSize minimum width or height
+ * @param aMacSize maximum width or height
+ * @param aMovement the amount the mouse was moved
+ * @param aResizerDirection resizer direction returned by GetDirection
+ */
+ static void AdjustDimensions(int32_t* aPos, int32_t* aSize,
+ int32_t aMinSize, int32_t aMaxSize,
+ int32_t aMovement, int8_t aResizerDirection);
+
+ struct SizeInfo {
+ nsString width, height;
+ };
+ static void SizeInfoDtorFunc(void *aObject, nsIAtom *aPropertyName,
+ void *aPropertyValue, void *aData);
+ static void ResizeContent(nsIContent* aContent, const Direction& aDirection,
+ const SizeInfo& aSizeInfo, SizeInfo* aOriginalSizeInfo);
+ static void MaybePersistOriginalSize(nsIContent* aContent, const SizeInfo& aSizeInfo);
+ static void RestoreOriginalSize(nsIContent* aContent);
+
+protected:
+ LayoutDeviceIntRect mMouseDownRect;
+ LayoutDeviceIntPoint mMouseDownPoint;
+}; // class nsResizerFrame
+
+#endif /* nsResizerFrame_h___ */
diff --git a/layout/xul/nsRootBoxFrame.cpp b/layout/xul/nsRootBoxFrame.cpp
new file mode 100644
index 000000000..fe41dce52
--- /dev/null
+++ b/layout/xul/nsRootBoxFrame.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHTMLParts.h"
+#include "nsStyleConsts.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsBoxFrame.h"
+#include "nsStackLayout.h"
+#include "nsIRootBox.h"
+#include "nsIContent.h"
+#include "nsXULTooltipListener.h"
+#include "nsFrameManager.h"
+#include "mozilla/BasicEvents.h"
+
+using namespace mozilla;
+
+// Interface IDs
+
+//#define DEBUG_REFLOW
+
+// static
+nsIRootBox*
+nsIRootBox::GetRootBox(nsIPresShell* aShell)
+{
+ if (!aShell) {
+ return nullptr;
+ }
+ nsIFrame* rootFrame = aShell->FrameManager()->GetRootFrame();
+ if (!rootFrame) {
+ return nullptr;
+ }
+
+ if (rootFrame) {
+ rootFrame = rootFrame->PrincipalChildList().FirstChild();
+ }
+
+ nsIRootBox* rootBox = do_QueryFrame(rootFrame);
+ return rootBox;
+}
+
+class nsRootBoxFrame : public nsBoxFrame, public nsIRootBox {
+public:
+
+ friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ explicit nsRootBoxFrame(nsStyleContext* aContext);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual nsPopupSetFrame* GetPopupSetFrame() override;
+ virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) override;
+ virtual nsIContent* GetDefaultTooltip() override;
+ virtual void SetDefaultTooltip(nsIContent* aTooltip) override;
+ virtual nsresult AddTooltipSupport(nsIContent* aNode) override;
+ virtual nsresult RemoveTooltipSupport(nsIContent* aNode) override;
+
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::rootFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // Override bogus IsFrameOfType in nsBoxFrame.
+ if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced))
+ return false;
+ return nsBoxFrame::IsFrameOfType(aFlags);
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ nsPopupSetFrame* mPopupSetFrame;
+
+protected:
+ nsIContent* mDefaultTooltip;
+};
+
+//----------------------------------------------------------------------
+
+nsContainerFrame*
+NS_NewRootBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsRootBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRootBoxFrame)
+
+nsRootBoxFrame::nsRootBoxFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext, true)
+{
+ mPopupSetFrame = nullptr;
+
+ nsCOMPtr<nsBoxLayout> layout;
+ NS_NewStackLayout(layout);
+ SetXULLayoutManager(layout);
+}
+
+void
+nsRootBoxFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list ID");
+ MOZ_ASSERT(mFrames.IsEmpty(), "already have a child frame");
+ nsBoxFrame::AppendFrames(aListID, aFrameList);
+}
+
+void
+nsRootBoxFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // Because we only support a single child frame inserting is the same
+ // as appending.
+ MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame");
+ AppendFrames(aListID, aFrameList);
+}
+
+void
+nsRootBoxFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list ID");
+ if (aOldFrame == mFrames.FirstChild()) {
+ nsBoxFrame::RemoveFrame(aListID, aOldFrame);
+ } else {
+ MOZ_CRASH("unknown aOldFrame");
+ }
+}
+
+#ifdef DEBUG_REFLOW
+int32_t gReflows = 0;
+#endif
+
+void
+nsRootBoxFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ DO_GLOBAL_REFLOW_COUNT("nsRootBoxFrame");
+
+#ifdef DEBUG_REFLOW
+ gReflows++;
+ printf("----Reflow %d----\n", gReflows);
+#endif
+ return nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+}
+
+void
+nsRootBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (mContent && mContent->GetProperty(nsGkAtoms::DisplayPortMargins)) {
+ // The XUL document's root element may have displayport margins set in
+ // ChromeProcessController::InitializeRoot, and we should to supply the
+ // base rect.
+ nsRect displayPortBase = aDirtyRect.Intersect(nsRect(nsPoint(0, 0), GetSize()));
+ nsLayoutUtils::SetDisplayPortBase(mContent, displayPortBase);
+ }
+
+ // root boxes don't need a debug border/outline or a selection overlay...
+ // They *may* have a background propagated to them, so force creation
+ // of a background display list element.
+ DisplayBorderBackgroundOutline(aBuilder, aLists, true);
+
+ BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+nsresult
+nsRootBoxFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ if (aEvent->mMessage == eMouseUp) {
+ nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+
+ return NS_OK;
+}
+
+// REVIEW: The override here was doing nothing since nsBoxFrame is our
+// parent class
+nsIAtom*
+nsRootBoxFrame::GetType() const
+{
+ return nsGkAtoms::rootFrame;
+}
+
+nsPopupSetFrame*
+nsRootBoxFrame::GetPopupSetFrame()
+{
+ return mPopupSetFrame;
+}
+
+void
+nsRootBoxFrame::SetPopupSetFrame(nsPopupSetFrame* aPopupSet)
+{
+ // Under normal conditions this should only be called once. However,
+ // if something triggers ReconstructDocElementHierarchy, we will
+ // destroy this frame's child (the nsDocElementBoxFrame), but not this
+ // frame. This will cause the popupset to remove itself by calling
+ // |SetPopupSetFrame(nullptr)|, and then we'll be able to accept a new
+ // popupset. Since the anonymous content is associated with the
+ // nsDocElementBoxFrame, we'll get a new popupset when the new doc
+ // element box frame is created.
+ if (!mPopupSetFrame || !aPopupSet) {
+ mPopupSetFrame = aPopupSet;
+ } else {
+ NS_NOTREACHED("Popup set is already defined! Only 1 allowed.");
+ }
+}
+
+nsIContent*
+nsRootBoxFrame::GetDefaultTooltip()
+{
+ return mDefaultTooltip;
+}
+
+void
+nsRootBoxFrame::SetDefaultTooltip(nsIContent* aTooltip)
+{
+ mDefaultTooltip = aTooltip;
+}
+
+nsresult
+nsRootBoxFrame::AddTooltipSupport(nsIContent* aNode)
+{
+ NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+
+ nsXULTooltipListener *listener = nsXULTooltipListener::GetInstance();
+ if (!listener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return listener->AddTooltipSupport(aNode);
+}
+
+nsresult
+nsRootBoxFrame::RemoveTooltipSupport(nsIContent* aNode)
+{
+ // XXjh yuck, I'll have to implement a way to get at
+ // the tooltip listener for a given node to make
+ // this work. Not crucial, we aren't removing
+ // tooltips from any nodes in the app just yet.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_QUERYFRAME_HEAD(nsRootBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsIRootBox)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsRootBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("RootBox"), aResult);
+}
+#endif
diff --git a/layout/xul/nsScrollBoxFrame.cpp b/layout/xul/nsScrollBoxFrame.cpp
new file mode 100644
index 000000000..4a6c9c2a8
--- /dev/null
+++ b/layout/xul/nsScrollBoxFrame.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsButtonBoxFrame.h"
+#include "nsITimer.h"
+#include "nsRepeatService.h"
+#include "mozilla/MouseEvents.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+class nsAutoRepeatBoxFrame : public nsButtonBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewAutoRepeatBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+protected:
+ explicit nsAutoRepeatBoxFrame(nsStyleContext* aContext):
+ nsButtonBoxFrame(aContext) {}
+
+ void StartRepeat() {
+ if (IsActivatedOnHover()) {
+ // No initial delay on hover.
+ nsRepeatService::GetInstance()->Start(Notify, this, 0);
+ } else {
+ nsRepeatService::GetInstance()->Start(Notify, this);
+ }
+ }
+ void StopRepeat() {
+ nsRepeatService::GetInstance()->Stop(Notify, this);
+ }
+ void Notify();
+ static void Notify(void* aData) {
+ static_cast<nsAutoRepeatBoxFrame*>(aData)->Notify();
+ }
+
+ bool mTrustedEvent;
+
+ bool IsActivatedOnHover();
+};
+
+nsIFrame*
+NS_NewAutoRepeatBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsAutoRepeatBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsAutoRepeatBoxFrame)
+
+nsresult
+nsAutoRepeatBoxFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ switch(aEvent->mMessage) {
+ // repeat mode may be "hover" for repeating while the mouse is hovering
+ // over the element, otherwise repetition is done while the element is
+ // active (pressed).
+ case eMouseEnterIntoWidget:
+ case eMouseOver:
+ if (IsActivatedOnHover()) {
+ StartRepeat();
+ mTrustedEvent = aEvent->IsTrusted();
+ }
+ break;
+
+ case eMouseExitFromWidget:
+ case eMouseOut:
+ // always stop on mouse exit
+ StopRepeat();
+ // Not really necessary but do this to be safe
+ mTrustedEvent = false;
+ break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ // skip button frame handling to prevent click handling
+ return nsBoxFrame::HandleEvent(aPresContext, mouseEvent, aEventStatus);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+NS_IMETHODIMP
+nsAutoRepeatBoxFrame::HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ if (!IsActivatedOnHover()) {
+ StartRepeat();
+ mTrustedEvent = aEvent->IsTrusted();
+ DoMouseClick(aEvent, mTrustedEvent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoRepeatBoxFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ if (!IsActivatedOnHover()) {
+ StopRepeat();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsAutoRepeatBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aAttribute == nsGkAtoms::type) {
+ StopRepeat();
+ }
+ return NS_OK;
+}
+
+void
+nsAutoRepeatBoxFrame::Notify()
+{
+ DoMouseClick(nullptr, mTrustedEvent);
+}
+
+void
+nsAutoRepeatBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // Ensure our repeat service isn't going... it's possible that a scrollbar can disappear out
+ // from under you while you're in the process of scrolling.
+ StopRepeat();
+ nsButtonBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+bool
+nsAutoRepeatBoxFrame::IsActivatedOnHover()
+{
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::repeat,
+ nsGkAtoms::hover, eCaseMatters);
+}
diff --git a/layout/xul/nsScrollbarButtonFrame.cpp b/layout/xul/nsScrollbarButtonFrame.cpp
new file mode 100644
index 000000000..206d9717f
--- /dev/null
+++ b/layout/xul/nsScrollbarButtonFrame.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsScrollbarButtonFrame.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsSliderFrame.h"
+#include "nsScrollbarFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsRepeatService.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+
+using namespace mozilla;
+using mozilla::layers::ScrollInputMethod;
+
+//
+// NS_NewToolbarFrame
+//
+// Creates a new Toolbar frame and returns it
+//
+nsIFrame*
+NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsScrollbarButtonFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarButtonFrame)
+
+nsresult
+nsScrollbarButtonFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ // If a web page calls event.preventDefault() we still want to
+ // scroll when scroll arrow is clicked. See bug 511075.
+ if (!mContent->IsInNativeAnonymousSubtree() &&
+ nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ switch (aEvent->mMessage) {
+ case eMouseDown:
+ mCursorOnThis = true;
+ // if we didn't handle the press ourselves, pass it on to the superclass
+ if (HandleButtonPress(aPresContext, aEvent, aEventStatus)) {
+ return NS_OK;
+ }
+ break;
+ case eMouseUp:
+ HandleRelease(aPresContext, aEvent, aEventStatus);
+ break;
+ case eMouseOut:
+ mCursorOnThis = false;
+ break;
+ case eMouseMove: {
+ nsPoint cursor =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ nsRect frameRect(nsPoint(0, 0), GetSize());
+ mCursorOnThis = frameRect.Contains(cursor);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+bool
+nsScrollbarButtonFrame::HandleButtonPress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ // Get the desired action for the scrollbar button.
+ LookAndFeel::IntID tmpAction;
+ uint16_t button = aEvent->AsMouseEvent()->button;
+ if (button == WidgetMouseEvent::eLeftButton) {
+ tmpAction = LookAndFeel::eIntID_ScrollButtonLeftMouseButtonAction;
+ } else if (button == WidgetMouseEvent::eMiddleButton) {
+ tmpAction = LookAndFeel::eIntID_ScrollButtonMiddleMouseButtonAction;
+ } else if (button == WidgetMouseEvent::eRightButton) {
+ tmpAction = LookAndFeel::eIntID_ScrollButtonRightMouseButtonAction;
+ } else {
+ return false;
+ }
+
+ // Get the button action metric from the pres. shell.
+ int32_t pressedButtonAction;
+ if (NS_FAILED(LookAndFeel::GetInt(tmpAction, &pressedButtonAction))) {
+ return false;
+ }
+
+ // get the scrollbar control
+ nsIFrame* scrollbar;
+ GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
+
+ if (scrollbar == nullptr)
+ return false;
+
+ static nsIContent::AttrValuesArray strings[] = { &nsGkAtoms::increment,
+ &nsGkAtoms::decrement,
+ nullptr };
+ int32_t index = mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::type,
+ strings, eCaseMatters);
+ int32_t direction;
+ if (index == 0)
+ direction = 1;
+ else if (index == 1)
+ direction = -1;
+ else
+ return false;
+
+ bool repeat = pressedButtonAction != 2;
+ // set this attribute so we can style it later
+ nsWeakFrame weakFrame(this);
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
+
+ nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
+
+ if (!weakFrame.IsAlive()) {
+ return false;
+ }
+
+ nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+ if (sb) {
+ nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+ switch (pressedButtonAction) {
+ case 0:
+ sb->SetIncrementToLine(direction);
+ if (m) {
+ m->ScrollByLine(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
+ }
+ break;
+ case 1:
+ sb->SetIncrementToPage(direction);
+ if (m) {
+ m->ScrollByPage(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
+ }
+ break;
+ case 2:
+ sb->SetIncrementToWhole(direction);
+ if (m) {
+ m->ScrollByWhole(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
+ }
+ break;
+ case 3:
+ default:
+ // We were told to ignore this click, or someone assigned a non-standard
+ // value to the button's action.
+ return false;
+ }
+ if (!weakFrame.IsAlive()) {
+ return false;
+ }
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollbarButtonClick);
+
+ if (!m) {
+ sb->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return false;
+ }
+ }
+ }
+ if (repeat) {
+ StartRepeat();
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsScrollbarButtonFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ // we're not active anymore
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
+ StopRepeat();
+ nsIFrame* scrollbar;
+ GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
+ nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+ if (sb) {
+ nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+ if (m) {
+ m->ScrollbarReleased(sb);
+ }
+ }
+ return NS_OK;
+}
+
+void nsScrollbarButtonFrame::Notify()
+{
+ if (mCursorOnThis ||
+ LookAndFeel::GetInt(
+ LookAndFeel::eIntID_ScrollbarButtonAutoRepeatBehavior, 0)) {
+ // get the scrollbar control
+ nsIFrame* scrollbar;
+ GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
+ nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+ if (sb) {
+ nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+ if (m) {
+ m->RepeatButtonScroll(sb);
+ } else {
+ sb->MoveToNewPosition();
+ }
+ }
+ }
+}
+
+void
+nsScrollbarButtonFrame::MouseClicked(WidgetGUIEvent* aEvent)
+{
+ nsButtonBoxFrame::MouseClicked(aEvent);
+ //MouseClicked();
+}
+
+nsresult
+nsScrollbarButtonFrame::GetChildWithTag(nsIAtom* atom, nsIFrame* start,
+ nsIFrame*& result)
+{
+ // recursively search our children
+ for (nsIFrame* childFrame : start->PrincipalChildList())
+ {
+ // get the content node
+ nsIContent* child = childFrame->GetContent();
+
+ if (child) {
+ // see if it is the child
+ if (child->IsXULElement(atom))
+ {
+ result = childFrame;
+
+ return NS_OK;
+ }
+ }
+
+ // recursive search the child
+ GetChildWithTag(atom, childFrame, result);
+ if (result != nullptr)
+ return NS_OK;
+ }
+
+ result = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsScrollbarButtonFrame::GetParentWithTag(nsIAtom* toFind, nsIFrame* start,
+ nsIFrame*& result)
+{
+ while (start)
+ {
+ start = start->GetParent();
+
+ if (start) {
+ // get the content node
+ nsIContent* child = start->GetContent();
+
+ if (child && child->IsXULElement(toFind)) {
+ result = start;
+ return NS_OK;
+ }
+ }
+ }
+
+ result = nullptr;
+ return NS_OK;
+}
+
+void
+nsScrollbarButtonFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // Ensure our repeat service isn't going... it's possible that a scrollbar can disappear out
+ // from under you while you're in the process of scrolling.
+ StopRepeat();
+ nsButtonBoxFrame::DestroyFrom(aDestructRoot);
+}
diff --git a/layout/xul/nsScrollbarButtonFrame.h b/layout/xul/nsScrollbarButtonFrame.h
new file mode 100644
index 000000000..9be73b13c
--- /dev/null
+++ b/layout/xul/nsScrollbarButtonFrame.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ This class lays out its children either vertically or horizontally
+
+**/
+
+#ifndef nsScrollbarButtonFrame_h___
+#define nsScrollbarButtonFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsButtonBoxFrame.h"
+#include "nsITimer.h"
+#include "nsRepeatService.h"
+
+class nsScrollbarButtonFrame : public nsButtonBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsScrollbarButtonFrame(nsStyleContext* aContext):
+ nsButtonBoxFrame(aContext), mCursorOnThis(false) {}
+
+ // Overrides
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ friend nsIFrame* NS_NewScrollbarButtonFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ static nsresult GetChildWithTag(nsIAtom* atom, nsIFrame* start, nsIFrame*& result);
+ static nsresult GetParentWithTag(nsIAtom* atom, nsIFrame* start, nsIFrame*& result);
+
+ bool HandleButtonPress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+
+ NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleDrag(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+protected:
+ virtual void MouseClicked(mozilla::WidgetGUIEvent* aEvent) override;
+
+ void StartRepeat() {
+ nsRepeatService::GetInstance()->Start(Notify, this);
+ }
+ void StopRepeat() {
+ nsRepeatService::GetInstance()->Stop(Notify, this);
+ }
+ void Notify();
+ static void Notify(void* aData) {
+ static_cast<nsScrollbarButtonFrame*>(aData)->Notify();
+ }
+
+ bool mCursorOnThis;
+};
+
+#endif
diff --git a/layout/xul/nsScrollbarFrame.cpp b/layout/xul/nsScrollbarFrame.cpp
new file mode 100644
index 000000000..d9c28a2bf
--- /dev/null
+++ b/layout/xul/nsScrollbarFrame.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsScrollbarFrame.h"
+#include "nsSliderFrame.h"
+#include "nsScrollbarButtonFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsThemeConstants.h"
+#include "nsIContent.h"
+#include "nsIDOMMutationEvent.h"
+
+using namespace mozilla;
+
+//
+// NS_NewScrollbarFrame
+//
+// Creates a new scrollbar frame and returns it
+//
+nsIFrame*
+NS_NewScrollbarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsScrollbarFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame)
+
+NS_QUERYFRAME_HEAD(nsScrollbarFrame)
+ NS_QUERYFRAME_ENTRY(nsScrollbarFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+void
+nsScrollbarFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // We want to be a reflow root since we use reflows to move the
+ // slider. Any reflow inside the scrollbar frame will be a reflow to
+ // move the slider and will thus not change anything outside of the
+ // scrollbar or change the size of the scrollbar frame.
+ mState |= NS_FRAME_REFLOW_ROOT;
+}
+
+void
+nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure our
+ // desired size agrees.
+ if (aReflowInput.AvailableWidth() == 0) {
+ aDesiredSize.Width() = 0;
+ }
+ if (aReflowInput.AvailableHeight() == 0) {
+ aDesiredSize.Height() = 0;
+ }
+}
+
+nsIAtom*
+nsScrollbarFrame::GetType() const
+{
+ return nsGkAtoms::scrollbarFrame;
+}
+
+nsresult
+nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ // if the current position changes, notify any nsGfxScrollFrame
+ // parent we may have
+ if (aAttribute != nsGkAtoms::curpos)
+ return rv;
+
+ nsIScrollableFrame* scrollable = do_QueryFrame(GetParent());
+ if (!scrollable)
+ return rv;
+
+ nsCOMPtr<nsIContent> content(mContent);
+ scrollable->CurPosAttributeChanged(content);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsScrollbarFrame::HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+void
+nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator)
+{
+ mScrollbarMediator = aMediator;
+}
+
+nsIScrollbarMediator*
+nsScrollbarFrame::GetScrollbarMediator()
+{
+ if (!mScrollbarMediator) {
+ return nullptr;
+ }
+ nsIFrame* f = mScrollbarMediator->GetPrimaryFrame();
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(f);
+ nsIScrollbarMediator* sbm;
+
+ if (scrollFrame) {
+ nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
+ sbm = do_QueryFrame(scrolledFrame);
+ if (sbm) {
+ return sbm;
+ }
+ }
+ sbm = do_QueryFrame(f);
+ if (f && !sbm) {
+ f = f->PresContext()->PresShell()->GetRootScrollFrame();
+ if (f && f->GetContent() == mScrollbarMediator) {
+ return do_QueryFrame(f);
+ }
+ }
+ return sbm;
+}
+
+nsresult
+nsScrollbarFrame::GetXULMargin(nsMargin& aMargin)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ aMargin.SizeTo(0,0,0,0);
+
+ if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+ nsPresContext* presContext = PresContext();
+ nsITheme* theme = presContext->GetTheme();
+ if (theme) {
+ LayoutDeviceIntSize size;
+ bool isOverridable;
+ theme->GetMinimumWidgetSize(presContext, this, NS_THEME_SCROLLBAR, &size,
+ &isOverridable);
+ if (IsXULHorizontal()) {
+ aMargin.top = -presContext->DevPixelsToAppUnits(size.height);
+ }
+ else {
+ aMargin.left = -presContext->DevPixelsToAppUnits(size.width);
+ }
+ rv = NS_OK;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ rv = nsBox::GetXULMargin(aMargin);
+ }
+
+ if (NS_SUCCEEDED(rv) && !IsXULHorizontal()) {
+ nsIScrollbarMediator* scrollFrame = GetScrollbarMediator();
+ if (scrollFrame && !scrollFrame->IsScrollbarOnRight()) {
+ Swap(aMargin.left, aMargin.right);
+ }
+ }
+
+ return rv;
+}
+
+void
+nsScrollbarFrame::SetIncrementToLine(int32_t aDirection)
+{
+ // get the scrollbar's content node
+ nsIContent* content = GetContent();
+ mSmoothScroll = true;
+ mIncrement = aDirection * nsSliderFrame::GetIncrement(content);
+}
+
+void
+nsScrollbarFrame::SetIncrementToPage(int32_t aDirection)
+{
+ // get the scrollbar's content node
+ nsIContent* content = GetContent();
+ mSmoothScroll = true;
+ mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content);
+}
+
+void
+nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection)
+{
+ // get the scrollbar's content node
+ nsIContent* content = GetContent();
+ if (aDirection == -1)
+ mIncrement = -nsSliderFrame::GetCurrentPosition(content);
+ else
+ mIncrement = nsSliderFrame::GetMaxPosition(content) -
+ nsSliderFrame::GetCurrentPosition(content);
+ // Don't repeat or use smooth scrolling if scrolling to beginning or end
+ // of a page.
+ mSmoothScroll = false;
+}
+
+int32_t
+nsScrollbarFrame::MoveToNewPosition()
+{
+ // get the scrollbar's content node
+ nsCOMPtr<nsIContent> content = GetContent();
+
+ // get the current pos
+ int32_t curpos = nsSliderFrame::GetCurrentPosition(content);
+
+ // get the max pos
+ int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
+
+ // save the old curpos
+ int32_t oldCurpos = curpos;
+
+ // increment the given amount
+ if (mIncrement) {
+ curpos += mIncrement;
+ }
+
+ // make sure the current position is between the current and max positions
+ if (curpos < 0) {
+ curpos = 0;
+ } else if (curpos > maxpos) {
+ curpos = maxpos;
+ }
+
+ // set the current position of the slider.
+ nsAutoString curposStr;
+ curposStr.AppendInt(curpos);
+
+ nsWeakFrame weakFrame(this);
+ if (mSmoothScroll) {
+ content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
+ }
+ content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false);
+ // notify the nsScrollbarFrame of the change
+ AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION);
+ if (!weakFrame.IsAlive()) {
+ return curpos;
+ }
+ // notify all nsSliderFrames of the change
+ nsIFrame::ChildListIterator childLists(this);
+ for (; !childLists.IsDone(); childLists.Next()) {
+ nsFrameList::Enumerator childFrames(childLists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* f = childFrames.get();
+ nsSliderFrame* sliderFrame = do_QueryFrame(f);
+ if (sliderFrame) {
+ sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION);
+ if (!weakFrame.IsAlive()) {
+ return curpos;
+ }
+ }
+ }
+ }
+ // See if we have appearance information for a theme.
+ const nsStyleDisplay* disp = StyleDisplay();
+ nsPresContext* presContext = PresContext();
+ if (disp->mAppearance) {
+ nsITheme *theme = presContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(presContext, this, disp->mAppearance)) {
+ bool repaint;
+ nsAttrValue oldValue;
+ oldValue.SetTo(oldCurpos);
+ theme->WidgetStateChanged(this, disp->mAppearance, nsGkAtoms::curpos,
+ &repaint, &oldValue);
+ }
+ }
+ content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
+ return curpos;
+}
diff --git a/layout/xul/nsScrollbarFrame.h b/layout/xul/nsScrollbarFrame.h
new file mode 100644
index 000000000..4048bc05b
--- /dev/null
+++ b/layout/xul/nsScrollbarFrame.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsScrollbarFrame
+//
+
+#ifndef nsScrollbarFrame_h__
+#define nsScrollbarFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsIScrollbarMediator;
+
+nsIFrame* NS_NewScrollbarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsScrollbarFrame : public nsBoxFrame
+{
+public:
+ explicit nsScrollbarFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext), mScrollbarMediator(nullptr) {}
+
+ NS_DECL_QUERYFRAME_TARGET(nsScrollbarFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("ScrollbarFrame"), aResult);
+ }
+#endif
+
+ // nsIFrame overrides
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ NS_IMETHOD HandlePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld) override;
+
+ NS_IMETHOD HandleDrag(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+ void SetScrollbarMediatorContent(nsIContent* aMediator);
+ nsIScrollbarMediator* GetScrollbarMediator();
+
+ // nsBox methods
+
+ /**
+ * Treat scrollbars as clipping their children; overflowing children
+ * will not be allowed to set an overflow rect on this
+ * frame. This means that when the scroll code decides to hide a
+ * scrollframe by setting its height or width to zero, that will
+ * hide the children too.
+ */
+ virtual bool DoesClipChildren() override { return true; }
+
+ virtual nsresult GetXULMargin(nsMargin& aMargin) override;
+
+ /**
+ * The following three methods set the value of mIncrement when a
+ * scrollbar button is pressed.
+ */
+ void SetIncrementToLine(int32_t aDirection);
+ void SetIncrementToPage(int32_t aDirection);
+ void SetIncrementToWhole(int32_t aDirection);
+ /**
+ * MoveToNewPosition() adds mIncrement to the current position and
+ * updates the curpos attribute.
+ * @returns The new position after clamping, in CSS Pixels
+ * @note This method might destroy the frame, pres shell, and other objects.
+ */
+ int32_t MoveToNewPosition();
+ int32_t GetIncrement() { return mIncrement; }
+
+protected:
+ int32_t mIncrement; // Amount to scroll, in CSSPixels
+ bool mSmoothScroll;
+
+private:
+ nsCOMPtr<nsIContent> mScrollbarMediator;
+}; // class nsScrollbarFrame
+
+#endif
diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp
new file mode 100644
index 000000000..8e083f20c
--- /dev/null
+++ b/layout/xul/nsSliderFrame.cpp
@@ -0,0 +1,1446 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsSliderFrame.h"
+
+#include "gfxPrefs.h"
+#include "nsStyleContext.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLParts.h"
+#include "nsIPresShell.h"
+#include "nsCSSRendering.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsScrollbarButtonFrame.h"
+#include "nsISliderListener.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsISupportsImpl.h"
+#include "nsScrollbarFrame.h"
+#include "nsRepeatService.h"
+#include "nsBoxLayoutState.h"
+#include "nsSprocketLayout.h"
+#include "nsIServiceManager.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "mozilla/Assertions.h" // for MOZ_ASSERT
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/AsyncDragMetrics.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+#include <algorithm>
+
+using namespace mozilla;
+using mozilla::layers::APZCCallbackHelper;
+using mozilla::layers::AsyncDragMetrics;
+using mozilla::layers::InputAPZContext;
+using mozilla::layers::ScrollInputMethod;
+
+bool nsSliderFrame::gMiddlePref = false;
+int32_t nsSliderFrame::gSnapMultiplier;
+
+// Turn this on if you want to debug slider frames.
+#undef DEBUG_SLIDER
+
+static already_AddRefed<nsIContent>
+GetContentOfBox(nsIFrame *aBox)
+{
+ nsCOMPtr<nsIContent> content = aBox->GetContent();
+ return content.forget();
+}
+
+nsIFrame*
+NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSliderFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
+
+NS_QUERYFRAME_HEAD(nsSliderFrame)
+ NS_QUERYFRAME_ENTRY(nsSliderFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+nsSliderFrame::nsSliderFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext),
+ mCurPos(0),
+ mChange(0),
+ mDragFinished(true),
+ mUserChanged(false),
+ mScrollingWithAPZ(false),
+ mSuppressionActive(false)
+{
+}
+
+// stop timer
+nsSliderFrame::~nsSliderFrame()
+{
+ if (mSuppressionActive) {
+ APZCCallbackHelper::SuppressDisplayport(false, PresContext() ?
+ PresContext()->PresShell() :
+ nullptr);
+ }
+}
+
+void
+nsSliderFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ static bool gotPrefs = false;
+ if (!gotPrefs) {
+ gotPrefs = true;
+
+ gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
+ gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
+ }
+
+ mCurPos = GetCurrentPosition(aContent);
+}
+
+void
+nsSliderFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ nsBoxFrame::RemoveFrame(aListID, aOldFrame);
+ if (mFrames.IsEmpty())
+ RemoveListener();
+}
+
+void
+nsSliderFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ bool wasEmpty = mFrames.IsEmpty();
+ nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+ if (wasEmpty)
+ AddListener();
+}
+
+void
+nsSliderFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ // if we have no children and on was added then make sure we add the
+ // listener
+ bool wasEmpty = mFrames.IsEmpty();
+ nsBoxFrame::AppendFrames(aListID, aFrameList);
+ if (wasEmpty)
+ AddListener();
+}
+
+int32_t
+nsSliderFrame::GetCurrentPosition(nsIContent* content)
+{
+ return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
+}
+
+int32_t
+nsSliderFrame::GetMinPosition(nsIContent* content)
+{
+ return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
+}
+
+int32_t
+nsSliderFrame::GetMaxPosition(nsIContent* content)
+{
+ return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
+}
+
+int32_t
+nsSliderFrame::GetIncrement(nsIContent* content)
+{
+ return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
+}
+
+
+int32_t
+nsSliderFrame::GetPageIncrement(nsIContent* content)
+{
+ return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
+}
+
+int32_t
+nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue)
+{
+ nsAutoString value;
+ content->GetAttr(kNameSpaceID_None, atom, value);
+ if (!value.IsEmpty()) {
+ nsresult error;
+
+ // convert it to an integer
+ defaultValue = value.ToInteger(&error);
+ }
+
+ return defaultValue;
+}
+
+class nsValueChangedRunnable : public Runnable
+{
+public:
+ nsValueChangedRunnable(nsISliderListener* aListener,
+ nsIAtom* aWhich,
+ int32_t aValue,
+ bool aUserChanged)
+ : mListener(aListener), mWhich(aWhich),
+ mValue(aValue), mUserChanged(aUserChanged)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ return mListener->ValueChanged(nsDependentAtomString(mWhich),
+ mValue, mUserChanged);
+ }
+
+ nsCOMPtr<nsISliderListener> mListener;
+ nsCOMPtr<nsIAtom> mWhich;
+ int32_t mValue;
+ bool mUserChanged;
+};
+
+class nsDragStateChangedRunnable : public Runnable
+{
+public:
+ nsDragStateChangedRunnable(nsISliderListener* aListener,
+ bool aDragBeginning)
+ : mListener(aListener),
+ mDragBeginning(aDragBeginning)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ return mListener->DragStateChanged(mDragBeginning);
+ }
+
+ nsCOMPtr<nsISliderListener> mListener;
+ bool mDragBeginning;
+};
+
+nsresult
+nsSliderFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+ // if the current position changes
+ if (aAttribute == nsGkAtoms::curpos) {
+ CurrentPositionChanged();
+ } else if (aAttribute == nsGkAtoms::minpos ||
+ aAttribute == nsGkAtoms::maxpos) {
+ // bounds check it.
+
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar;
+ scrollbar = GetContentOfBox(scrollbarBox);
+ int32_t current = GetCurrentPosition(scrollbar);
+ int32_t min = GetMinPosition(scrollbar);
+ int32_t max = GetMaxPosition(scrollbar);
+
+ // inform the parent <scale> that the minimum or maximum changed
+ nsIFrame* parent = GetParent();
+ if (parent) {
+ nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
+ if (sliderListener) {
+ nsContentUtils::AddScriptRunner(
+ new nsValueChangedRunnable(sliderListener, aAttribute,
+ aAttribute == nsGkAtoms::minpos ? min : max, false));
+ }
+ }
+
+ if (current < min || current > max)
+ {
+ int32_t direction = 0;
+ if (current < min || max < min) {
+ current = min;
+ direction = -1;
+ } else if (current > max) {
+ current = max;
+ direction = 1;
+ }
+
+ // set the new position and notify observers
+ nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
+ if (scrollbarFrame) {
+ nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
+ scrollbarFrame->SetIncrementToWhole(direction);
+ if (mediator) {
+ mediator->ScrollByWhole(scrollbarFrame, direction,
+ nsIScrollbarMediator::ENABLE_SNAP);
+ }
+ }
+ // 'this' might be destroyed here
+
+ nsContentUtils::AddScriptRunner(
+ new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
+ }
+ }
+
+ if (aAttribute == nsGkAtoms::minpos ||
+ aAttribute == nsGkAtoms::maxpos ||
+ aAttribute == nsGkAtoms::pageincrement ||
+ aAttribute == nsGkAtoms::increment) {
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+
+ return rv;
+}
+
+void
+nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
+ // This is EVIL, we shouldn't be messing with event delivery just to get
+ // thumb mouse drag events to arrive at the slider!
+ aLists.Outlines()->AppendNewToTop(new (aBuilder)
+ nsDisplayEventReceiver(aBuilder, this));
+ return;
+ }
+
+ nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+}
+
+void
+nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // if we are too small to have a thumb don't paint it.
+ nsIFrame* thumb = nsBox::GetChildXULBox(this);
+
+ if (thumb) {
+ nsRect thumbRect(thumb->GetRect());
+ nsMargin m;
+ thumb->GetXULMargin(m);
+ thumbRect.Inflate(m);
+
+ nsRect crect;
+ GetXULClientRect(crect);
+
+ if (crect.width < thumbRect.width || crect.height < thumbRect.height)
+ return;
+
+ // If this scrollbar is the scrollbar of an actively scrolled scroll frame,
+ // layerize the scrollbar thumb, wrap it in its own ContainerLayer and
+ // attach scrolling information to it.
+ // We do this here and not in the thumb's nsBoxFrame::BuildDisplayList so
+ // that the event region that gets created for the thumb is included in
+ // the nsDisplayOwnLayer contents.
+
+ uint32_t flags = 0;
+ mozilla::layers::FrameMetrics::ViewID scrollTargetId =
+ mozilla::layers::FrameMetrics::NULL_SCROLL_ID;
+ aBuilder->GetScrollbarInfo(&scrollTargetId, &flags);
+ bool thumbGetsLayer = (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID);
+ nsLayoutUtils::SetScrollbarThumbLayerization(thumb, thumbGetsLayer);
+
+ if (thumbGetsLayer) {
+ nsDisplayListCollection tempLists;
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, tempLists);
+
+ // This is a bit of a hack. Collect up all descendant display items
+ // and merge them into a single Content() list.
+ nsDisplayList masterList;
+ masterList.AppendToTop(tempLists.BorderBackground());
+ masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
+ masterList.AppendToTop(tempLists.Floats());
+ masterList.AppendToTop(tempLists.Content());
+ masterList.AppendToTop(tempLists.PositionedDescendants());
+ masterList.AppendToTop(tempLists.Outlines());
+
+ // Wrap the list to make it its own layer.
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayOwnLayer(aBuilder, this, &masterList, flags, scrollTargetId,
+ GetThumbRatio()));
+
+ return;
+ }
+ }
+
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+NS_IMETHODIMP
+nsSliderFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ // get the thumb should be our only child
+ nsIFrame* thumbBox = nsBox::GetChildXULBox(this);
+
+ if (!thumbBox) {
+ SyncLayout(aState);
+ return NS_OK;
+ }
+
+ EnsureOrient();
+
+#ifdef DEBUG_LAYOUT
+ if (mState & NS_STATE_DEBUG_WAS_SET) {
+ if (mState & NS_STATE_SET_TO_DEBUG)
+ SetXULDebug(aState, true);
+ else
+ SetXULDebug(aState, false);
+ }
+#endif
+
+ // get the content area inside our borders
+ nsRect clientRect;
+ GetXULClientRect(clientRect);
+
+ // get the scrollbar
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar;
+ scrollbar = GetContentOfBox(scrollbarBox);
+
+ // get the thumb's pref size
+ nsSize thumbSize = thumbBox->GetXULPrefSize(aState);
+
+ if (IsXULHorizontal())
+ thumbSize.height = clientRect.height;
+ else
+ thumbSize.width = clientRect.width;
+
+ int32_t curPos = GetCurrentPosition(scrollbar);
+ int32_t minPos = GetMinPosition(scrollbar);
+ int32_t maxPos = GetMaxPosition(scrollbar);
+ int32_t pageIncrement = GetPageIncrement(scrollbar);
+
+ maxPos = std::max(minPos, maxPos);
+ curPos = clamped(curPos, minPos, maxPos);
+
+ nscoord& availableLength = IsXULHorizontal() ? clientRect.width : clientRect.height;
+ nscoord& thumbLength = IsXULHorizontal() ? thumbSize.width : thumbSize.height;
+
+ if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetXULFlex() > 0) {
+ float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
+ thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio));
+ }
+
+ // Round the thumb's length to device pixels.
+ nsPresContext* presContext = PresContext();
+ thumbLength = presContext->DevPixelsToAppUnits(
+ presContext->AppUnitsToDevPixels(thumbLength));
+
+ // mRatio translates the thumb position in app units to the value.
+ mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1;
+
+ // in reverse mode, curpos is reversed such that lower values are to the
+ // right or bottom and increase leftwards or upwards. In this case, use the
+ // offset from the end instead of the beginning.
+ bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::reverse, eCaseMatters);
+ nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
+
+ // set the thumb's coord to be the current pos * the ratio.
+ nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height);
+ int32_t& thumbPos = (IsXULHorizontal() ? thumbRect.x : thumbRect.y);
+ thumbPos += NSToCoordRound(pos * mRatio);
+
+ nsRect oldThumbRect(thumbBox->GetRect());
+ LayoutChildAt(aState, thumbBox, thumbRect);
+
+ SyncLayout(aState);
+
+ // Redraw only if thumb changed size.
+ if (!oldThumbRect.IsEqualInterior(thumbRect))
+ XULRedraw(aState);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ // If a web page calls event.preventDefault() we still want to
+ // scroll when scroll arrow is clicked. See bug 511075.
+ if (!mContent->IsInNativeAnonymousSubtree() &&
+ nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ if (!mDragFinished && !isDraggingThumb()) {
+ StopDrag();
+ return NS_OK;
+ }
+
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar;
+ scrollbar = GetContentOfBox(scrollbarBox);
+ bool isHorizontal = IsXULHorizontal();
+
+ if (isDraggingThumb())
+ {
+ switch (aEvent->mMessage) {
+ case eTouchMove:
+ case eMouseMove: {
+ if (mScrollingWithAPZ) {
+ break;
+ }
+ nsPoint eventPoint;
+ if (!GetEventPoint(aEvent, eventPoint)) {
+ break;
+ }
+ if (mChange) {
+ // On Linux the destination point is determined by the initial click
+ // on the scrollbar track and doesn't change until the mouse button
+ // is released.
+#ifndef MOZ_WIDGET_GTK
+ // On the other platforms we need to update the destination point now.
+ mDestinationPoint = eventPoint;
+ StopRepeat();
+ StartRepeat();
+#endif
+ break;
+ }
+
+ nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return NS_OK;
+ }
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollbarDrag);
+
+ // take our current position and subtract the start location
+ pos -= mDragStart;
+ bool isMouseOutsideThumb = false;
+ if (gSnapMultiplier) {
+ nsSize thumbSize = thumbFrame->GetSize();
+ if (isHorizontal) {
+ // horizontal scrollbar - check if mouse is above or below thumb
+ // XXXbz what about looking at the .y of the thumb's rect? Is that
+ // always zero here?
+ if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
+ eventPoint.y > thumbSize.height +
+ gSnapMultiplier * thumbSize.height)
+ isMouseOutsideThumb = true;
+ }
+ else {
+ // vertical scrollbar - check if mouse is left or right of thumb
+ if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
+ eventPoint.x > thumbSize.width +
+ gSnapMultiplier * thumbSize.width)
+ isMouseOutsideThumb = true;
+ }
+ }
+ if (aEvent->mClass == eTouchEventClass) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ if (isMouseOutsideThumb)
+ {
+ SetCurrentThumbPosition(scrollbar, mThumbStart, false, false);
+ return NS_OK;
+ }
+
+ // set it
+ SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping
+ }
+ break;
+
+ case eTouchEnd:
+ case eMouseUp:
+ if (ShouldScrollForEvent(aEvent)) {
+ StopDrag();
+ //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state.
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ return NS_OK;
+ } else if (ShouldScrollToClickForEvent(aEvent)) {
+ nsPoint eventPoint;
+ if (!GetEventPoint(aEvent, eventPoint)) {
+ return NS_OK;
+ }
+ nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
+
+ // adjust so that the middle of the thumb is placed under the click
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return NS_OK;
+ }
+ nsSize thumbSize = thumbFrame->GetSize();
+ nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollbarTrackClick);
+
+ // set it
+ nsWeakFrame weakFrame(this);
+ // should aMaySnap be true here?
+ SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
+
+ DragThumb(true);
+
+#ifdef MOZ_WIDGET_GTK
+ nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
+ thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
+#endif
+
+ if (aEvent->mClass == eTouchEventClass) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+
+ if (isHorizontal)
+ mThumbStart = thumbFrame->GetPosition().x;
+ else
+ mThumbStart = thumbFrame->GetPosition().y;
+
+ mDragStart = pos - mThumbStart;
+ }
+#ifdef MOZ_WIDGET_GTK
+ else if (ShouldScrollForEvent(aEvent) &&
+ aEvent->mClass == eMouseEventClass &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) {
+ // HandlePress and HandleRelease are usually called via
+ // nsFrame::HandleEvent, but only for the left mouse button.
+ if (aEvent->mMessage == eMouseDown) {
+ HandlePress(aPresContext, aEvent, aEventStatus);
+ } else if (aEvent->mMessage == eMouseUp) {
+ HandleRelease(aPresContext, aEvent, aEventStatus);
+ }
+
+ return NS_OK;
+ }
+#endif
+
+ // XXX hack until handle release is actually called in nsframe.
+ // if (aEvent->mMessage == eMouseOut ||
+ // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP ||
+ // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) {
+ // HandleRelease(aPresContext, aEvent, aEventStatus);
+ // }
+
+ if (aEvent->mMessage == eMouseOut && mChange)
+ HandleRelease(aPresContext, aEvent, aEventStatus);
+
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+// Helper function to collect the "scroll to click" metric. Beware of
+// caching this, users expect to be able to change the system preference
+// and see the browser change its behavior immediately.
+bool
+nsSliderFrame::GetScrollToClick()
+{
+ if (GetScrollbar() != this) {
+ return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false);
+ }
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return true;
+ }
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
+ nsGkAtoms::_false, eCaseMatters)) {
+ return false;
+ }
+
+#ifdef XP_MACOSX
+ return true;
+#else
+ return false;
+#endif
+}
+
+nsIFrame*
+nsSliderFrame::GetScrollbar()
+{
+ // if we are in a scrollbar then return the scrollbar's content node
+ // if we are not then return ours.
+ nsIFrame* scrollbar;
+ nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
+
+ if (scrollbar == nullptr)
+ return this;
+
+ return scrollbar->IsXULBoxFrame() ? scrollbar : this;
+}
+
+void
+nsSliderFrame::PageUpDown(nscoord change)
+{
+ // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
+ // asking it for the current position and the page increment. If we are not in a scrollbar we will
+ // get the values from our own node.
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar;
+ scrollbar = GetContentOfBox(scrollbarBox);
+
+ nscoord pageIncrement = GetPageIncrement(scrollbar);
+ int32_t curpos = GetCurrentPosition(scrollbar);
+ int32_t minpos = GetMinPosition(scrollbar);
+ int32_t maxpos = GetMaxPosition(scrollbar);
+
+ // get the new position and make sure it is in bounds
+ int32_t newpos = curpos + change * pageIncrement;
+ if (newpos < minpos || maxpos < minpos)
+ newpos = minpos;
+ else if (newpos > maxpos)
+ newpos = maxpos;
+
+ SetCurrentPositionInternal(scrollbar, newpos, true);
+}
+
+// called when the current position changed and we need to update the thumb's location
+void
+nsSliderFrame::CurrentPositionChanged()
+{
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar;
+ scrollbar = GetContentOfBox(scrollbarBox);
+
+ // get the current position
+ int32_t curPos = GetCurrentPosition(scrollbar);
+
+ // do nothing if the position did not change
+ if (mCurPos == curPos)
+ return;
+
+ // get our current min and max position from our content node
+ int32_t minPos = GetMinPosition(scrollbar);
+ int32_t maxPos = GetMaxPosition(scrollbar);
+
+ maxPos = std::max(minPos, maxPos);
+ curPos = clamped(curPos, minPos, maxPos);
+
+ // get the thumb's rect
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame)
+ return; // The thumb may stream in asynchronously via XBL.
+
+ nsRect thumbRect = thumbFrame->GetRect();
+
+ nsRect clientRect;
+ GetXULClientRect(clientRect);
+
+ // figure out the new rect
+ nsRect newThumbRect(thumbRect);
+
+ bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::reverse, eCaseMatters);
+ nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
+
+ if (IsXULHorizontal())
+ newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
+ else
+ newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
+
+ // avoid putting the scroll thumb at subpixel positions which cause needless invalidations
+ nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
+ nsPoint snappedThumbLocation = ToAppUnits(
+ newThumbRect.TopLeft().ToNearestPixels(appUnitsPerPixel),
+ appUnitsPerPixel);
+ if (IsXULHorizontal()) {
+ newThumbRect.x = snappedThumbLocation.x;
+ } else {
+ newThumbRect.y = snappedThumbLocation.y;
+ }
+
+ // set the rect
+ thumbFrame->SetRect(newThumbRect);
+
+ // Request a repaint of the scrollbar
+ nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
+ nsIScrollbarMediator* mediator = scrollbarFrame
+ ? scrollbarFrame->GetScrollbarMediator() : nullptr;
+ if (!mediator || !mediator->ShouldSuppressScrollbarRepaints()) {
+ SchedulePaint();
+ }
+
+ mCurPos = curPos;
+
+ // inform the parent <scale> if it exists that the value changed
+ nsIFrame* parent = GetParent();
+ if (parent) {
+ nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
+ if (sliderListener) {
+ nsContentUtils::AddScriptRunner(
+ new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged));
+ }
+ }
+}
+
+static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) {
+ nsAutoString str;
+ str.AppendInt(aNewPos);
+
+ if (aIsSmooth) {
+ aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
+ }
+ aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
+ if (aIsSmooth) {
+ aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
+ }
+}
+
+// Use this function when you want to set the scroll position via the position
+// of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
+// the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
+void
+nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos,
+ bool aIsSmooth, bool aMaySnap)
+{
+ nsRect crect;
+ GetXULClientRect(crect);
+ nscoord offset = IsXULHorizontal() ? crect.x : crect.y;
+ int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
+
+ if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
+ nsGkAtoms::_true, eCaseMatters)) {
+ // If snap="true", then the slider may only be set to min + (increment * x).
+ // Otherwise, the slider may be set to any positive integer.
+ int32_t increment = GetIncrement(aScrollbar);
+ newPos = NSToIntRound(newPos / float(increment)) * increment;
+ }
+
+ SetCurrentPosition(aScrollbar, newPos, aIsSmooth);
+}
+
+// Use this function when you know the target scroll position of the scrolled content.
+// aNewPos should be passed to this function as a position as if the minpos is 0.
+// That is, the minpos will be added to the position by this function. In a reverse
+// direction slider, the newpos should be the distance from the end.
+void
+nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos,
+ bool aIsSmooth)
+{
+ // get min and max position from our content node
+ int32_t minpos = GetMinPosition(aScrollbar);
+ int32_t maxpos = GetMaxPosition(aScrollbar);
+
+ // in reverse direction sliders, flip the value so that it goes from
+ // right to left, or bottom to top.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::reverse, eCaseMatters))
+ aNewPos = maxpos - aNewPos;
+ else
+ aNewPos += minpos;
+
+ // get the new position and make sure it is in bounds
+ if (aNewPos < minpos || maxpos < minpos)
+ aNewPos = minpos;
+ else if (aNewPos > maxpos)
+ aNewPos = maxpos;
+
+ SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth);
+}
+
+void
+nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos,
+ bool aIsSmooth)
+{
+ nsCOMPtr<nsIContent> scrollbar = aScrollbar;
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsWeakFrame weakFrame(this);
+
+ mUserChanged = true;
+
+ nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
+ if (scrollbarFrame) {
+ // See if we have a mediator.
+ nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
+ if (mediator) {
+ nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
+ nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
+ mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateAttribute(scrollbar, aNewPos, /* aNotify */false, aIsSmooth);
+ CurrentPositionChanged();
+ mUserChanged = false;
+ return;
+ }
+ }
+
+ UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ mUserChanged = false;
+
+#ifdef DEBUG_SLIDER
+ printf("Current Pos=%d\n",aNewPos);
+#endif
+
+}
+
+nsIAtom*
+nsSliderFrame::GetType() const
+{
+ return nsGkAtoms::sliderFrame;
+}
+
+void
+nsSliderFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsBoxFrame::SetInitialChildList(aListID, aChildList);
+ if (aListID == kPrincipalList) {
+ AddListener();
+ }
+}
+
+nsresult
+nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent)
+{
+ // Only process the event if the thumb is not being dragged.
+ if (mSlider && !mSlider->isDraggingThumb())
+ return mSlider->StartDrag(aEvent);
+
+ return NS_OK;
+}
+
+bool
+nsSliderFrame::StartAPZDrag()
+{
+ if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) {
+ return false;
+ }
+
+ nsContainerFrame* cf = GetScrollbar()->GetParent();
+ if (!cf) {
+ return false;
+ }
+
+ nsIContent* scrollableContent = cf->GetContent();
+ if (!scrollableContent) {
+ return false;
+ }
+
+ mozilla::layers::FrameMetrics::ViewID scrollTargetId;
+ bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId);
+ bool hasAPZView = hasID && (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID);
+
+ if (!hasAPZView) {
+ return false;
+ }
+
+ nsIFrame* scrollbarBox = GetScrollbar();
+ nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
+
+ // This rect is the range in which the scroll thumb can slide in.
+ nsRect sliderTrack = GetRect() - scrollbarBox->GetPosition();
+ CSSIntRect sliderTrackCSS = CSSIntRect::FromAppUnitsRounded(sliderTrack);
+
+ uint64_t inputblockId = InputAPZContext::GetInputBlockId();
+ uint32_t presShellId = PresContext()->PresShell()->GetPresShellId();
+ AsyncDragMetrics dragMetrics(scrollTargetId, presShellId, inputblockId,
+ NSAppUnitsToIntPixels(mDragStart,
+ float(AppUnitsPerCSSPixel())),
+ sliderTrackCSS,
+ IsXULHorizontal() ? AsyncDragMetrics::HORIZONTAL :
+ AsyncDragMetrics::VERTICAL);
+
+ if (!nsLayoutUtils::HasDisplayPort(scrollableContent)) {
+ return false;
+ }
+
+ // When we start an APZ drag, we wont get mouse events for the drag.
+ // APZ will consume them all and only notify us of the new scroll position.
+ this->GetNearestWidget()->StartAsyncScrollbarDrag(dragMetrics);
+ return true;
+}
+
+nsresult
+nsSliderFrame::StartDrag(nsIDOMEvent* aEvent)
+{
+#ifdef DEBUG_SLIDER
+ printf("Begin dragging\n");
+#endif
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ return NS_OK;
+
+ WidgetGUIEvent* event = aEvent->WidgetEventPtr()->AsGUIEvent();
+
+ if (!ShouldScrollForEvent(event)) {
+ return NS_OK;
+ }
+
+ nsPoint pt;
+ if (!GetEventPoint(event, pt)) {
+ return NS_OK;
+ }
+ bool isHorizontal = IsXULHorizontal();
+ nscoord pos = isHorizontal ? pt.x : pt.y;
+
+ // If we should scroll-to-click, first place the middle of the slider thumb
+ // under the mouse.
+ nsCOMPtr<nsIContent> scrollbar;
+ nscoord newpos = pos;
+ bool scrollToClick = ShouldScrollToClickForEvent(event);
+ if (scrollToClick) {
+ // adjust so that the middle of the thumb is placed under the click
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return NS_OK;
+ }
+ nsSize thumbSize = thumbFrame->GetSize();
+ nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
+
+ newpos -= (thumbLength/2);
+
+ nsIFrame* scrollbarBox = GetScrollbar();
+ scrollbar = GetContentOfBox(scrollbarBox);
+ }
+
+ DragThumb(true);
+
+ if (scrollToClick) {
+ // should aMaySnap be true here?
+ SetCurrentThumbPosition(scrollbar, newpos, false, false);
+ }
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return NS_OK;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
+ thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
+#endif
+
+ if (isHorizontal)
+ mThumbStart = thumbFrame->GetPosition().x;
+ else
+ mThumbStart = thumbFrame->GetPosition().y;
+
+ mDragStart = pos - mThumbStart;
+
+ mScrollingWithAPZ = StartAPZDrag();
+
+#ifdef DEBUG_SLIDER
+ printf("Pressed mDragStart=%d\n",mDragStart);
+#endif
+
+ if (!mScrollingWithAPZ && !mSuppressionActive) {
+ MOZ_ASSERT(PresContext()->PresShell());
+ APZCCallbackHelper::SuppressDisplayport(true, PresContext()->PresShell());
+ mSuppressionActive = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSliderFrame::StopDrag()
+{
+ AddListener();
+ DragThumb(false);
+
+ mScrollingWithAPZ = false;
+
+ if (mSuppressionActive) {
+ MOZ_ASSERT(PresContext()->PresShell());
+ APZCCallbackHelper::SuppressDisplayport(false, PresContext()->PresShell());
+ mSuppressionActive = false;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (thumbFrame) {
+ nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
+ thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
+ }
+#endif
+
+ if (mChange) {
+ StopRepeat();
+ mChange = 0;
+ }
+ return NS_OK;
+}
+
+void
+nsSliderFrame::DragThumb(bool aGrabMouseEvents)
+{
+ mDragFinished = !aGrabMouseEvents;
+
+ // inform the parent <scale> that a drag is beginning or ending
+ nsIFrame* parent = GetParent();
+ if (parent) {
+ nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
+ if (sliderListener) {
+ nsContentUtils::AddScriptRunner(
+ new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents));
+ }
+ }
+
+ nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr,
+ aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0);
+}
+
+bool
+nsSliderFrame::isDraggingThumb()
+{
+ return (nsIPresShell::GetCapturingContent() == GetContent());
+}
+
+void
+nsSliderFrame::AddListener()
+{
+ if (!mMediator) {
+ mMediator = new nsSliderMediator(this);
+ }
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return;
+ }
+ thumbFrame->GetContent()->
+ AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator,
+ false, false);
+ thumbFrame->GetContent()->
+ AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator,
+ false, false);
+}
+
+void
+nsSliderFrame::RemoveListener()
+{
+ NS_ASSERTION(mMediator, "No listener was ever added!!");
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame)
+ return;
+
+ thumbFrame->GetContent()->
+ RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false);
+}
+
+bool
+nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent)
+{
+ switch (aEvent->mMessage) {
+ case eTouchStart:
+ case eTouchEnd:
+ return true;
+ case eMouseDown:
+ case eMouseUp: {
+ uint16_t button = aEvent->AsMouseEvent()->button;
+#ifdef MOZ_WIDGET_GTK
+ return (button == WidgetMouseEvent::eLeftButton) ||
+ (button == WidgetMouseEvent::eRightButton && GetScrollToClick()) ||
+ (button == WidgetMouseEvent::eMiddleButton && gMiddlePref && !GetScrollToClick());
+#else
+ return (button == WidgetMouseEvent::eLeftButton) ||
+ (button == WidgetMouseEvent::eMiddleButton && gMiddlePref);
+#endif
+ }
+ default:
+ return false;
+ }
+}
+
+bool
+nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
+{
+ if (!ShouldScrollForEvent(aEvent)) {
+ return false;
+ }
+
+ if (aEvent->mMessage == eTouchStart) {
+ return GetScrollToClick();
+ }
+
+ if (aEvent->mMessage != eMouseDown) {
+ return false;
+ }
+
+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ // On Mac and Linux, clicking the scrollbar thumb should never scroll to click.
+ if (IsEventOverThumb(aEvent)) {
+ return false;
+ }
+#endif
+
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
+#ifdef XP_MACOSX
+ bool invertPref = mouseEvent->IsAlt();
+#else
+ bool invertPref = mouseEvent->IsShift();
+#endif
+ return GetScrollToClick() != invertPref;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ if (mouseEvent->button == WidgetMouseEvent::eRightButton) {
+ return !GetScrollToClick();
+ }
+#endif
+
+ return true;
+}
+
+bool
+nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent)
+{
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ return false;
+ }
+
+ nsPoint eventPoint;
+ if (!GetEventPoint(aEvent, eventPoint)) {
+ return false;
+ }
+
+ nsRect thumbRect = thumbFrame->GetRect();
+#if defined(MOZ_WIDGET_GTK)
+ /* Scrollbar track can have padding, so it's better to check that eventPoint
+ * is inside of actual thumb, not just its one axis. The part of the scrollbar
+ * track adjacent to thumb can actually receive events in GTK3 */
+ return eventPoint.x >= thumbRect.x && eventPoint.x < thumbRect.XMost() &&
+ eventPoint.y >= thumbRect.y && eventPoint.y < thumbRect.YMost();
+#else
+ bool isHorizontal = IsXULHorizontal();
+ nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y;
+ nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y;
+ nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost();
+
+ return eventPos >= thumbStart && eventPos < thumbEnd;
+#endif
+}
+
+NS_IMETHODIMP
+nsSliderFrame::HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ if (IsEventOverThumb(aEvent)) {
+ return NS_OK;
+ }
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) // display:none?
+ return NS_OK;
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ return NS_OK;
+
+ nsRect thumbRect = thumbFrame->GetRect();
+
+ nscoord change = 1;
+ nsPoint eventPoint;
+ if (!GetEventPoint(aEvent, eventPoint)) {
+ return NS_OK;
+ }
+ if (IsXULHorizontal() ? eventPoint.x < thumbRect.x
+ : eventPoint.y < thumbRect.y)
+ change = -1;
+
+ mChange = change;
+ DragThumb(true);
+ // On Linux we want to keep scrolling in the direction indicated by |change|
+ // until the mouse is released. On the other platforms we want to stop
+ // scrolling as soon as the scrollbar thumb has reached the current mouse
+ // position.
+#ifdef MOZ_WIDGET_GTK
+ nsRect clientRect;
+ GetXULClientRect(clientRect);
+
+ // Set the destination point to the very end of the scrollbar so that
+ // scrolling doesn't stop halfway through.
+ if (change > 0) {
+ mDestinationPoint = nsPoint(clientRect.width, clientRect.height);
+ }
+ else {
+ mDestinationPoint = nsPoint(0, 0);
+ }
+#else
+ mDestinationPoint = eventPoint;
+#endif
+ StartRepeat();
+ PageScroll(change);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ StopRepeat();
+
+ nsIFrame* scrollbar = GetScrollbar();
+ nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+ if (sb) {
+ nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+ if (m) {
+ m->ScrollbarReleased(sb);
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // tell our mediator if we have one we are gone.
+ if (mMediator) {
+ mMediator->SetSlider(nullptr);
+ mMediator = nullptr;
+ }
+ StopRepeat();
+
+ // call base class Destroy()
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+nsSize
+nsSliderFrame::GetXULPrefSize(nsBoxLayoutState& aState)
+{
+ EnsureOrient();
+ return nsBoxFrame::GetXULPrefSize(aState);
+}
+
+nsSize
+nsSliderFrame::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ EnsureOrient();
+
+ // our min size is just our borders and padding
+ return nsBox::GetXULMinSize(aState);
+}
+
+nsSize
+nsSliderFrame::GetXULMaxSize(nsBoxLayoutState& aState)
+{
+ EnsureOrient();
+ return nsBoxFrame::GetXULMaxSize(aState);
+}
+
+void
+nsSliderFrame::EnsureOrient()
+{
+ nsIFrame* scrollbarBox = GetScrollbar();
+
+ bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
+ if (isHorizontal)
+ mState |= NS_STATE_IS_HORIZONTAL;
+ else
+ mState &= ~NS_STATE_IS_HORIZONTAL;
+}
+
+
+void
+nsSliderFrame::Notify(void)
+{
+ bool stop = false;
+
+ nsIFrame* thumbFrame = mFrames.FirstChild();
+ if (!thumbFrame) {
+ StopRepeat();
+ return;
+ }
+ nsRect thumbRect = thumbFrame->GetRect();
+
+ bool isHorizontal = IsXULHorizontal();
+
+ // See if the thumb has moved past our destination point.
+ // if it has we want to stop.
+ if (isHorizontal) {
+ if (mChange < 0) {
+ if (thumbRect.x < mDestinationPoint.x)
+ stop = true;
+ } else {
+ if (thumbRect.x + thumbRect.width > mDestinationPoint.x)
+ stop = true;
+ }
+ } else {
+ if (mChange < 0) {
+ if (thumbRect.y < mDestinationPoint.y)
+ stop = true;
+ } else {
+ if (thumbRect.y + thumbRect.height > mDestinationPoint.y)
+ stop = true;
+ }
+ }
+
+
+ if (stop) {
+ StopRepeat();
+ } else {
+ PageScroll(mChange);
+ }
+}
+
+void
+nsSliderFrame::PageScroll(nscoord aChange)
+{
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::reverse, eCaseMatters)) {
+ aChange = -aChange;
+ }
+ nsIFrame* scrollbar = GetScrollbar();
+ nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+ if (sb) {
+ nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+ sb->SetIncrementToPage(aChange);
+ if (m) {
+ m->ScrollByPage(sb, aChange, nsIScrollbarMediator::ENABLE_SNAP);
+ return;
+ }
+ }
+ PageUpDown(aChange);
+}
+
+float
+nsSliderFrame::GetThumbRatio() const
+{
+ // mRatio is in thumb app units per scrolled css pixels. Convert it to a
+ // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb
+ // is in the scrollframe's parent's space whereas the scrolled CSS pixels
+ // are in the scrollframe's space).
+ return mRatio / mozilla::AppUnitsPerCSSPixel();
+}
+
+NS_IMPL_ISUPPORTS(nsSliderMediator,
+ nsIDOMEventListener)
diff --git a/layout/xul/nsSliderFrame.h b/layout/xul/nsSliderFrame.h
new file mode 100644
index 000000000..832065a21
--- /dev/null
+++ b/layout/xul/nsSliderFrame.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSliderFrame_h__
+#define nsSliderFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsRepeatService.h"
+#include "nsBoxFrame.h"
+#include "nsIAtom.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsIDOMEventListener.h"
+
+class nsITimer;
+class nsSliderFrame;
+
+nsIFrame* NS_NewSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsSliderMediator final : public nsIDOMEventListener
+{
+public:
+
+ NS_DECL_ISUPPORTS
+
+ nsSliderFrame* mSlider;
+
+ explicit nsSliderMediator(nsSliderFrame* aSlider) { mSlider = aSlider; }
+
+ virtual void SetSlider(nsSliderFrame* aSlider) { mSlider = aSlider; }
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+protected:
+ virtual ~nsSliderMediator() {}
+};
+
+class nsSliderFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+ NS_DECL_QUERYFRAME_TARGET(nsSliderFrame)
+ NS_DECL_QUERYFRAME
+
+ friend class nsSliderMediator;
+
+ explicit nsSliderFrame(nsStyleContext* aContext);
+ virtual ~nsSliderFrame();
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("SliderFrame"), aResult);
+ }
+#endif
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override;
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+
+ // nsIFrame overrides
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* asPrevInFlow) override;
+
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+ // nsContainerFrame overrides
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ nsresult StartDrag(nsIDOMEvent* aEvent);
+ nsresult StopDrag();
+
+ bool StartAPZDrag();
+
+ static int32_t GetCurrentPosition(nsIContent* content);
+ static int32_t GetMinPosition(nsIContent* content);
+ static int32_t GetMaxPosition(nsIContent* content);
+ static int32_t GetIncrement(nsIContent* content);
+ static int32_t GetPageIncrement(nsIContent* content);
+ static int32_t GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue);
+ void EnsureOrient();
+
+ NS_IMETHOD HandlePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleDrag(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ // Return the ratio the scrollbar thumb should move in proportion to the
+ // scrolled frame.
+ float GetThumbRatio() const;
+
+private:
+
+ bool GetScrollToClick();
+ nsIFrame* GetScrollbar();
+ bool ShouldScrollForEvent(mozilla::WidgetGUIEvent* aEvent);
+ bool ShouldScrollToClickForEvent(mozilla::WidgetGUIEvent* aEvent);
+ bool IsEventOverThumb(mozilla::WidgetGUIEvent* aEvent);
+
+ void PageUpDown(nscoord change);
+ void SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewPos, bool aIsSmooth,
+ bool aMaySnap);
+ void SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos, bool aIsSmooth);
+ void SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t pos,
+ bool aIsSmooth);
+ void CurrentPositionChanged();
+
+ void DragThumb(bool aGrabMouseEvents);
+ void AddListener();
+ void RemoveListener();
+ bool isDraggingThumb();
+
+ void StartRepeat() {
+ nsRepeatService::GetInstance()->Start(Notify, this);
+ }
+ void StopRepeat() {
+ nsRepeatService::GetInstance()->Stop(Notify, this);
+ }
+ void Notify();
+ static void Notify(void* aData) {
+ (static_cast<nsSliderFrame*>(aData))->Notify();
+ }
+ void PageScroll(nscoord aChange);
+
+ nsPoint mDestinationPoint;
+ RefPtr<nsSliderMediator> mMediator;
+
+ float mRatio;
+
+ nscoord mDragStart;
+ nscoord mThumbStart;
+
+ int32_t mCurPos;
+
+ nscoord mChange;
+
+ bool mDragFinished;
+
+ // true if an attribute change has been caused by the user manipulating the
+ // slider. This allows notifications to tell how a slider's current position
+ // was changed.
+ bool mUserChanged;
+
+ // true if we've handed off the scrolling to APZ. This means that we should
+ // ignore scrolling events as the position will be updated by APZ. If we were
+ // to process these events then the scroll position update would conflict
+ // causing the scroll position to jump.
+ bool mScrollingWithAPZ;
+
+ // true if displayport suppression is active, for more performant
+ // scrollbar-dragging behaviour.
+ bool mSuppressionActive;
+
+ static bool gMiddlePref;
+ static int32_t gSnapMultiplier;
+}; // class nsSliderFrame
+
+#endif
diff --git a/layout/xul/nsSplitterFrame.cpp b/layout/xul/nsSplitterFrame.cpp
new file mode 100644
index 000000000..7879a176d
--- /dev/null
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsSplitterFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsScrollbarButtonFrame.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIPresShell.h"
+#include "nsFrameList.h"
+#include "nsHTMLParts.h"
+#include "nsStyleContext.h"
+#include "nsBoxLayoutState.h"
+#include "nsIServiceManager.h"
+#include "nsContainerFrame.h"
+#include "nsContentCID.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/UniquePtr.h"
+
+using namespace mozilla;
+
+class nsSplitterInfo {
+public:
+ nscoord min;
+ nscoord max;
+ nscoord current;
+ nscoord changed;
+ nsCOMPtr<nsIContent> childElem;
+ int32_t flex;
+ int32_t index;
+};
+
+class nsSplitterFrameInner final : public nsIDOMEventListener
+{
+protected:
+ virtual ~nsSplitterFrameInner();
+
+public:
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
+ {
+ mOuter = aSplitter;
+ mPressed = false;
+ }
+
+ void Disconnect() { mOuter = nullptr; }
+
+ nsresult MouseDown(nsIDOMEvent* aMouseEvent);
+ nsresult MouseUp(nsIDOMEvent* aMouseEvent);
+ nsresult MouseMove(nsIDOMEvent* aMouseEvent);
+
+ void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
+ void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
+
+ void AdjustChildren(nsPresContext* aPresContext);
+ void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal);
+
+ void AddRemoveSpace(nscoord aDiff,
+ nsSplitterInfo* aChildInfos,
+ int32_t aCount,
+ int32_t& aSpaceLeft);
+
+ void ResizeChildTo(nscoord& aDiff,
+ nsSplitterInfo* aChildrenBeforeInfos,
+ nsSplitterInfo* aChildrenAfterInfos,
+ int32_t aChildrenBeforeCount,
+ int32_t aChildrenAfterCount,
+ bool aBounded);
+
+ void UpdateState();
+
+ void AddListener();
+ void RemoveListener();
+
+ enum ResizeType { Closest, Farthest, Flex, Grow };
+ enum State { Open, CollapsedBefore, CollapsedAfter, Dragging };
+ enum CollapseDirection { Before, After };
+
+ ResizeType GetResizeBefore();
+ ResizeType GetResizeAfter();
+ State GetState();
+
+ void Reverse(UniquePtr<nsSplitterInfo[]>& aIndexes, int32_t aCount);
+ bool SupportsCollapseDirection(CollapseDirection aDirection);
+
+ void EnsureOrient();
+ void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize);
+
+ nsSplitterFrame* mOuter;
+ bool mDidDrag;
+ nscoord mDragStart;
+ nscoord mCurrentPos;
+ nsIFrame* mParentBox;
+ bool mPressed;
+ UniquePtr<nsSplitterInfo[]> mChildInfosBefore;
+ UniquePtr<nsSplitterInfo[]> mChildInfosAfter;
+ int32_t mChildInfosBeforeCount;
+ int32_t mChildInfosAfterCount;
+ State mState;
+ nscoord mSplitterPos;
+ bool mDragging;
+
+};
+
+NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
+
+nsSplitterFrameInner::ResizeType
+nsSplitterFrameInner::GetResizeBefore()
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::farthest, &nsGkAtoms::flex, nullptr};
+ switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::resizebefore,
+ strings, eCaseMatters)) {
+ case 0: return Farthest;
+ case 1: return Flex;
+ }
+ return Closest;
+}
+
+nsSplitterFrameInner::~nsSplitterFrameInner()
+{
+}
+
+nsSplitterFrameInner::ResizeType
+nsSplitterFrameInner::GetResizeAfter()
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::farthest, &nsGkAtoms::flex, &nsGkAtoms::grow, nullptr};
+ switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::resizeafter,
+ strings, eCaseMatters)) {
+ case 0: return Farthest;
+ case 1: return Flex;
+ case 2: return Grow;
+ }
+ return Closest;
+}
+
+nsSplitterFrameInner::State
+nsSplitterFrameInner::GetState()
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::dragging, &nsGkAtoms::collapsed, nullptr};
+ static nsIContent::AttrValuesArray strings_substate[] =
+ {&nsGkAtoms::before, &nsGkAtoms::after, nullptr};
+ switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::state,
+ strings, eCaseMatters)) {
+ case 0: return Dragging;
+ case 1:
+ switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::substate,
+ strings_substate,
+ eCaseMatters)) {
+ case 0: return CollapsedBefore;
+ case 1: return CollapsedAfter;
+ default:
+ if (SupportsCollapseDirection(After))
+ return CollapsedAfter;
+ return CollapsedBefore;
+ }
+ }
+ return Open;
+}
+
+//
+// NS_NewSplitterFrame
+//
+// Creates a new Toolbar frame and returns it
+//
+nsIFrame*
+NS_NewSplitterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSplitterFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
+
+nsSplitterFrame::nsSplitterFrame(nsStyleContext* aContext)
+: nsBoxFrame(aContext),
+ mInner(0)
+{
+}
+
+void
+nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (mInner) {
+ mInner->RemoveListener();
+ mInner->Disconnect();
+ mInner->Release();
+ mInner = nullptr;
+ }
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+
+nsresult
+nsSplitterFrame::GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor)
+{
+ return nsBoxFrame::GetCursor(aPoint, aCursor);
+
+ /*
+ if (IsXULHorizontal())
+ aCursor = NS_STYLE_CURSOR_N_RESIZE;
+ else
+ aCursor = NS_STYLE_CURSOR_W_RESIZE;
+
+ return NS_OK;
+ */
+}
+
+nsresult
+nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+ // if the alignment changed. Let the grippy know
+ if (aAttribute == nsGkAtoms::align) {
+ // tell the slider its attribute changed so it can
+ // update itself
+ nsIFrame* grippy = nullptr;
+ nsScrollbarButtonFrame::GetChildWithTag(nsGkAtoms::grippy, this, grippy);
+ if (grippy)
+ grippy->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ } else if (aAttribute == nsGkAtoms::state) {
+ mInner->UpdateState();
+ }
+
+ return rv;
+}
+
+/**
+ * Initialize us. If we are in a box get our alignment so we know what direction we are
+ */
+void
+nsSplitterFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ MOZ_ASSERT(!mInner);
+ mInner = new nsSplitterFrameInner(this);
+
+ mInner->AddRef();
+ mInner->mState = nsSplitterFrameInner::Open;
+ mInner->mDragging = false;
+
+ // determine orientation of parent, and if vertical, set orient to vertical
+ // on splitter content, then re-resolve style
+ // XXXbz this is pretty messed up, since this can change whether we should
+ // have a frame at all. This really needs a better solution.
+ if (aParent && aParent->IsXULBoxFrame()) {
+ if (!aParent->IsXULHorizontal()) {
+ if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None,
+ nsGkAtoms::orient)) {
+ aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
+ NS_LITERAL_STRING("vertical"), false);
+ nsStyleContext* parentStyleContext = StyleContext()->GetParent();
+ RefPtr<nsStyleContext> newContext = PresContext()->StyleSet()->
+ ResolveStyleFor(aContent->AsElement(), parentStyleContext);
+ SetStyleContextWithoutNotification(newContext);
+ }
+ }
+ }
+
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mInner->mState = nsSplitterFrameInner::Open;
+ mInner->AddListener();
+ mInner->mParentBox = nullptr;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState)
+{
+ if (GetStateBits() & NS_FRAME_FIRST_REFLOW)
+ {
+ mInner->mParentBox = nsBox::GetParentXULBox(this);
+ mInner->UpdateState();
+ }
+
+ return nsBoxFrame::DoXULLayout(aState);
+}
+
+
+void
+nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal)
+{
+ nsIFrame* box = nsBox::GetParentXULBox(this);
+ if (box) {
+ aIsHorizontal = !box->IsXULHorizontal();
+ }
+ else
+ nsBoxFrame::GetInitialOrientation(aIsHorizontal);
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ return NS_OK;
+}
+
+void
+nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ // if the mouse is captured always return us as the frame.
+ if (mInner->mDragging)
+ {
+ // XXX It's probably better not to check visibility here, right?
+ aLists.Outlines()->AppendNewToTop(new (aBuilder)
+ nsDisplayEventReceiver(aBuilder, this));
+ return;
+ }
+}
+
+nsresult
+nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ nsWeakFrame weakFrame(this);
+ RefPtr<nsSplitterFrameInner> inner(mInner);
+ switch (aEvent->mMessage) {
+ case eMouseMove:
+ inner->MouseDrag(aPresContext, aEvent);
+ break;
+
+ case eMouseUp:
+ if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+ inner->MouseUp(aPresContext, aEvent);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+void
+nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent)
+{
+ if (mDragging && mOuter) {
+ AdjustChildren(aPresContext);
+ AddListener();
+ nsIPresShell::SetCapturingContent(nullptr, 0); // XXXndeakin is this needed?
+ mDragging = false;
+ State newState = GetState();
+ // if the state is dragging then make it Open.
+ if (newState == Dragging)
+ mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, EmptyString(), true);
+
+ mPressed = false;
+
+ // if we dragged then fire a command event.
+ if (mDidDrag) {
+ nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(mOuter->GetContent());
+ element->DoCommand();
+ }
+
+ //printf("MouseUp\n");
+ }
+
+ mChildInfosBefore = nullptr;
+ mChildInfosAfter = nullptr;
+ mChildInfosBeforeCount = 0;
+ mChildInfosAfterCount = 0;
+}
+
+void
+nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent)
+{
+ if (mDragging && mOuter) {
+
+ //printf("Dragging\n");
+
+ bool isHorizontal = !mOuter->IsXULHorizontal();
+ // convert coord to pixels
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
+ mParentBox);
+ nscoord pos = isHorizontal ? pt.x : pt.y;
+
+ // mDragStart is in frame coordinates
+ nscoord start = mDragStart;
+
+ // take our current position and subtract the start location
+ pos -= start;
+
+ //printf("Diff=%d\n", pos);
+
+ ResizeType resizeAfter = GetResizeAfter();
+
+ bool bounded;
+
+ if (resizeAfter == nsSplitterFrameInner::Grow)
+ bounded = false;
+ else
+ bounded = true;
+
+ int i;
+ for (i=0; i < mChildInfosBeforeCount; i++)
+ mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
+
+ for (i=0; i < mChildInfosAfterCount; i++)
+ mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
+
+ nscoord oldPos = pos;
+
+ ResizeChildTo(pos,
+ mChildInfosBefore.get(), mChildInfosAfter.get(),
+ mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
+
+ State currentState = GetState();
+ bool supportsBefore = SupportsCollapseDirection(Before);
+ bool supportsAfter = SupportsCollapseDirection(After);
+
+ const bool isRTL = mOuter->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ bool pastEnd = oldPos > 0 && oldPos > pos;
+ bool pastBegin = oldPos < 0 && oldPos < pos;
+ if (isRTL) {
+ // Swap the boundary checks in RTL mode
+ bool tmp = pastEnd;
+ pastEnd = pastBegin;
+ pastBegin = tmp;
+ }
+ const bool isCollapsedBefore = pastBegin && supportsBefore;
+ const bool isCollapsedAfter = pastEnd && supportsAfter;
+
+ // if we are in a collapsed position
+ if (isCollapsedBefore || isCollapsedAfter)
+ {
+ // and we are not collapsed then collapse
+ if (currentState == Dragging) {
+ if (pastEnd)
+ {
+ //printf("Collapse right\n");
+ if (supportsAfter)
+ {
+ nsCOMPtr<nsIContent> outer = mOuter->mContent;
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
+ NS_LITERAL_STRING("after"),
+ true);
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("collapsed"),
+ true);
+ }
+
+ } else if (pastBegin)
+ {
+ //printf("Collapse left\n");
+ if (supportsBefore)
+ {
+ nsCOMPtr<nsIContent> outer = mOuter->mContent;
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
+ NS_LITERAL_STRING("before"),
+ true);
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("collapsed"),
+ true);
+ }
+ }
+ }
+ } else {
+ // if we are not in a collapsed position and we are not dragging make sure
+ // we are dragging.
+ if (currentState != Dragging)
+ mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"), true);
+ AdjustChildren(aPresContext);
+ }
+
+ mDidDrag = true;
+ }
+}
+
+void
+nsSplitterFrameInner::AddListener()
+{
+ mOuter->GetContent()->
+ AddEventListener(NS_LITERAL_STRING("mouseup"), this, false, false);
+ mOuter->GetContent()->
+ AddEventListener(NS_LITERAL_STRING("mousedown"), this, false, false);
+ mOuter->GetContent()->
+ AddEventListener(NS_LITERAL_STRING("mousemove"), this, false, false);
+ mOuter->GetContent()->
+ AddEventListener(NS_LITERAL_STRING("mouseout"), this, false, false);
+}
+
+void
+nsSplitterFrameInner::RemoveListener()
+{
+ ENSURE_TRUE(mOuter);
+ mOuter->GetContent()->
+ RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, false);
+ mOuter->GetContent()->
+ RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, false);
+ mOuter->GetContent()->
+ RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, false);
+ mOuter->GetContent()->
+ RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, false);
+}
+
+nsresult
+nsSplitterFrameInner::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("mouseup"))
+ return MouseUp(aEvent);
+ if (eventType.EqualsLiteral("mousedown"))
+ return MouseDown(aEvent);
+ if (eventType.EqualsLiteral("mousemove") ||
+ eventType.EqualsLiteral("mouseout"))
+ return MouseMove(aEvent);
+
+ NS_ABORT();
+ return NS_OK;
+}
+
+nsresult
+nsSplitterFrameInner::MouseUp(nsIDOMEvent* aMouseEvent)
+{
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ mPressed = false;
+
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+
+ return NS_OK;
+}
+
+nsresult
+nsSplitterFrameInner::MouseDown(nsIDOMEvent* aMouseEvent)
+{
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aMouseEvent));
+ if (!mouseEvent)
+ return NS_OK;
+
+ int16_t button = 0;
+ mouseEvent->GetButton(&button);
+
+ // only if left button
+ if (button != 0)
+ return NS_OK;
+
+ if (mOuter->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ return NS_OK;
+
+ mParentBox = nsBox::GetParentXULBox(mOuter);
+ if (!mParentBox)
+ return NS_OK;
+
+ // get our index
+ nsPresContext* outerPresContext = mOuter->PresContext();
+ const nsFrameList& siblingList(mParentBox->PrincipalChildList());
+ int32_t childIndex = siblingList.IndexOf(mOuter);
+ // if it's 0 (or not found) then stop right here.
+ // It might be not found if we're not in the parent's primary frame list.
+ if (childIndex <= 0)
+ return NS_OK;
+
+ int32_t childCount = siblingList.GetLength();
+ // if it's the last index then we need to allow for resizeafter="grow"
+ if (childIndex == childCount - 1 && GetResizeAfter() != Grow)
+ return NS_OK;
+
+ nsRenderingContext rc(
+ outerPresContext->PresShell()->CreateReferenceRenderingContext());
+ nsBoxLayoutState state(outerPresContext, &rc);
+ mCurrentPos = 0;
+ mPressed = true;
+
+ mDidDrag = false;
+
+ EnsureOrient();
+ bool isHorizontal = !mOuter->IsXULHorizontal();
+
+ ResizeType resizeBefore = GetResizeBefore();
+ ResizeType resizeAfter = GetResizeAfter();
+
+ mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount);
+ mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount);
+
+ // create info 2 lists. One of the children before us and one after.
+ int32_t count = 0;
+ mChildInfosBeforeCount = 0;
+ mChildInfosAfterCount = 0;
+
+ nsIFrame* childBox = nsBox::GetChildXULBox(mParentBox);
+
+ while (nullptr != childBox)
+ {
+ nsIContent* content = childBox->GetContent();
+ nsIDocument* doc = content->OwnerDoc();
+ int32_t dummy;
+ nsIAtom* atom = doc->BindingManager()->ResolveTag(content, &dummy);
+
+ // skip over any splitters
+ if (atom != nsGkAtoms::splitter) {
+ nsSize prefSize = childBox->GetXULPrefSize(state);
+ nsSize minSize = childBox->GetXULMinSize(state);
+ nsSize maxSize = nsBox::BoundsCheckMinMax(minSize, childBox->GetXULMaxSize(state));
+ prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize);
+
+ mOuter->AddMargin(childBox, minSize);
+ mOuter->AddMargin(childBox, prefSize);
+ mOuter->AddMargin(childBox, maxSize);
+
+ nscoord flex = childBox->GetXULFlex();
+
+ nsMargin margin(0,0,0,0);
+ childBox->GetXULMargin(margin);
+ nsRect r(childBox->GetRect());
+ r.Inflate(margin);
+
+ // We need to check for hidden attribute too, since treecols with
+ // the hidden="true" attribute are not really hidden, just collapsed
+ if (!content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::fixed,
+ nsGkAtoms::_true, eCaseMatters) &&
+ !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters)) {
+ if (count < childIndex && (resizeBefore != Flex || flex > 0)) {
+ mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
+ mChildInfosBefore[mChildInfosBeforeCount].min = isHorizontal ? minSize.width : minSize.height;
+ mChildInfosBefore[mChildInfosBeforeCount].max = isHorizontal ? maxSize.width : maxSize.height;
+ mChildInfosBefore[mChildInfosBeforeCount].current = isHorizontal ? r.width : r.height;
+ mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
+ mChildInfosBefore[mChildInfosBeforeCount].index = count;
+ mChildInfosBefore[mChildInfosBeforeCount].changed = mChildInfosBefore[mChildInfosBeforeCount].current;
+ mChildInfosBeforeCount++;
+ } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) {
+ mChildInfosAfter[mChildInfosAfterCount].childElem = content;
+ mChildInfosAfter[mChildInfosAfterCount].min = isHorizontal ? minSize.width : minSize.height;
+ mChildInfosAfter[mChildInfosAfterCount].max = isHorizontal ? maxSize.width : maxSize.height;
+ mChildInfosAfter[mChildInfosAfterCount].current = isHorizontal ? r.width : r.height;
+ mChildInfosAfter[mChildInfosAfterCount].flex = flex;
+ mChildInfosAfter[mChildInfosAfterCount].index = count;
+ mChildInfosAfter[mChildInfosAfterCount].changed = mChildInfosAfter[mChildInfosAfterCount].current;
+ mChildInfosAfterCount++;
+ }
+ }
+ }
+
+ childBox = nsBox::GetNextXULBox(childBox);
+ count++;
+ }
+
+ if (!mParentBox->IsXULNormalDirection()) {
+ // The before array is really the after array, and the order needs to be reversed.
+ // First reverse both arrays.
+ Reverse(mChildInfosBefore, mChildInfosBeforeCount);
+ Reverse(mChildInfosAfter, mChildInfosAfterCount);
+
+ // Now swap the two arrays.
+ Swap(mChildInfosBeforeCount, mChildInfosAfterCount);
+ Swap(mChildInfosBefore, mChildInfosAfter);
+ }
+
+ // if resizebefore is not Farthest, reverse the list because the first child
+ // in the list is the farthest, and we want the first child to be the closest.
+ if (resizeBefore != Farthest)
+ Reverse(mChildInfosBefore, mChildInfosBeforeCount);
+
+ // if the resizeafter is the Farthest we must reverse the list because the first child in the list
+ // is the closest we want the first child to be the Farthest.
+ if (resizeAfter == Farthest)
+ Reverse(mChildInfosAfter, mChildInfosAfterCount);
+
+ // grow only applys to the children after. If grow is set then no space should be taken out of any children after
+ // us. To do this we just set the size of that list to be 0.
+ if (resizeAfter == Grow)
+ mChildInfosAfterCount = 0;
+
+ int32_t c;
+ nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent->AsEvent(),
+ mParentBox);
+ if (isHorizontal) {
+ c = pt.x;
+ mSplitterPos = mOuter->mRect.x;
+ } else {
+ c = pt.y;
+ mSplitterPos = mOuter->mRect.y;
+ }
+
+ mDragStart = c;
+
+ //printf("Pressed mDragStart=%d\n",mDragStart);
+
+ nsIPresShell::SetCapturingContent(mOuter->GetContent(), CAPTURE_IGNOREALLOWED);
+
+ return NS_OK;
+}
+
+nsresult
+nsSplitterFrameInner::MouseMove(nsIDOMEvent* aMouseEvent)
+{
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ if (!mPressed)
+ return NS_OK;
+
+ if (mDragging)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
+ mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("dragging"), true);
+
+ RemoveListener();
+ mDragging = true;
+
+ return NS_OK;
+}
+
+void
+nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos, int32_t aCount)
+{
+ UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]);
+
+ for (int i=0; i < aCount; i++)
+ infos[i] = aChildInfos[aCount - 1 - i];
+
+ aChildInfos = Move(infos);
+}
+
+bool
+nsSplitterFrameInner::SupportsCollapseDirection
+(
+ nsSplitterFrameInner::CollapseDirection aDirection
+)
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::before, &nsGkAtoms::after, &nsGkAtoms::both, nullptr};
+
+ switch (mOuter->mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::collapse,
+ strings, eCaseMatters)) {
+ case 0:
+ return (aDirection == Before);
+ case 1:
+ return (aDirection == After);
+ case 2:
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsSplitterFrameInner::UpdateState()
+{
+ // State Transitions:
+ // Open -> Dragging
+ // Open -> CollapsedBefore
+ // Open -> CollapsedAfter
+ // CollapsedBefore -> Open
+ // CollapsedBefore -> Dragging
+ // CollapsedAfter -> Open
+ // CollapsedAfter -> Dragging
+ // Dragging -> Open
+ // Dragging -> CollapsedBefore (auto collapse)
+ // Dragging -> CollapsedAfter (auto collapse)
+
+ State newState = GetState();
+
+ if (newState == mState) {
+ // No change.
+ return;
+ }
+
+ if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
+ mOuter->GetParent()->IsXULBoxFrame()) {
+ // Find the splitter's immediate sibling.
+ nsIFrame* splitterSibling;
+ if (newState == CollapsedBefore || mState == CollapsedBefore) {
+ splitterSibling = mOuter->GetPrevSibling();
+ } else {
+ splitterSibling = mOuter->GetNextSibling();
+ }
+
+ if (splitterSibling) {
+ nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
+ if (sibling) {
+ if (mState == CollapsedBefore || mState == CollapsedAfter) {
+ // CollapsedBefore -> Open
+ // CollapsedBefore -> Dragging
+ // CollapsedAfter -> Open
+ // CollapsedAfter -> Dragging
+ nsContentUtils::AddScriptRunner(
+ new nsUnsetAttrRunnable(sibling, nsGkAtoms::collapsed));
+ } else if ((mState == Open || mState == Dragging)
+ && (newState == CollapsedBefore ||
+ newState == CollapsedAfter)) {
+ // Open -> CollapsedBefore / CollapsedAfter
+ // Dragging -> CollapsedBefore / CollapsedAfter
+ nsContentUtils::AddScriptRunner(
+ new nsSetAttrRunnable(sibling, nsGkAtoms::collapsed,
+ NS_LITERAL_STRING("true")));
+ }
+ }
+ }
+ }
+ mState = newState;
+}
+
+void
+nsSplitterFrameInner::EnsureOrient()
+{
+ bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL);
+ if (isHorizontal)
+ mOuter->mState |= NS_STATE_IS_HORIZONTAL;
+ else
+ mOuter->mState &= ~NS_STATE_IS_HORIZONTAL;
+}
+
+void
+nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext)
+{
+ EnsureOrient();
+ bool isHorizontal = !mOuter->IsXULHorizontal();
+
+ AdjustChildren(aPresContext, mChildInfosBefore.get(),
+ mChildInfosBeforeCount, isHorizontal);
+ AdjustChildren(aPresContext, mChildInfosAfter.get(),
+ mChildInfosAfterCount, isHorizontal);
+}
+
+static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, nsIContent* aContent)
+{
+ nsIFrame* childBox = nsBox::GetChildXULBox(aParentBox);
+
+ while (nullptr != childBox) {
+ if (childBox->GetContent() == aContent) {
+ return childBox;
+ }
+ childBox = nsBox::GetNextXULBox(childBox);
+ }
+ return nullptr;
+}
+
+void
+nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal)
+{
+ ///printf("------- AdjustChildren------\n");
+
+ nsBoxLayoutState state(aPresContext);
+
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ // first set all the widths.
+ nsIFrame* child = nsBox::GetChildXULBox(mOuter);
+ while(child)
+ {
+ SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ // now set our changed widths.
+ for (int i=0; i < aCount; i++)
+ {
+ nscoord pref = aChildInfos[i].changed;
+ nsIFrame* childBox = GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
+
+ if (childBox) {
+ SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
+ }
+ }
+}
+
+void
+nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize)
+{
+ nsRect rect(aChildBox->GetRect());
+ nscoord pref = 0;
+
+ if (!aSize)
+ {
+ if (aIsHorizontal)
+ pref = rect.width;
+ else
+ pref = rect.height;
+ } else {
+ pref = *aSize;
+ }
+
+ nsMargin margin(0,0,0,0);
+ aChildBox->GetXULMargin(margin);
+
+ nsCOMPtr<nsIAtom> attribute;
+
+ if (aIsHorizontal) {
+ pref -= (margin.left + margin.right);
+ attribute = nsGkAtoms::width;
+ } else {
+ pref -= (margin.top + margin.bottom);
+ attribute = nsGkAtoms::height;
+ }
+
+ nsIContent* content = aChildBox->GetContent();
+
+ // set its preferred size.
+ nsAutoString prefValue;
+ prefValue.AppendInt(pref/aOnePixel);
+ if (content->AttrValueIs(kNameSpaceID_None, attribute,
+ prefValue, eCaseMatters))
+ return;
+
+ nsWeakFrame weakBox(aChildBox);
+ content->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
+ ENSURE_TRUE(weakBox.IsAlive());
+ aState.PresShell()->FrameNeedsReflow(aChildBox, nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+}
+
+
+void
+nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
+ nsSplitterInfo* aChildInfos,
+ int32_t aCount,
+ int32_t& aSpaceLeft)
+{
+ aSpaceLeft = 0;
+
+ for (int i=0; i < aCount; i++) {
+ nscoord min = aChildInfos[i].min;
+ nscoord max = aChildInfos[i].max;
+ nscoord& c = aChildInfos[i].changed;
+
+ // figure our how much space to add or remove
+ if (c + aDiff < min) {
+ aDiff += (c - min);
+ c = min;
+ } else if (c + aDiff > max) {
+ aDiff -= (max - c);
+ c = max;
+ } else {
+ c += aDiff;
+ aDiff = 0;
+ }
+
+ // there is not space left? We are done
+ if (aDiff == 0)
+ break;
+ }
+
+ aSpaceLeft = aDiff;
+}
+
+/**
+ * Ok if we want to resize a child we will know the actual size in pixels we want it to be.
+ * This is not the preferred size. But they only way we can change a child is my manipulating its
+ * preferred size. So give the actual pixel size this return method will return figure out the preferred
+ * size and set it.
+ */
+
+void
+nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff,
+ nsSplitterInfo* aChildrenBeforeInfos,
+ nsSplitterInfo* aChildrenAfterInfos,
+ int32_t aChildrenBeforeCount,
+ int32_t aChildrenAfterCount,
+ bool aBounded)
+{
+ nscoord spaceLeft;
+ AddRemoveSpace(aDiff, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft);
+
+ // if there is any space left over remove it from the dif we were originally given
+ aDiff -= spaceLeft;
+ AddRemoveSpace(-aDiff, aChildrenAfterInfos,aChildrenAfterCount,spaceLeft);
+
+ if (spaceLeft != 0) {
+ if (aBounded) {
+ aDiff += spaceLeft;
+ AddRemoveSpace(spaceLeft, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft);
+ }
+ }
+}
diff --git a/layout/xul/nsSplitterFrame.h b/layout/xul/nsSplitterFrame.h
new file mode 100644
index 000000000..df8872255
--- /dev/null
+++ b/layout/xul/nsSplitterFrame.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// nsSplitterFrame
+//
+
+#ifndef nsSplitterFrame_h__
+#define nsSplitterFrame_h__
+
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsSplitterFrameInner;
+
+nsIFrame* NS_NewSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+class nsSplitterFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsSplitterFrame(nsStyleContext* aContext);
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("SplitterFrame"), aResult);
+ }
+#endif
+
+ // nsIFrame overrides
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor) override;
+
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+
+ NS_IMETHOD HandlePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld) override;
+
+ NS_IMETHOD HandleDrag(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void GetInitialOrientation(bool& aIsHorizontal) override;
+
+private:
+
+ friend class nsSplitterFrameInner;
+ nsSplitterFrameInner* mInner;
+
+}; // class nsSplitterFrame
+
+#endif
diff --git a/layout/xul/nsSprocketLayout.cpp b/layout/xul/nsSprocketLayout.cpp
new file mode 100644
index 000000000..8ce73566b
--- /dev/null
+++ b/layout/xul/nsSprocketLayout.cpp
@@ -0,0 +1,1652 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsBoxLayoutState.h"
+#include "nsSprocketLayout.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsContainerFrame.h"
+#include "nsBoxFrame.h"
+#include "StackArena.h"
+#include "mozilla/Likely.h"
+#include <algorithm>
+
+nsBoxLayout* nsSprocketLayout::gInstance = nullptr;
+
+//#define DEBUG_GROW
+
+#define DEBUG_SPRING_SIZE 8
+#define DEBUG_BORDER_SIZE 2
+#define COIL_SIZE 8
+
+
+nsresult
+NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout)
+{
+ if (!nsSprocketLayout::gInstance) {
+ nsSprocketLayout::gInstance = new nsSprocketLayout();
+ NS_IF_ADDREF(nsSprocketLayout::gInstance);
+ }
+ // we have not instance variables so just return our static one.
+ aNewLayout = nsSprocketLayout::gInstance;
+ return NS_OK;
+}
+
+/*static*/ void
+nsSprocketLayout::Shutdown()
+{
+ NS_IF_RELEASE(gInstance);
+}
+
+nsSprocketLayout::nsSprocketLayout()
+{
+}
+
+bool
+nsSprocketLayout::IsXULHorizontal(nsIFrame* aBox)
+{
+ return (aBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
+}
+
+void
+nsSprocketLayout::GetFrameState(nsIFrame* aBox, nsFrameState& aState)
+{
+ aState = aBox->GetStateBits();
+}
+
+static uint8_t
+GetFrameDirection(nsIFrame* aBox)
+{
+ return aBox->StyleVisibility()->mDirection;
+}
+
+static void
+HandleBoxPack(nsIFrame* aBox, const nsFrameState& aFrameState, nscoord& aX, nscoord& aY,
+ const nsRect& aOriginalRect, const nsRect& aClientRect)
+{
+ // In the normal direction we lay out our kids in the positive direction (e.g., |x| will get
+ // bigger for a horizontal box, and |y| will get bigger for a vertical box). In the reverse
+ // direction, the opposite is true. We'll be laying out each child at a smaller |x| or
+ // |y|.
+ uint8_t frameDirection = GetFrameDirection(aBox);
+
+ if (aFrameState & NS_STATE_IS_HORIZONTAL) {
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) {
+ // The normal direction. |x| increases as we move through our children.
+ aX = aClientRect.x;
+ }
+ else {
+ // The reverse direction. |x| decreases as we move through our children.
+ aX = aClientRect.x + aOriginalRect.width;
+ }
+ // |y| is always in the normal direction in horizontal boxes
+ aY = aClientRect.y;
+ }
+ else {
+ // take direction property into account for |x| in vertical boxes
+ if (frameDirection == NS_STYLE_DIRECTION_LTR) {
+ // The normal direction. |x| increases as we move through our children.
+ aX = aClientRect.x;
+ }
+ else {
+ // The reverse direction. |x| decreases as we move through our children.
+ aX = aClientRect.x + aOriginalRect.width;
+ }
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) {
+ // The normal direction. |y| increases as we move through our children.
+ aY = aClientRect.y;
+ }
+ else {
+ // The reverse direction. |y| decreases as we move through our children.
+ aY = aClientRect.y + aOriginalRect.height;
+ }
+ }
+
+ // Get our pack/alignment information.
+ nsIFrame::Halignment halign = aBox->GetXULHAlign();
+ nsIFrame::Valignment valign = aBox->GetXULVAlign();
+
+ // The following code handles box PACKING. Packing comes into play in the case where the computed size for
+ // all of our children (now stored in our client rect) is smaller than the size available for
+ // the box (stored in |aOriginalRect|).
+ //
+ // Here we adjust our |x| and |y| variables accordingly so that we start at the beginning,
+ // middle, or end of the box.
+ //
+ // XXXdwh JUSTIFY needs to be implemented!
+ if (aFrameState & NS_STATE_IS_HORIZONTAL) {
+ switch(halign) {
+ case nsBoxFrame::hAlign_Left:
+ break; // Nothing to do. The default initialized us properly.
+
+ case nsBoxFrame::hAlign_Center:
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aX += (aOriginalRect.width - aClientRect.width)/2;
+ else
+ aX -= (aOriginalRect.width - aClientRect.width)/2;
+ break;
+
+ case nsBoxFrame::hAlign_Right:
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aX += (aOriginalRect.width - aClientRect.width);
+ else
+ aX -= (aOriginalRect.width - aClientRect.width);
+ break; // Nothing to do for the reverse dir. The default initialized us properly.
+ }
+ } else {
+ switch(valign) {
+ case nsBoxFrame::vAlign_Top:
+ case nsBoxFrame::vAlign_BaseLine: // This value is technically impossible to specify for pack.
+ break; // Don't do anything. We were initialized correctly.
+
+ case nsBoxFrame::vAlign_Middle:
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aY += (aOriginalRect.height - aClientRect.height)/2;
+ else
+ aY -= (aOriginalRect.height - aClientRect.height)/2;
+ break;
+
+ case nsBoxFrame::vAlign_Bottom:
+ if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aY += (aOriginalRect.height - aClientRect.height);
+ else
+ aY -= (aOriginalRect.height - aClientRect.height);
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSprocketLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ // See if we are collapsed. If we are, then simply iterate over all our
+ // children and give them a rect of 0 width and height.
+ if (aBox->IsXULCollapsed()) {
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while(child)
+ {
+ nsBoxFrame::LayoutChildAt(aState, child, nsRect(0,0,0,0));
+ child = nsBox::GetNextXULBox(child);
+ }
+ return NS_OK;
+ }
+
+ nsBoxLayoutState::AutoReflowDepth depth(aState);
+ mozilla::AutoStackArena arena;
+
+ // ----- figure out our size ----------
+ const nsSize originalSize = aBox->GetSize();
+
+ // -- make sure we remove our border and padding ----
+ nsRect clientRect;
+ aBox->GetXULClientRect(clientRect);
+
+ // |originalClientRect| represents the rect of the entire box (excluding borders
+ // and padding). We store it here because we're going to use |clientRect| to hold
+ // the required size for all our kids. As an example, consider an hbox with a
+ // specified width of 300. If the kids total only 150 pixels of width, then
+ // we have 150 pixels left over. |clientRect| is going to hold a width of 150 and
+ // is going to be adjusted based off the value of the PACK property. If flexible
+ // objects are in the box, then the two rects will match.
+ nsRect originalClientRect(clientRect);
+
+ // The frame state contains cached knowledge about our box, such as our orientation
+ // and direction.
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+
+ // Build a list of our children's desired sizes and computed sizes
+ nsBoxSize* boxSizes = nullptr;
+ nsComputedBoxSize* computedBoxSizes = nullptr;
+
+ nscoord min = 0;
+ nscoord max = 0;
+ int32_t flexes = 0;
+ PopulateBoxSizes(aBox, aState, boxSizes, min, max, flexes);
+
+ // The |size| variable will hold the total size of children along the axis of
+ // the box. Continuing with the example begun in the comment above, size would
+ // be 150 pixels.
+ nscoord size = clientRect.width;
+ if (!IsXULHorizontal(aBox))
+ size = clientRect.height;
+ ComputeChildSizes(aBox, aState, size, boxSizes, computedBoxSizes);
+
+ // After the call to ComputeChildSizes, the |size| variable contains the
+ // total required size of all the children. We adjust our clientRect in the
+ // appropriate dimension to match this size. In our example, we now assign
+ // 150 pixels into the clientRect.width.
+ //
+ // The variables |min| and |max| hold the minimum required size box must be
+ // in the OPPOSITE orientation, e.g., for a horizontal box, |min| is the minimum
+ // height we require to enclose our children, and |max| is the maximum height
+ // required to enclose our children.
+ if (IsXULHorizontal(aBox)) {
+ clientRect.width = size;
+ if (clientRect.height < min)
+ clientRect.height = min;
+
+ if (frameState & NS_STATE_AUTO_STRETCH) {
+ if (clientRect.height > max)
+ clientRect.height = max;
+ }
+ } else {
+ clientRect.height = size;
+ if (clientRect.width < min)
+ clientRect.width = min;
+
+ if (frameState & NS_STATE_AUTO_STRETCH) {
+ if (clientRect.width > max)
+ clientRect.width = max;
+ }
+ }
+
+ // With the sizes computed, now it's time to lay out our children.
+ bool finished;
+ nscoord passes = 0;
+
+ // We flow children at their preferred locations (along with the appropriate computed flex).
+ // After we flow a child, it is possible that the child will change its size. If/when this happens,
+ // we have to do another pass. Typically only 2 passes are required, but the code is prepared to
+ // do as many passes as are necessary to achieve equilibrium.
+ nscoord x = 0;
+ nscoord y = 0;
+ nscoord origX = 0;
+ nscoord origY = 0;
+
+ // |childResized| lets us know if a child changed its size after we attempted to lay it out at
+ // the specified size. If this happens, we usually have to do another pass.
+ bool childResized = false;
+
+ // |passes| stores our number of passes. If for any reason we end up doing more than, say, 10
+ // passes, we assert to indicate that something is seriously screwed up.
+ passes = 0;
+ do
+ {
+#ifdef DEBUG_REFLOW
+ if (passes > 0) {
+ AddIndents();
+ printf("ChildResized doing pass: %d\n", passes);
+ }
+#endif
+
+ // Always assume that we're done. This will change if, for example, children don't stay
+ // the same size after being flowed.
+ finished = true;
+
+ // Handle box packing.
+ HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect);
+
+ // Now that packing is taken care of we set up a few additional
+ // tracking variables.
+ origX = x;
+ origY = y;
+
+ // Now we iterate over our box children and our box size lists in
+ // parallel. For each child, we look at its sizes and figure out
+ // where to place it.
+ nsComputedBoxSize* childComputedBoxSize = computedBoxSizes;
+ nsBoxSize* childBoxSize = boxSizes;
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ int32_t count = 0;
+ while (child || (childBoxSize && childBoxSize->bogus))
+ {
+ // If for some reason, our lists are not the same length, we guard
+ // by bailing out of the loop.
+ if (childBoxSize == nullptr) {
+ NS_NOTREACHED("Lists not the same length.");
+ break;
+ }
+
+ nscoord width = clientRect.width;
+ nscoord height = clientRect.height;
+
+ if (!childBoxSize->bogus) {
+ // We have a valid box size entry. This entry already contains information about our
+ // sizes along the axis of the box (e.g., widths in a horizontal box). If our default
+ // ALIGN is not stretch, however, then we also need to know the child's size along the
+ // opposite axis.
+ if (!(frameState & NS_STATE_AUTO_STRETCH)) {
+ nsSize prefSize = child->GetXULPrefSize(aState);
+ nsSize minSize = child->GetXULMinSize(aState);
+ nsSize maxSize = child->GetXULMaxSize(aState);
+ prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize);
+
+ AddMargin(child, prefSize);
+ width = std::min(prefSize.width, originalClientRect.width);
+ height = std::min(prefSize.height, originalClientRect.height);
+ }
+ }
+
+ // Obtain the computed size along the axis of the box for this child from the computedBoxSize entry.
+ // We store the result in |width| for horizontal boxes and |height| for vertical boxes.
+ if (frameState & NS_STATE_IS_HORIZONTAL)
+ width = childComputedBoxSize->size;
+ else
+ height = childComputedBoxSize->size;
+
+ // Adjust our x/y for the left/right spacing.
+ if (frameState & NS_STATE_IS_HORIZONTAL) {
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ x += (childBoxSize->left);
+ else
+ x -= (childBoxSize->right);
+ } else {
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ y += (childBoxSize->left);
+ else
+ y -= (childBoxSize->right);
+ }
+
+ // Now we build a child rect.
+ nscoord rectX = x;
+ nscoord rectY = y;
+ if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) {
+ if (frameState & NS_STATE_IS_HORIZONTAL)
+ rectX -= width;
+ else
+ rectY -= height;
+ }
+
+ // We now create an accurate child rect based off our computed size information.
+ nsRect childRect(rectX, rectY, width, height);
+
+ // Sanity check against our clientRect. It is possible that a child specified
+ // a size that is too large to fit. If that happens, then we have to grow
+ // our client rect. Remember, clientRect is not the total rect of the enclosing
+ // box. It currently holds our perception of how big the children needed to
+ // be.
+ if (childRect.width > clientRect.width)
+ clientRect.width = childRect.width;
+
+ if (childRect.height > clientRect.height)
+ clientRect.height = childRect.height;
+
+ // Either |nextX| or |nextY| is updated by this function call, according
+ // to our axis.
+ nscoord nextX = x;
+ nscoord nextY = y;
+
+ ComputeChildsNextPosition(aBox, x, y, nextX, nextY, childRect);
+
+ // Now we further update our nextX/Y along our axis.
+ // We also set childRect.y/x along the opposite axis appropriately for a
+ // stretch alignment. (Non-stretch alignment is handled below.)
+ if (frameState & NS_STATE_IS_HORIZONTAL) {
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ nextX += (childBoxSize->right);
+ else
+ nextX -= (childBoxSize->left);
+ childRect.y = originalClientRect.y;
+ }
+ else {
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ nextY += (childBoxSize->right);
+ else
+ nextY -= (childBoxSize->left);
+ if (GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR) {
+ childRect.x = originalClientRect.x;
+ } else {
+ // keep the right edge of the box the same
+ childRect.x = clientRect.x + originalClientRect.width - childRect.width;
+ }
+ }
+
+ // If we encounter a completely bogus box size, we just leave this child completely
+ // alone and continue through the loop to the next child.
+ if (childBoxSize->bogus)
+ {
+ childComputedBoxSize = childComputedBoxSize->next;
+ childBoxSize = childBoxSize->next;
+ count++;
+ x = nextX;
+ y = nextY;
+ continue;
+ }
+
+ nsMargin margin(0,0,0,0);
+
+ bool layout = true;
+
+ // Deflate the rect of our child by its margin.
+ child->GetXULMargin(margin);
+ childRect.Deflate(margin);
+ if (childRect.width < 0)
+ childRect.width = 0;
+ if (childRect.height < 0)
+ childRect.height = 0;
+
+ // Now we're trying to figure out if we have to lay out this child, i.e., to call
+ // the child's XULLayout method.
+ if (passes > 0) {
+ layout = false;
+ } else {
+ // Always perform layout if we are dirty or have dirty children
+ if (!NS_SUBTREE_DIRTY(child))
+ layout = false;
+ }
+
+ nsRect oldRect(child->GetRect());
+
+ // Non-stretch alignment will be handled in AlignChildren(), so don't
+ // change child out-of-axis positions yet.
+ if (!(frameState & NS_STATE_AUTO_STRETCH)) {
+ if (frameState & NS_STATE_IS_HORIZONTAL) {
+ childRect.y = oldRect.y;
+ } else {
+ childRect.x = oldRect.x;
+ }
+ }
+
+ // We computed a childRect. Now we want to set the bounds of the child to be that rect.
+ // If our old rect is different, then we know our size changed and we cache that fact
+ // in the |sizeChanged| variable.
+
+ child->SetXULBounds(aState, childRect);
+ bool sizeChanged = (childRect.width != oldRect.width ||
+ childRect.height != oldRect.height);
+
+ if (sizeChanged) {
+ // Our size is different. Sanity check against our maximum allowed size to ensure
+ // we didn't exceed it.
+ nsSize minSize = child->GetXULMinSize(aState);
+ nsSize maxSize = child->GetXULMaxSize(aState);
+ maxSize = nsBox::BoundsCheckMinMax(minSize, maxSize);
+
+ // make sure the size is in our max size.
+ if (childRect.width > maxSize.width)
+ childRect.width = maxSize.width;
+
+ if (childRect.height > maxSize.height)
+ childRect.height = maxSize.height;
+
+ // set it again
+ child->SetXULBounds(aState, childRect);
+ }
+
+ // If we already determined that layout was required or if our size has changed, then
+ // we make sure to call layout on the child, since its children may need to be shifted
+ // around as a result of the size change.
+ if (layout || sizeChanged)
+ child->XULLayout(aState);
+
+ // If the child was a block or inline (e.g., HTML) it may have changed its rect *during* layout.
+ // We have to check for this.
+ nsRect newChildRect(child->GetRect());
+
+ if (!newChildRect.IsEqualInterior(childRect)) {
+#ifdef DEBUG_GROW
+ child->XULDumpBox(stdout);
+ printf(" GREW from (%d,%d) -> (%d,%d)\n", childRect.width, childRect.height, newChildRect.width, newChildRect.height);
+#endif
+ newChildRect.Inflate(margin);
+ childRect.Inflate(margin);
+
+ // The child changed size during layout. The ChildResized method handles this
+ // scenario.
+ ChildResized(aBox,
+ aState,
+ child,
+ childBoxSize,
+ childComputedBoxSize,
+ boxSizes,
+ computedBoxSizes,
+ childRect,
+ newChildRect,
+ clientRect,
+ flexes,
+ finished);
+
+ // We note that a child changed size, which means that another pass will be required.
+ childResized = true;
+
+ // Now that a child resized, it's entirely possible that OUR rect is too small. Now we
+ // ensure that |originalClientRect| is grown to accommodate the size of |clientRect|.
+ if (clientRect.width > originalClientRect.width)
+ originalClientRect.width = clientRect.width;
+
+ if (clientRect.height > originalClientRect.height)
+ originalClientRect.height = clientRect.height;
+
+ if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) {
+ // Our childRect had its XMost() or YMost() (depending on our layout
+ // direction), positioned at a certain point. Ensure that the
+ // newChildRect satisfies the same constraint. Note that this is
+ // just equivalent to adjusting the x/y by the difference in
+ // width/height between childRect and newChildRect. So we don't need
+ // to reaccount for the left and right of the box layout state again.
+ if (frameState & NS_STATE_IS_HORIZONTAL)
+ newChildRect.x = childRect.XMost() - newChildRect.width;
+ else
+ newChildRect.y = childRect.YMost() - newChildRect.height;
+ }
+
+ if (!(frameState & NS_STATE_IS_HORIZONTAL)) {
+ if (GetFrameDirection(aBox) != NS_STYLE_DIRECTION_LTR) {
+ // keep the right edge the same
+ newChildRect.x = childRect.XMost() - newChildRect.width;
+ }
+ }
+
+ // If the child resized then recompute its position.
+ ComputeChildsNextPosition(aBox, x, y, nextX, nextY, newChildRect);
+
+ if (newChildRect.width >= margin.left + margin.right && newChildRect.height >= margin.top + margin.bottom)
+ newChildRect.Deflate(margin);
+
+ if (childRect.width >= margin.left + margin.right && childRect.height >= margin.top + margin.bottom)
+ childRect.Deflate(margin);
+
+ child->SetXULBounds(aState, newChildRect);
+
+ // If we are the first box that changed size, then we don't need to do a second pass
+ if (count == 0)
+ finished = true;
+ }
+
+ // Now update our x/y finally.
+ x = nextX;
+ y = nextY;
+
+ // Move to the next child.
+ childComputedBoxSize = childComputedBoxSize->next;
+ childBoxSize = childBoxSize->next;
+
+ child = nsBox::GetNextXULBox(child);
+ count++;
+ }
+
+ // Sanity-checking code to ensure we don't do an infinite # of passes.
+ passes++;
+ NS_ASSERTION(passes < 10, "A Box's child is constantly growing!!!!!");
+ if (passes > 10)
+ break;
+ } while (false == finished);
+
+ // Get rid of our size lists.
+ while(boxSizes)
+ {
+ nsBoxSize* toDelete = boxSizes;
+ boxSizes = boxSizes->next;
+ delete toDelete;
+ }
+
+ while(computedBoxSizes)
+ {
+ nsComputedBoxSize* toDelete = computedBoxSizes;
+ computedBoxSizes = computedBoxSizes->next;
+ delete toDelete;
+ }
+
+ if (childResized) {
+ // See if one of our children forced us to get bigger
+ nsRect tmpClientRect(originalClientRect);
+ nsMargin bp(0,0,0,0);
+ aBox->GetXULBorderAndPadding(bp);
+ tmpClientRect.Inflate(bp);
+
+ if (tmpClientRect.width > originalSize.width || tmpClientRect.height > originalSize.height)
+ {
+ // if it did reset our bounds.
+ nsRect bounds(aBox->GetRect());
+ if (tmpClientRect.width > originalSize.width)
+ bounds.width = tmpClientRect.width;
+
+ if (tmpClientRect.height > originalSize.height)
+ bounds.height = tmpClientRect.height;
+
+ aBox->SetXULBounds(aState, bounds);
+ }
+ }
+
+ // Because our size grew, we now have to readjust because of box packing. Repack
+ // in order to update our x and y to the correct values.
+ HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect);
+
+ // Compare against our original x and y and only worry about adjusting the children if
+ // we really did have to change the positions because of packing (typically for 'center'
+ // or 'end' pack values).
+ if (x != origX || y != origY) {
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ // reposition all our children
+ while (child)
+ {
+ nsRect childRect(child->GetRect());
+ childRect.x += (x - origX);
+ childRect.y += (y - origY);
+ child->SetXULBounds(aState, childRect);
+ child = nsBox::GetNextXULBox(child);
+ }
+ }
+
+ // Perform out-of-axis alignment for non-stretch alignments
+ if (!(frameState & NS_STATE_AUTO_STRETCH)) {
+ AlignChildren(aBox, aState);
+ }
+
+ // That's it! If you made it this far without having a nervous breakdown,
+ // congratulations! Go get yourself a beer.
+ return NS_OK;
+}
+
+void
+nsSprocketLayout::PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aState, nsBoxSize*& aBoxSizes, nscoord& aMinSize, nscoord& aMaxSize, int32_t& aFlexes)
+{
+ // used for the equal size flag
+ nscoord biggestPrefWidth = 0;
+ nscoord biggestMinWidth = 0;
+ nscoord smallestMaxWidth = NS_INTRINSICSIZE;
+
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+
+ //if (frameState & NS_STATE_CURRENTLY_IN_DEBUG)
+ // printf("In debug\n");
+
+ aMinSize = 0;
+ aMaxSize = NS_INTRINSICSIZE;
+
+ bool isHorizontal;
+
+ if (IsXULHorizontal(aBox))
+ isHorizontal = true;
+ else
+ isHorizontal = false;
+
+ // this is a nice little optimization
+ // it turns out that if we only have 1 flexable child
+ // then it does not matter what its preferred size is
+ // there is nothing to flex it relative. This is great
+ // because we can avoid asking for a preferred size in this
+ // case. Why is this good? Well you might have html inside it
+ // and asking html for its preferred size is rather expensive.
+ // so we can just optimize it out this way.
+
+ // set flexes
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ aFlexes = 0;
+ nsBoxSize* currentBox = nullptr;
+
+#if 0
+ nsBoxSize* start = aBoxSizes;
+
+ while(child)
+ {
+ // ok if we started with a list move down the list
+ // until we reach the end. Then start looking at childen.
+ // This feature is used extensively for Grid.
+ nscoord flex = 0;
+
+ if (!start) {
+ if (!currentBox) {
+ aBoxSizes = new (aState) nsBoxSize();
+ currentBox = aBoxSizes;
+ } else {
+ currentBox->next = new (aState) nsBoxSize();
+ currentBox = currentBox->next;
+ }
+
+
+ flex = child->GetXULFlex();
+
+ currentBox->flex = flex;
+ currentBox->collapsed = child->IsXULCollapsed();
+ } else {
+ flex = start->flex;
+ start = start->next;
+ }
+
+ if (flex > 0)
+ aFlexes++;
+
+ child = GetNextXULBox(child);
+ }
+#endif
+
+ // get pref, min, max
+ child = nsBox::GetChildXULBox(aBox);
+ currentBox = aBoxSizes;
+ nsBoxSize* last = nullptr;
+
+ nscoord maxFlex = 0;
+ int32_t childCount = 0;
+
+ while(child)
+ {
+ while (currentBox && currentBox->bogus) {
+ last = currentBox;
+ currentBox = currentBox->next;
+ }
+ ++childCount;
+ nsSize pref(0,0);
+ nsSize minSize(0,0);
+ nsSize maxSize(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
+ nscoord ascent = 0;
+ bool collapsed = child->IsXULCollapsed();
+
+ if (!collapsed) {
+ // only one flexible child? Cool we will just make its preferred size
+ // 0 then and not even have to ask for it.
+ //if (flexes != 1) {
+
+ pref = child->GetXULPrefSize(aState);
+ minSize = child->GetXULMinSize(aState);
+ maxSize = nsBox::BoundsCheckMinMax(minSize, child->GetXULMaxSize(aState));
+ ascent = child->GetXULBoxAscent(aState);
+ nsMargin margin;
+ child->GetXULMargin(margin);
+ ascent += margin.top;
+ //}
+
+ pref = nsBox::BoundsCheck(minSize, pref, maxSize);
+
+ AddMargin(child, pref);
+ AddMargin(child, minSize);
+ AddMargin(child, maxSize);
+ }
+
+ if (!currentBox) {
+ // create one.
+ currentBox = new (aState) nsBoxSize();
+ if (!aBoxSizes) {
+ aBoxSizes = currentBox;
+ last = aBoxSizes;
+ } else {
+ last->next = currentBox;
+ last = currentBox;
+ }
+
+ nscoord minWidth;
+ nscoord maxWidth;
+ nscoord prefWidth;
+
+ // get sizes from child
+ if (isHorizontal) {
+ minWidth = minSize.width;
+ maxWidth = maxSize.width;
+ prefWidth = pref.width;
+ } else {
+ minWidth = minSize.height;
+ maxWidth = maxSize.height;
+ prefWidth = pref.height;
+ }
+
+ nscoord flex = child->GetXULFlex();
+
+ // set them if you collapsed you are not flexible.
+ if (collapsed) {
+ currentBox->flex = 0;
+ }
+ else {
+ if (flex > maxFlex) {
+ maxFlex = flex;
+ }
+ currentBox->flex = flex;
+ }
+
+ // we specified all our children are equal size;
+ if (frameState & NS_STATE_EQUAL_SIZE) {
+
+ if (prefWidth > biggestPrefWidth)
+ biggestPrefWidth = prefWidth;
+
+ if (minWidth > biggestMinWidth)
+ biggestMinWidth = minWidth;
+
+ if (maxWidth < smallestMaxWidth)
+ smallestMaxWidth = maxWidth;
+ } else { // not we can set our children right now.
+ currentBox->pref = prefWidth;
+ currentBox->min = minWidth;
+ currentBox->max = maxWidth;
+ }
+
+ NS_ASSERTION(minWidth <= prefWidth && prefWidth <= maxWidth,"Bad min, pref, max widths!");
+
+ }
+
+ if (!isHorizontal) {
+ if (minSize.width > aMinSize)
+ aMinSize = minSize.width;
+
+ if (maxSize.width < aMaxSize)
+ aMaxSize = maxSize.width;
+
+ } else {
+ if (minSize.height > aMinSize)
+ aMinSize = minSize.height;
+
+ if (maxSize.height < aMaxSize)
+ aMaxSize = maxSize.height;
+ }
+
+ currentBox->collapsed = collapsed;
+ aFlexes += currentBox->flex;
+
+ child = nsBox::GetNextXULBox(child);
+
+ last = currentBox;
+ currentBox = currentBox->next;
+
+ }
+
+ if (childCount > 0) {
+ nscoord maxAllowedFlex = nscoord_MAX / childCount;
+
+ if (MOZ_UNLIKELY(maxFlex > maxAllowedFlex)) {
+ // clamp all the flexes
+ currentBox = aBoxSizes;
+ while (currentBox) {
+ currentBox->flex = std::min(currentBox->flex, maxAllowedFlex);
+ currentBox = currentBox->next;
+ }
+ }
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(maxFlex == 0, "How did that happen?");
+ }
+#endif
+
+ // we specified all our children are equal size;
+ if (frameState & NS_STATE_EQUAL_SIZE) {
+ smallestMaxWidth = std::max(smallestMaxWidth, biggestMinWidth);
+ biggestPrefWidth = nsBox::BoundsCheck(biggestMinWidth, biggestPrefWidth, smallestMaxWidth);
+
+ currentBox = aBoxSizes;
+
+ while(currentBox)
+ {
+ if (!currentBox->collapsed) {
+ currentBox->pref = biggestPrefWidth;
+ currentBox->min = biggestMinWidth;
+ currentBox->max = smallestMaxWidth;
+ } else {
+ currentBox->pref = 0;
+ currentBox->min = 0;
+ currentBox->max = 0;
+ }
+ currentBox = currentBox->next;
+ }
+ }
+
+}
+
+void
+nsSprocketLayout::ComputeChildsNextPosition(nsIFrame* aBox,
+ const nscoord& aCurX,
+ const nscoord& aCurY,
+ nscoord& aNextX,
+ nscoord& aNextY,
+ const nsRect& aCurrentChildSize)
+{
+ // Get the position along the box axis for the child.
+ // The out-of-axis position is not set.
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+
+ if (IsXULHorizontal(aBox)) {
+ // horizontal box's children.
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aNextX = aCurX + aCurrentChildSize.width;
+ else
+ aNextX = aCurX - aCurrentChildSize.width;
+
+ } else {
+ // vertical box's children.
+ if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
+ aNextY = aCurY + aCurrentChildSize.height;
+ else
+ aNextY = aCurY - aCurrentChildSize.height;
+ }
+}
+
+void
+nsSprocketLayout::AlignChildren(nsIFrame* aBox,
+ nsBoxLayoutState& aState)
+{
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+ bool isHorizontal = (frameState & NS_STATE_IS_HORIZONTAL) != 0;
+ nsRect clientRect;
+ aBox->GetXULClientRect(clientRect);
+
+ NS_PRECONDITION(!(frameState & NS_STATE_AUTO_STRETCH),
+ "Only AlignChildren() with non-stretch alignment");
+
+ // These are only calculated if needed
+ nsIFrame::Halignment halign;
+ nsIFrame::Valignment valign;
+ nscoord maxAscent = 0;
+ bool isLTR;
+
+ if (isHorizontal) {
+ valign = aBox->GetXULVAlign();
+ if (valign == nsBoxFrame::vAlign_BaseLine) {
+ maxAscent = aBox->GetXULBoxAscent(aState);
+ }
+ } else {
+ isLTR = GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR;
+ halign = aBox->GetXULHAlign();
+ }
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while (child) {
+
+ nsMargin margin;
+ child->GetXULMargin(margin);
+ nsRect childRect = child->GetRect();
+
+ if (isHorizontal) {
+ const nscoord startAlign = clientRect.y + margin.top;
+ const nscoord endAlign =
+ clientRect.YMost() - margin.bottom - childRect.height;
+
+ nscoord y = 0;
+ switch (valign) {
+ case nsBoxFrame::vAlign_Top:
+ y = startAlign;
+ break;
+ case nsBoxFrame::vAlign_Middle:
+ // Should this center the border box?
+ // This centers the margin box, the historical behavior.
+ y = (startAlign + endAlign) / 2;
+ break;
+ case nsBoxFrame::vAlign_Bottom:
+ y = endAlign;
+ break;
+ case nsBoxFrame::vAlign_BaseLine:
+ // Alignments don't force the box to grow (only sizes do),
+ // so keep the children within the box.
+ y = maxAscent - child->GetXULBoxAscent(aState);
+ y = std::max(startAlign, y);
+ y = std::min(y, endAlign);
+ break;
+ }
+
+ childRect.y = y;
+
+ } else { // vertical box
+ const nscoord leftAlign = clientRect.x + margin.left;
+ const nscoord rightAlign =
+ clientRect.XMost() - margin.right - childRect.width;
+
+ nscoord x = 0;
+ switch (halign) {
+ case nsBoxFrame::hAlign_Left: // start
+ x = isLTR ? leftAlign : rightAlign;
+ break;
+ case nsBoxFrame::hAlign_Center:
+ x = (leftAlign + rightAlign) / 2;
+ break;
+ case nsBoxFrame::hAlign_Right: // end
+ x = isLTR ? rightAlign : leftAlign;
+ break;
+ }
+
+ childRect.x = x;
+ }
+
+ if (childRect.TopLeft() != child->GetPosition()) {
+ child->SetXULBounds(aState, childRect);
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+}
+
+void
+nsSprocketLayout::ChildResized(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nsIFrame* aChild,
+ nsBoxSize* aChildBoxSize,
+ nsComputedBoxSize* aChildComputedSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize* aComputedBoxSizes,
+ const nsRect& aChildLayoutRect,
+ nsRect& aChildActualRect,
+ nsRect& aContainingRect,
+ int32_t aFlexes,
+ bool& aFinished)
+
+{
+ nsRect childCurrentRect(aChildLayoutRect);
+
+ bool isHorizontal = IsXULHorizontal(aBox);
+ nscoord childLayoutWidth = GET_WIDTH(aChildLayoutRect,isHorizontal);
+ nscoord& childActualWidth = GET_WIDTH(aChildActualRect,isHorizontal);
+ nscoord& containingWidth = GET_WIDTH(aContainingRect,isHorizontal);
+
+ //nscoord childLayoutHeight = GET_HEIGHT(aChildLayoutRect,isHorizontal);
+ nscoord& childActualHeight = GET_HEIGHT(aChildActualRect,isHorizontal);
+ nscoord& containingHeight = GET_HEIGHT(aContainingRect,isHorizontal);
+
+ bool recompute = false;
+
+ // if we are a horizontal box see if the child will fit inside us.
+ if ( childActualHeight > containingHeight) {
+ // if we are a horizontal box and the child is bigger than our height
+
+ // ok if the height changed then we need to reflow everyone but us at the new height
+ // so we will set the changed index to be us. And signal that we need a new pass.
+
+ nsSize min = aChild->GetXULMinSize(aState);
+ nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(aState));
+ AddMargin(aChild, max);
+
+ if (isHorizontal)
+ childActualHeight = max.height < childActualHeight ? max.height : childActualHeight;
+ else
+ childActualHeight = max.width < childActualHeight ? max.width : childActualHeight;
+
+ // only set if it changes
+ if (childActualHeight > containingHeight) {
+ containingHeight = childActualHeight;
+
+ // remember we do not need to clear the resized list because changing the height of a horizontal box
+ // will not affect the width of any of its children because block flow left to right, top to bottom. Just trust me
+ // on this one.
+ aFinished = false;
+
+ // only recompute if there are flexes.
+ if (aFlexes > 0) {
+ // relayout everything
+ recompute = true;
+ InvalidateComputedSizes(aComputedBoxSizes);
+ nsComputedBoxSize* node = aComputedBoxSizes;
+
+ while(node) {
+ node->resized = false;
+ node = node->next;
+ }
+
+ }
+ }
+ }
+
+ if (childActualWidth > childLayoutWidth) {
+ nsSize min = aChild->GetXULMinSize(aState);
+ nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(aState));
+
+ AddMargin(aChild, max);
+
+ // our width now becomes the new size
+
+ if (isHorizontal)
+ childActualWidth = max.width < childActualWidth ? max.width : childActualWidth;
+ else
+ childActualWidth = max.height < childActualWidth ? max.height : childActualWidth;
+
+ if (childActualWidth > childLayoutWidth) {
+ aChildComputedSize->size = childActualWidth;
+ aChildBoxSize->min = childActualWidth;
+ if (aChildBoxSize->pref < childActualWidth)
+ aChildBoxSize->pref = childActualWidth;
+ if (aChildBoxSize->max < childActualWidth)
+ aChildBoxSize->max = childActualWidth;
+
+ // if we have flexible elements with us then reflex things. Otherwise we can skip doing it.
+ if (aFlexes > 0) {
+ InvalidateComputedSizes(aComputedBoxSizes);
+
+ nsComputedBoxSize* node = aComputedBoxSizes;
+ aChildComputedSize->resized = true;
+
+ while(node) {
+ if (node->resized)
+ node->valid = true;
+
+ node = node->next;
+ }
+
+ recompute = true;
+ aFinished = false;
+ } else {
+ containingWidth += aChildComputedSize->size - childLayoutWidth;
+ }
+ }
+ }
+
+ if (recompute)
+ ComputeChildSizes(aBox, aState, containingWidth, aBoxSizes, aComputedBoxSizes);
+
+ if (!childCurrentRect.IsEqualInterior(aChildActualRect)) {
+ // the childRect includes the margin
+ // make sure we remove it before setting
+ // the bounds.
+ nsMargin margin(0,0,0,0);
+ aChild->GetXULMargin(margin);
+ nsRect rect(aChildActualRect);
+ if (rect.width >= margin.left + margin.right && rect.height >= margin.top + margin.bottom)
+ rect.Deflate(margin);
+
+ aChild->SetXULBounds(aState, rect);
+ aChild->XULLayout(aState);
+ }
+
+}
+
+void
+nsSprocketLayout::InvalidateComputedSizes(nsComputedBoxSize* aComputedBoxSizes)
+{
+ while(aComputedBoxSizes) {
+ aComputedBoxSizes->valid = false;
+ aComputedBoxSizes = aComputedBoxSizes->next;
+ }
+}
+
+void
+nsSprocketLayout::ComputeChildSizes(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nscoord& aGivenSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize*& aComputedBoxSizes)
+{
+
+ //nscoord onePixel = aState.PresContext()->IntScaledPixelsToTwips(1);
+
+ int32_t sizeRemaining = aGivenSize;
+ int32_t spacerConstantsRemaining = 0;
+
+ // ----- calculate the spacers constants and the size remaining -----
+
+ if (!aComputedBoxSizes)
+ aComputedBoxSizes = new (aState) nsComputedBoxSize();
+
+ nsBoxSize* boxSizes = aBoxSizes;
+ nsComputedBoxSize* computedBoxSizes = aComputedBoxSizes;
+ int32_t count = 0;
+ int32_t validCount = 0;
+
+ while (boxSizes)
+ {
+
+ NS_ASSERTION((boxSizes->min <= boxSizes->pref && boxSizes->pref <= boxSizes->max),"bad pref, min, max size");
+
+
+ // ignore collapsed children
+ // if (boxSizes->collapsed)
+ // {
+ // computedBoxSizes->valid = true;
+ // computedBoxSizes->size = boxSizes->pref;
+ // validCount++;
+ // boxSizes->flex = 0;
+ // }// else {
+
+ if (computedBoxSizes->valid) {
+ sizeRemaining -= computedBoxSizes->size;
+ validCount++;
+ } else {
+ if (boxSizes->flex == 0)
+ {
+ computedBoxSizes->valid = true;
+ computedBoxSizes->size = boxSizes->pref;
+ validCount++;
+ }
+
+ spacerConstantsRemaining += boxSizes->flex;
+ sizeRemaining -= boxSizes->pref;
+ }
+
+ sizeRemaining -= (boxSizes->left + boxSizes->right);
+
+ //}
+
+ boxSizes = boxSizes->next;
+
+ if (boxSizes && !computedBoxSizes->next)
+ computedBoxSizes->next = new (aState) nsComputedBoxSize();
+
+ computedBoxSizes = computedBoxSizes->next;
+ count++;
+ }
+
+ // everything accounted for?
+ if (validCount < count)
+ {
+ // ----- Ok we are give a size to fit into so stretch or squeeze to fit
+ // ----- Make sure we look at our min and max size
+ bool limit = true;
+ for (int pass=1; true == limit; pass++)
+ {
+ limit = false;
+ boxSizes = aBoxSizes;
+ computedBoxSizes = aComputedBoxSizes;
+
+ while (boxSizes) {
+
+ // ignore collapsed spacers
+
+ // if (!boxSizes->collapsed) {
+
+ nscoord pref = 0;
+ nscoord max = NS_INTRINSICSIZE;
+ nscoord min = 0;
+ nscoord flex = 0;
+
+ pref = boxSizes->pref;
+ min = boxSizes->min;
+ max = boxSizes->max;
+ flex = boxSizes->flex;
+
+ // ----- look at our min and max limits make sure we aren't too small or too big -----
+ if (!computedBoxSizes->valid) {
+ int32_t newSize = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining);
+
+ if (newSize<=min) {
+ computedBoxSizes->size = min;
+ computedBoxSizes->valid = true;
+ spacerConstantsRemaining -= flex;
+ sizeRemaining += pref;
+ sizeRemaining -= min;
+ limit = true;
+ } else if (newSize>=max) {
+ computedBoxSizes->size = max;
+ computedBoxSizes->valid = true;
+ spacerConstantsRemaining -= flex;
+ sizeRemaining += pref;
+ sizeRemaining -= max;
+ limit = true;
+ }
+ }
+ // }
+ boxSizes = boxSizes->next;
+ computedBoxSizes = computedBoxSizes->next;
+ }
+ }
+ }
+
+ // ---- once we have removed and min and max issues just stretch us out in the remaining space
+ // ---- or shrink us. Depends on the size remaining and the spacer constants
+ aGivenSize = 0;
+ boxSizes = aBoxSizes;
+ computedBoxSizes = aComputedBoxSizes;
+
+ while (boxSizes) {
+
+ // ignore collapsed spacers
+ // if (!(boxSizes && boxSizes->collapsed)) {
+
+ nscoord pref = 0;
+ nscoord flex = 0;
+ pref = boxSizes->pref;
+ flex = boxSizes->flex;
+
+ if (!computedBoxSizes->valid) {
+ computedBoxSizes->size = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining);
+ computedBoxSizes->valid = true;
+ }
+
+ aGivenSize += (boxSizes->left + boxSizes->right);
+ aGivenSize += computedBoxSizes->size;
+
+ // }
+
+ boxSizes = boxSizes->next;
+ computedBoxSizes = computedBoxSizes->next;
+ }
+}
+
+
+nsSize
+nsSprocketLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize vpref (0, 0);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ nscoord biggestPref = 0;
+
+ // run through all the children and get their min, max, and preferred sizes
+ // return us the size of the box
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+ bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
+ int32_t count = 0;
+
+ while (child)
+ {
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ nsSize pref = child->GetXULPrefSize(aState);
+ AddMargin(child, pref);
+
+ if (isEqual) {
+ if (isHorizontal)
+ {
+ if (pref.width > biggestPref)
+ biggestPref = pref.width;
+ } else {
+ if (pref.height > biggestPref)
+ biggestPref = pref.height;
+ }
+ }
+
+ AddLargestSize(vpref, pref, isHorizontal);
+ count++;
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ if (isEqual) {
+ if (isHorizontal)
+ vpref.width = biggestPref*count;
+ else
+ vpref.height = biggestPref*count;
+ }
+
+ // now add our border and padding
+ AddBorderAndPadding(aBox, vpref);
+
+ return vpref;
+}
+
+nsSize
+nsSprocketLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize minSize (0, 0);
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ nscoord biggestMin = 0;
+
+
+ // run through all the children and get their min, max, and preferred sizes
+ // return us the size of the box
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+ bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
+ int32_t count = 0;
+
+ while (child)
+ {
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ nsSize min = child->GetXULMinSize(aState);
+ nsSize pref(0,0);
+
+ // if the child is not flexible then
+ // its min size is its pref size.
+ if (child->GetXULFlex() == 0) {
+ pref = child->GetXULPrefSize(aState);
+ if (isHorizontal)
+ min.width = pref.width;
+ else
+ min.height = pref.height;
+ }
+
+ if (isEqual) {
+ if (isHorizontal)
+ {
+ if (min.width > biggestMin)
+ biggestMin = min.width;
+ } else {
+ if (min.height > biggestMin)
+ biggestMin = min.height;
+ }
+ }
+
+ AddMargin(child, min);
+ AddLargestSize(minSize, min, isHorizontal);
+ count++;
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+
+ if (isEqual) {
+ if (isHorizontal)
+ minSize.width = biggestMin*count;
+ else
+ minSize.height = biggestMin*count;
+ }
+
+ // now add our border and padding
+ AddBorderAndPadding(aBox, minSize);
+
+ return minSize;
+}
+
+nsSize
+nsSprocketLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ nscoord smallestMax = NS_INTRINSICSIZE;
+ nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+
+ // run through all the children and get their min, max, and preferred sizes
+ // return us the size of the box
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ nsFrameState frameState = nsFrameState(0);
+ GetFrameState(aBox, frameState);
+ bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
+ int32_t count = 0;
+
+ while (child)
+ {
+ // ignore collapsed children
+ if (!child->IsXULCollapsed())
+ {
+ // if completely redefined don't even ask our child for its size.
+ nsSize min = child->GetXULMinSize(aState);
+ nsSize max = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(aState));
+
+ AddMargin(child, max);
+ AddSmallestSize(maxSize, max, isHorizontal);
+
+ if (isEqual) {
+ if (isHorizontal)
+ {
+ if (max.width < smallestMax)
+ smallestMax = max.width;
+ } else {
+ if (max.height < smallestMax)
+ smallestMax = max.height;
+ }
+ }
+ count++;
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ if (isEqual) {
+ if (isHorizontal) {
+ if (smallestMax != NS_INTRINSICSIZE)
+ maxSize.width = smallestMax*count;
+ else
+ maxSize.width = NS_INTRINSICSIZE;
+ } else {
+ if (smallestMax != NS_INTRINSICSIZE)
+ maxSize.height = smallestMax*count;
+ else
+ maxSize.height = NS_INTRINSICSIZE;
+ }
+ }
+
+ // now add our border and padding
+ AddBorderAndPadding(aBox, maxSize);
+
+ return maxSize;
+}
+
+
+nscoord
+nsSprocketLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nscoord vAscent = 0;
+
+ bool isHorizontal = IsXULHorizontal(aBox);
+
+ // run through all the children and get their min, max, and preferred sizes
+ // return us the size of the box
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+
+ while (child)
+ {
+ // ignore collapsed children
+ //if (!child->IsXULCollapsed())
+ //{
+ // if completely redefined don't even ask our child for its size.
+ nscoord ascent = child->GetXULBoxAscent(aState);
+
+ nsMargin margin;
+ child->GetXULMargin(margin);
+ ascent += margin.top;
+
+ if (isHorizontal)
+ {
+ if (ascent > vAscent)
+ vAscent = ascent;
+ } else {
+ if (vAscent == 0)
+ vAscent = ascent;
+ }
+ //}
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ nsMargin borderPadding;
+ aBox->GetXULBorderAndPadding(borderPadding);
+
+ return vAscent + borderPadding.top;
+}
+
+void
+nsSprocketLayout::SetLargestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal)
+{
+ if (aIsHorizontal)
+ {
+ if (aSize1.height < aSize2.height)
+ aSize1.height = aSize2.height;
+ } else {
+ if (aSize1.width < aSize2.width)
+ aSize1.width = aSize2.width;
+ }
+}
+
+void
+nsSprocketLayout::SetSmallestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal)
+{
+ if (aIsHorizontal)
+ {
+ if (aSize1.height > aSize2.height)
+ aSize1.height = aSize2.height;
+ } else {
+ if (aSize1.width > aSize2.width)
+ aSize1.width = aSize2.width;
+
+ }
+}
+
+void
+nsSprocketLayout::AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal)
+{
+ if (aIsHorizontal)
+ AddCoord(aSize.width, aSizeToAdd.width);
+ else
+ AddCoord(aSize.height, aSizeToAdd.height);
+
+ SetLargestSize(aSize, aSizeToAdd, aIsHorizontal);
+}
+
+void
+nsSprocketLayout::AddCoord(nscoord& aCoord, nscoord aCoordToAdd)
+{
+ if (aCoord != NS_INTRINSICSIZE)
+ {
+ if (aCoordToAdd == NS_INTRINSICSIZE)
+ aCoord = aCoordToAdd;
+ else
+ aCoord += aCoordToAdd;
+ }
+}
+void
+nsSprocketLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal)
+{
+ if (aIsHorizontal)
+ AddCoord(aSize.width, aSizeToAdd.width);
+ else
+ AddCoord(aSize.height, aSizeToAdd.height);
+
+ SetSmallestSize(aSize, aSizeToAdd, aIsHorizontal);
+}
+
+bool
+nsSprocketLayout::GetDefaultFlex(int32_t& aFlex)
+{
+ aFlex = 0;
+ return true;
+}
+
+nsComputedBoxSize::nsComputedBoxSize()
+{
+ resized = false;
+ valid = false;
+ size = 0;
+ next = nullptr;
+}
+
+nsBoxSize::nsBoxSize()
+{
+ pref = 0;
+ min = 0;
+ max = NS_INTRINSICSIZE;
+ collapsed = false;
+ left = 0;
+ right = 0;
+ flex = 0;
+ next = nullptr;
+ bogus = false;
+}
+
+
+void*
+nsBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW
+{
+ return mozilla::AutoStackArena::Allocate(sz);
+}
+
+
+void
+nsBoxSize::operator delete(void* aPtr, size_t sz)
+{
+}
+
+
+void*
+nsComputedBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW
+{
+ return mozilla::AutoStackArena::Allocate(sz);
+}
+
+void
+nsComputedBoxSize::operator delete(void* aPtr, size_t sz)
+{
+}
diff --git a/layout/xul/nsSprocketLayout.h b/layout/xul/nsSprocketLayout.h
new file mode 100644
index 000000000..0f15dce44
--- /dev/null
+++ b/layout/xul/nsSprocketLayout.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSprocketLayout_h___
+#define nsSprocketLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxLayout.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+
+class nsBoxSize
+{
+public:
+
+ nsBoxSize();
+
+ nscoord pref;
+ nscoord min;
+ nscoord max;
+ nscoord flex;
+ nscoord left;
+ nscoord right;
+ bool collapsed;
+ bool bogus;
+
+ nsBoxSize* next;
+
+ void* operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW;
+ void operator delete(void* aPtr, size_t sz);
+};
+
+class nsComputedBoxSize
+{
+public:
+ nsComputedBoxSize();
+
+ nscoord size;
+ bool valid;
+ bool resized;
+ nsComputedBoxSize* next;
+
+ void* operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW;
+ void operator delete(void* aPtr, size_t sz);
+};
+
+#define GET_WIDTH(size, isHorizontal) (isHorizontal ? size.width : size.height)
+#define GET_HEIGHT(size, isHorizontal) (isHorizontal ? size.height : size.width)
+#define GET_X(size, isHorizontal) (isHorizontal ? size.x : size.y)
+#define GET_Y(size, isHorizontal) (isHorizontal ? size.y : size.x)
+#define GET_COORD(aX, aY, isHorizontal) (isHorizontal ? aX : aY)
+
+#define SET_WIDTH(size, coord, isHorizontal) if (isHorizontal) { (size).width = (coord); } else { (size).height = (coord); }
+#define SET_HEIGHT(size, coord, isHorizontal) if (isHorizontal) { (size).height = (coord); } else { (size).width = (coord); }
+#define SET_X(size, coord, isHorizontal) if (isHorizontal) { (size).x = (coord); } else { (size).y = (coord); }
+#define SET_Y(size, coord, isHorizontal) if (isHorizontal) { (size).y = (coord); } else { (size).x = (coord); }
+
+#define SET_COORD(aX, aY, coord, isHorizontal) if (isHorizontal) { aX = (coord); } else { aY = (coord); }
+
+nsresult NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout);
+
+class nsSprocketLayout : public nsBoxLayout {
+
+public:
+
+ friend nsresult NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout);
+ static void Shutdown();
+
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+
+ nsSprocketLayout();
+
+ static bool IsXULHorizontal(nsIFrame* aBox);
+
+ static void SetLargestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal);
+ static void SetSmallestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal);
+
+ static void AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal);
+ static void AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal);
+ static void AddCoord(nscoord& aCoord, nscoord aCoordToAdd);
+
+protected:
+
+
+ void ComputeChildsNextPosition(nsIFrame* aBox,
+ const nscoord& aCurX,
+ const nscoord& aCurY,
+ nscoord& aNextX,
+ nscoord& aNextY,
+ const nsRect& aChildSize);
+
+ void ChildResized(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nsIFrame* aChild,
+ nsBoxSize* aChildBoxSize,
+ nsComputedBoxSize* aChildComputedBoxSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize* aComputedBoxSizes,
+ const nsRect& aChildLayoutRect,
+ nsRect& aChildActualRect,
+ nsRect& aContainingRect,
+ int32_t aFlexes,
+ bool& aFinished);
+
+ void AlignChildren(nsIFrame* aBox,
+ nsBoxLayoutState& aState);
+
+ virtual void ComputeChildSizes(nsIFrame* aBox,
+ nsBoxLayoutState& aState,
+ nscoord& aGivenSize,
+ nsBoxSize* aBoxSizes,
+ nsComputedBoxSize*& aComputedBoxSizes);
+
+
+ virtual void PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState,
+ nsBoxSize*& aBoxSizes, nscoord& aMinSize,
+ nscoord& aMaxSize, int32_t& aFlexes);
+
+ virtual void InvalidateComputedSizes(nsComputedBoxSize* aComputedBoxSizes);
+
+ virtual bool GetDefaultFlex(int32_t& aFlex);
+
+ virtual void GetFrameState(nsIFrame* aBox, nsFrameState& aState);
+
+private:
+
+
+ // because the sprocket layout manager has no instance variables. We
+ // can make a static one and reuse it everywhere.
+ static nsBoxLayout* gInstance;
+
+};
+
+#endif
+
diff --git a/layout/xul/nsStackFrame.cpp b/layout/xul/nsStackFrame.cpp
new file mode 100644
index 000000000..437d558f9
--- /dev/null
+++ b/layout/xul/nsStackFrame.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsStackFrame.h"
+#include "nsStyleContext.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsHTMLParts.h"
+#include "nsIPresShell.h"
+#include "nsCSSRendering.h"
+#include "nsBoxLayoutState.h"
+#include "nsStackLayout.h"
+#include "nsDisplayList.h"
+
+nsIFrame*
+NS_NewStackFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsStackFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsStackFrame)
+
+nsStackFrame::nsStackFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout;
+ NS_NewStackLayout(layout);
+ SetXULLayoutManager(layout);
+}
+
+// REVIEW: The old code put everything in the background layer. To be more
+// consistent with the way other frames work, I'm putting everything in the
+// Content() (i.e., foreground) layer (see nsFrame::BuildDisplayListForChild,
+// the case for stacking context but non-positioned, non-floating frames).
+// This could easily be changed back by hacking nsBoxFrame::BuildDisplayListInternal
+// a bit more.
+void
+nsStackFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // BuildDisplayListForChild puts stacking contexts into the PositionedDescendants
+ // list. So we need to map that list to aLists.Content(). This is an easy way to
+ // do that.
+ nsDisplayList* content = aLists.Content();
+ nsDisplayListSet kidLists(content, content, content, content, content, content);
+ nsIFrame* kid = mFrames.FirstChild();
+ while (kid) {
+ // Force each child into its own true stacking context.
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, kidLists,
+ DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
+ kid = kid->GetNextSibling();
+ }
+}
diff --git a/layout/xul/nsStackFrame.h b/layout/xul/nsStackFrame.h
new file mode 100644
index 000000000..b90a16b21
--- /dev/null
+++ b/layout/xul/nsStackFrame.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ A frame that can have multiple children. Only one child may be displayed at one time. So the
+ can be flipped though like a Stack of cards.
+
+**/
+
+#ifndef nsStackFrame_h___
+#define nsStackFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsStackFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewStackFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("Stack"), aResult);
+ }
+#endif
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+protected:
+ explicit nsStackFrame(nsStyleContext* aContext);
+}; // class nsStackFrame
+
+
+
+#endif
+
diff --git a/layout/xul/nsStackLayout.cpp b/layout/xul/nsStackLayout.cpp
new file mode 100644
index 000000000..6072e0612
--- /dev/null
+++ b/layout/xul/nsStackLayout.cpp
@@ -0,0 +1,385 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+#include "nsStackLayout.h"
+#include "nsCOMPtr.h"
+#include "nsBoxLayoutState.h"
+#include "nsBox.h"
+#include "nsBoxFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla;
+
+nsBoxLayout* nsStackLayout::gInstance = nullptr;
+
+#define SPECIFIED_LEFT (1 << NS_SIDE_LEFT)
+#define SPECIFIED_RIGHT (1 << NS_SIDE_RIGHT)
+#define SPECIFIED_TOP (1 << NS_SIDE_TOP)
+#define SPECIFIED_BOTTOM (1 << NS_SIDE_BOTTOM)
+
+nsresult
+NS_NewStackLayout(nsCOMPtr<nsBoxLayout>& aNewLayout)
+{
+ if (!nsStackLayout::gInstance) {
+ nsStackLayout::gInstance = new nsStackLayout();
+ NS_IF_ADDREF(nsStackLayout::gInstance);
+ }
+ // we have not instance variables so just return our static one.
+ aNewLayout = nsStackLayout::gInstance;
+ return NS_OK;
+}
+
+/*static*/ void
+nsStackLayout::Shutdown()
+{
+ NS_IF_RELEASE(gInstance);
+}
+
+nsStackLayout::nsStackLayout()
+{
+}
+
+/*
+ * Sizing: we are as wide as the widest child plus its left offset
+ * we are tall as the tallest child plus its top offset.
+ *
+ * Only children which have -moz-stack-sizing set to stretch-to-fit
+ * (the default) will be included in the size computations.
+ */
+
+nsSize
+nsStackLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize prefSize (0, 0);
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while (child) {
+ if (child->StyleXUL()->mStretchStack) {
+ nsSize pref = child->GetXULPrefSize(aState);
+
+ AddMargin(child, pref);
+ nsMargin offset;
+ GetOffset(child, offset);
+ pref.width += offset.LeftRight();
+ pref.height += offset.TopBottom();
+ AddLargestSize(prefSize, pref);
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ AddBorderAndPadding(aBox, prefSize);
+
+ return prefSize;
+}
+
+nsSize
+nsStackLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize minSize (0, 0);
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while (child) {
+ if (child->StyleXUL()->mStretchStack) {
+ nsSize min = child->GetXULMinSize(aState);
+
+ AddMargin(child, min);
+ nsMargin offset;
+ GetOffset(child, offset);
+ min.width += offset.LeftRight();
+ min.height += offset.TopBottom();
+ AddLargestSize(minSize, min);
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ AddBorderAndPadding(aBox, minSize);
+
+ return minSize;
+}
+
+nsSize
+nsStackLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while (child) {
+ if (child->StyleXUL()->mStretchStack) {
+ nsSize min = child->GetXULMinSize(aState);
+ nsSize max = child->GetXULMaxSize(aState);
+
+ max = nsBox::BoundsCheckMinMax(min, max);
+
+ AddMargin(child, max);
+ nsMargin offset;
+ GetOffset(child, offset);
+ max.width += offset.LeftRight();
+ max.height += offset.TopBottom();
+ AddSmallestSize(maxSize, max);
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ AddBorderAndPadding(aBox, maxSize);
+
+ return maxSize;
+}
+
+
+nscoord
+nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nscoord vAscent = 0;
+
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ while (child) {
+ nscoord ascent = child->GetXULBoxAscent(aState);
+ nsMargin margin;
+ child->GetXULMargin(margin);
+ ascent += margin.top;
+ if (ascent > vAscent)
+ vAscent = ascent;
+
+ child = nsBox::GetNextXULBox(child);
+ }
+
+ return vAscent;
+}
+
+uint8_t
+nsStackLayout::GetOffset(nsIFrame* aChild, nsMargin& aOffset)
+{
+ aOffset = nsMargin(0, 0, 0, 0);
+
+ // get the left, right, top and bottom offsets
+
+ // As an optimization, we cache the fact that we are not positioned to avoid
+ // wasting time fetching attributes.
+ if (aChild->IsXULBoxFrame() &&
+ (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED))
+ return 0;
+
+ uint8_t offsetSpecified = 0;
+ nsIContent* content = aChild->GetContent();
+ if (content) {
+ bool ltr = aChild->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR;
+ nsAutoString value;
+ nsresult error;
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ if (ltr) {
+ aOffset.left =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_LEFT;
+ } else {
+ aOffset.right =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_RIGHT;
+ }
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ if (ltr) {
+ aOffset.right =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_RIGHT;
+ } else {
+ aOffset.left =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_LEFT;
+ }
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ aOffset.left =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_LEFT;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ aOffset.right =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_RIGHT;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ aOffset.top =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_TOP;
+ }
+
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value);
+ if (!value.IsEmpty()) {
+ value.Trim("%");
+ aOffset.bottom =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ offsetSpecified |= SPECIFIED_BOTTOM;
+ }
+ }
+
+ if (!offsetSpecified && aChild->IsXULBoxFrame()) {
+ // If no offset was specified at all, then we cache this fact to avoid requerying
+ // CSS or the content model.
+ aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED);
+ }
+
+ return offsetSpecified;
+}
+
+
+NS_IMETHODIMP
+nsStackLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState)
+{
+ nsRect clientRect;
+ aBox->GetXULClientRect(clientRect);
+
+ bool grow;
+
+ do {
+ nsIFrame* child = nsBox::GetChildXULBox(aBox);
+ grow = false;
+
+ while (child)
+ {
+ nsMargin margin;
+ child->GetXULMargin(margin);
+ nsRect childRect(clientRect);
+ childRect.Deflate(margin);
+
+ if (childRect.width < 0)
+ childRect.width = 0;
+
+ if (childRect.height < 0)
+ childRect.height = 0;
+
+ nsRect oldRect(child->GetRect());
+ bool sizeChanged = !oldRect.IsEqualEdges(childRect);
+
+ // only lay out dirty children or children whose sizes have changed
+ if (sizeChanged || NS_SUBTREE_DIRTY(child)) {
+ // add in the child's margin
+ nsMargin margin;
+ child->GetXULMargin(margin);
+
+ // obtain our offset from the top left border of the stack's content box.
+ nsMargin offset;
+ uint8_t offsetSpecified = GetOffset(child, offset);
+
+ // Set the position and size based on which offsets have been specified:
+ // left only - offset from left edge, preferred width
+ // right only - offset from right edge, preferred width
+ // left and right - offset from left and right edges, width in between this
+ // neither - no offset, full width of stack
+ // Vertical direction is similar.
+ //
+ // Margins on the child are also included in the edge offsets
+ if (offsetSpecified) {
+ nsSize min = child->GetXULMinSize(aState);
+ nsSize max = child->GetXULMaxSize(aState);
+ if (offsetSpecified & SPECIFIED_LEFT) {
+ childRect.x = clientRect.x + offset.left + margin.left;
+ if (offsetSpecified & SPECIFIED_RIGHT) {
+ nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight();
+ childRect.width = clamped(width, min.width, max.width);
+ }
+ else {
+ nscoord width = child->GetXULPrefSize(aState).width;
+ childRect.width = clamped(width, min.width, max.width);
+ }
+ }
+ else if (offsetSpecified & SPECIFIED_RIGHT) {
+ nscoord width = child->GetXULPrefSize(aState).width;
+ childRect.width = clamped(width, min.width, max.width);
+ childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width;
+ }
+
+ if (offsetSpecified & SPECIFIED_TOP) {
+ childRect.y = clientRect.y + offset.top + margin.top;
+ if (offsetSpecified & SPECIFIED_BOTTOM) {
+ nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom();
+ childRect.height = clamped(height, min.height, max.height);
+ }
+ else {
+ nscoord height = child->GetXULPrefSize(aState).height;
+ childRect.height = clamped(height, min.height, max.height);
+ }
+ }
+ else if (offsetSpecified & SPECIFIED_BOTTOM) {
+ nscoord height = child->GetXULPrefSize(aState).height;
+ childRect.height = clamped(height, min.height, max.height);
+ childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height;
+ }
+ }
+
+ // Now place the child.
+ child->SetXULBounds(aState, childRect);
+
+ // Flow the child.
+ child->XULLayout(aState);
+
+ // Get the child's new rect.
+ childRect = child->GetRect();
+ childRect.Inflate(margin);
+
+ if (child->StyleXUL()->mStretchStack) {
+ // Did the child push back on us and get bigger?
+ if (offset.LeftRight() + childRect.width > clientRect.width) {
+ clientRect.width = childRect.width + offset.LeftRight();
+ grow = true;
+ }
+
+ if (offset.TopBottom() + childRect.height > clientRect.height) {
+ clientRect.height = childRect.height + offset.TopBottom();
+ grow = true;
+ }
+ }
+ }
+
+ child = nsBox::GetNextXULBox(child);
+ }
+ } while (grow);
+
+ // if some HTML inside us got bigger we need to force ourselves to
+ // get bigger
+ nsRect bounds(aBox->GetRect());
+ nsMargin bp;
+ aBox->GetXULBorderAndPadding(bp);
+ clientRect.Inflate(bp);
+
+ if (clientRect.width > bounds.width || clientRect.height > bounds.height)
+ {
+ if (clientRect.width > bounds.width)
+ bounds.width = clientRect.width;
+ if (clientRect.height > bounds.height)
+ bounds.height = clientRect.height;
+
+ aBox->SetXULBounds(aState, bounds);
+ }
+
+ return NS_OK;
+}
+
diff --git a/layout/xul/nsStackLayout.h b/layout/xul/nsStackLayout.h
new file mode 100644
index 000000000..1eb1a6318
--- /dev/null
+++ b/layout/xul/nsStackLayout.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+
+ Eric D Vaughan
+ A frame that can have multiple children. Only one child may be displayed at one time. So the
+ can be flipped though like a deck of cards.
+
+**/
+
+#ifndef nsStackLayout_h___
+#define nsStackLayout_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBoxLayout.h"
+#include "nsCOMPtr.h"
+#include "nsCoord.h"
+
+class nsIPresShell;
+
+nsresult NS_NewStackLayout(nsCOMPtr<nsBoxLayout>& aNewLayout);
+
+class nsStackLayout : public nsBoxLayout
+{
+public:
+
+ friend nsresult NS_NewStackLayout(nsCOMPtr<nsBoxLayout>& aNewLayout);
+ static void Shutdown();
+
+ nsStackLayout();
+
+ NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override;
+
+ virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override;
+
+ // get the child offsets for aChild and set them in aMargin. Returns a
+ // bitfield mask of the SPECIFIED_LEFT, SPECIFIED_RIGHT, SPECIFIED_TOP and
+ // SPECIFIED_BOTTOM offsets indicating which sides have been specified by
+ // attributes.
+ static uint8_t GetOffset(nsIFrame* aChild, nsMargin& aMargin);
+
+private:
+ static nsBoxLayout* gInstance;
+
+}; // class nsStackLayout
+
+
+
+#endif
+
diff --git a/layout/xul/nsTextBoxFrame.cpp b/layout/xul/nsTextBoxFrame.cpp
new file mode 100644
index 000000000..c82d3d6b9
--- /dev/null
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -0,0 +1,1241 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextBoxFrame.h"
+
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsFontMetrics.h"
+#include "nsReadableUtils.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsStyleContext.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsMenuBarListener.h"
+#include "nsXPIDLString.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULLabelElement.h"
+#include "mozilla/EventStateManager.h"
+#include "nsITheme.h"
+#include "nsUnicharUtils.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsCSSRendering.h"
+#include "nsIReflowCallback.h"
+#include "nsBoxFrame.h"
+#include "mozilla/Preferences.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/Attributes.h"
+#include "nsUnicodeProperties.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+#include "nsBidiUtils.h"
+#include "nsBidiPresUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+class nsAccessKeyInfo
+{
+public:
+ int32_t mAccesskeyIndex;
+ nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset;
+};
+
+
+bool nsTextBoxFrame::gAlwaysAppendAccessKey = false;
+bool nsTextBoxFrame::gAccessKeyPrefInitialized = false;
+bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false;
+bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false;
+
+nsIFrame*
+NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTextBoxFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
+
+NS_QUERYFRAME_HEAD(nsTextBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
+
+nsresult
+nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ bool aResize;
+ bool aRedraw;
+
+ UpdateAttributes(aAttribute, aResize, aRedraw);
+
+ if (aResize) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ } else if (aRedraw) {
+ nsBoxLayoutState state(PresContext());
+ XULRedraw(state);
+ }
+
+ // If the accesskey changed, register for the new value
+ // The old value has been unregistered in nsXULElement::SetAttr
+ if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control)
+ RegUnregAccessKey(true);
+
+ return NS_OK;
+}
+
+nsTextBoxFrame::nsTextBoxFrame(nsStyleContext* aContext):
+ nsLeafBoxFrame(aContext), mAccessKeyInfo(nullptr), mCropType(CropRight),
+ mNeedsReflowCallback(false)
+{
+ MarkIntrinsicISizesDirty();
+}
+
+nsTextBoxFrame::~nsTextBoxFrame()
+{
+ delete mAccessKeyInfo;
+}
+
+
+void
+nsTextBoxFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ bool aResize;
+ bool aRedraw;
+ UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
+
+ // register access key
+ RegUnregAccessKey(true);
+}
+
+void
+nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // unregister access key
+ RegUnregAccessKey(false);
+ nsLeafBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+bool
+nsTextBoxFrame::AlwaysAppendAccessKey()
+{
+ if (!gAccessKeyPrefInitialized)
+ {
+ gAccessKeyPrefInitialized = true;
+
+ const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
+ nsAdoptingString val = Preferences::GetLocalizedString(prefName);
+ gAlwaysAppendAccessKey = val.EqualsLiteral("true");
+ }
+ return gAlwaysAppendAccessKey;
+}
+
+bool
+nsTextBoxFrame::InsertSeparatorBeforeAccessKey()
+{
+ if (!gInsertSeparatorPrefInitialized)
+ {
+ gInsertSeparatorPrefInitialized = true;
+
+ const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
+ nsAdoptingString val = Preferences::GetLocalizedString(prefName);
+ gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
+ }
+ return gInsertSeparatorBeforeAccessKey;
+}
+
+class nsAsyncAccesskeyUpdate final : public nsIReflowCallback
+{
+public:
+ explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame)
+ {
+ }
+
+ virtual bool ReflowFinished() override
+ {
+ bool shouldFlush = false;
+ nsTextBoxFrame* frame =
+ static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame());
+ if (frame) {
+ shouldFlush = frame->UpdateAccesskey(mWeakFrame);
+ }
+ delete this;
+ return shouldFlush;
+ }
+
+ virtual void ReflowCallbackCanceled() override
+ {
+ delete this;
+ }
+
+ nsWeakFrame mWeakFrame;
+};
+
+bool
+nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis)
+{
+ nsAutoString accesskey;
+ nsCOMPtr<nsIDOMXULLabelElement> labelElement = do_QueryInterface(mContent);
+ NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
+ if (labelElement) {
+ // Accesskey may be stored on control.
+ labelElement->GetAccessKey(accesskey);
+ NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
+ }
+ else {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey);
+ }
+
+ if (!accesskey.Equals(mAccessKey)) {
+ // Need to get clean mTitle.
+ RecomputeTitle();
+ mAccessKey = accesskey;
+ UpdateAccessTitle();
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ return true;
+ }
+ return false;
+}
+
+void
+nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute,
+ bool& aResize,
+ bool& aRedraw)
+{
+ bool doUpdateTitle = false;
+ aResize = false;
+ aRedraw = false;
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center,
+ &nsGkAtoms::right, &nsGkAtoms::end, &nsGkAtoms::none, nullptr};
+ CroppingStyle cropType;
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
+ strings, eCaseMatters)) {
+ case 0:
+ case 1:
+ cropType = CropLeft;
+ break;
+ case 2:
+ cropType = CropCenter;
+ break;
+ case 3:
+ case 4:
+ cropType = CropRight;
+ break;
+ case 5:
+ cropType = CropNone;
+ break;
+ default:
+ cropType = CropAuto;
+ break;
+ }
+
+ if (cropType != mCropType) {
+ aResize = true;
+ mCropType = cropType;
+ }
+ }
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
+ RecomputeTitle();
+ doUpdateTitle = true;
+ }
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
+ mNeedsReflowCallback = true;
+ // Ensure that layout is refreshed and reflow callback called.
+ aResize = true;
+ }
+
+ if (doUpdateTitle) {
+ UpdateAccessTitle();
+ aResize = true;
+ }
+
+}
+
+class nsDisplayXULTextBox : public nsDisplayItem {
+public:
+ nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder,
+ nsTextBoxFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame),
+ mDisableSubpixelAA(false)
+ {
+ MOZ_COUNT_CTOR(nsDisplayXULTextBox);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULTextBox() {
+ MOZ_COUNT_DTOR(nsDisplayXULTextBox);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override;
+
+ virtual void DisableComponentAlpha() override {
+ mDisableSubpixelAA = true;
+ }
+
+ void PaintTextToContext(nsRenderingContext* aCtx,
+ nsPoint aOffset,
+ const nscolor* aColor);
+
+ bool mDisableSubpixelAA;
+};
+
+static void
+PaintTextShadowCallback(nsRenderingContext* aCtx,
+ nsPoint aShadowOffset,
+ const nscolor& aShadowColor,
+ void* aData)
+{
+ reinterpret_cast<nsDisplayXULTextBox*>(aData)->
+ PaintTextToContext(aCtx, aShadowOffset, &aShadowColor);
+}
+
+void
+nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
+ mDisableSubpixelAA);
+
+ // Paint the text shadow before doing any foreground stuff
+ nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect +
+ ToReferenceFrame();
+ nsLayoutUtils::PaintTextShadow(mFrame, aCtx,
+ drawRect, mVisibleRect,
+ mFrame->StyleColor()->mColor,
+ PaintTextShadowCallback,
+ (void*)this);
+
+ PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
+}
+
+void
+nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx,
+ nsPoint aOffset,
+ const nscolor* aColor)
+{
+ static_cast<nsTextBoxFrame*>(mFrame)->
+ PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor);
+}
+
+nsRect
+nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+nsRect
+nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
+{
+ return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
+ ToReferenceFrame();
+}
+
+void
+nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!IsVisibleForPainting(aBuilder))
+ return;
+
+ nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayXULTextBox(aBuilder, this));
+}
+
+void
+nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ const nscolor* aOverrideColor)
+{
+ if (mTitle.IsEmpty())
+ return;
+
+ DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
+}
+
+void
+nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aTextRect,
+ const nscolor* aOverrideColor)
+{
+ nsPresContext* presContext = PresContext();
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // paint the title
+ nscolor overColor = 0;
+ nscolor underColor = 0;
+ nscolor strikeColor = 0;
+ uint8_t overStyle = 0;
+ uint8_t underStyle = 0;
+ uint8_t strikeStyle = 0;
+
+ // Begin with no decorations
+ uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
+ // A mask of all possible decorations.
+ uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
+
+ WritingMode wm = GetWritingMode();
+ bool vertical = wm.IsVertical();
+
+ nsIFrame* f = this;
+ do { // find decoration colors
+ nsStyleContext* context = f->StyleContext();
+ if (!context->HasTextDecorationLines()) {
+ break;
+ }
+ const nsStyleTextReset* styleText = context->StyleTextReset();
+
+ if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here
+ nscolor color;
+ if (aOverrideColor) {
+ color = *aOverrideColor;
+ } else {
+ color = context->StyleColor()->
+ CalcComplexColor(styleText->mTextDecorationColor);
+ }
+ uint8_t style = styleText->mTextDecorationStyle;
+
+ if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask &
+ styleText->mTextDecorationLine) {
+ underColor = color;
+ underStyle = style;
+ decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ }
+ if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask &
+ styleText->mTextDecorationLine) {
+ overColor = color;
+ overStyle = style;
+ decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
+ decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
+ }
+ if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask &
+ styleText->mTextDecorationLine) {
+ strikeColor = color;
+ strikeStyle = style;
+ decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
+ decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
+ }
+ }
+ } while (0 != decorMask &&
+ (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
+
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ fontMet->SetVertical(wm.IsVertical());
+ fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation);
+
+ nscoord offset;
+ nscoord size;
+ nscoord ascent = fontMet->MaxAscent();
+
+ nsPoint baselinePt;
+ if (wm.IsVertical()) {
+ baselinePt.x =
+ presContext->RoundAppUnitsToNearestDevPixels(aTextRect.x +
+ (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent));
+ baselinePt.y = aTextRect.y;
+ } else {
+ baselinePt.x = aTextRect.x;
+ baselinePt.y =
+ presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
+ }
+
+ nsCSSRendering::PaintDecorationLineParams params;
+ params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
+ params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x),
+ presContext->AppUnitsToGfxUnits(aTextRect.y));
+ params.icoordInFrame =
+ Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x));
+ params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0);
+ params.ascent = presContext->AppUnitsToGfxUnits(ascent);
+ params.vertical = vertical;
+
+ // XXX todo: vertical-mode support for decorations not tested yet,
+ // probably won't be positioned correctly
+
+ // Underlines are drawn before overlines, and both before the text
+ // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
+ // (We don't apply this rule to the access-key underline because we only
+ // find out where that is as a side effect of drawing the text, in the
+ // general case -- see below.)
+ if (decorations & (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) {
+ fontMet->GetUnderline(offset, size);
+ params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
+ if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) &&
+ underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
+ params.color = underColor;
+ params.offset = presContext->AppUnitsToGfxUnits(offset);
+ params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ params.style = underStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+ if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) &&
+ overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
+ params.color = overColor;
+ params.offset = params.ascent;
+ params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
+ params.style = overStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+ }
+
+ nsRenderingContext refContext(
+ PresContext()->PresShell()->CreateReferenceRenderingContext());
+ DrawTarget* refDrawTarget = refContext.GetDrawTarget();
+
+ CalculateUnderline(refDrawTarget, *fontMet);
+
+ nscolor c = aOverrideColor ? *aOverrideColor : StyleColor()->mColor;
+ ColorPattern color(ToDeviceColor(c));
+ aRenderingContext.ThebesContext()->SetColor(Color::FromABGR(c));
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (mState & NS_FRAME_IS_BIDI) {
+ presContext->SetBidiEnabled();
+ nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext());
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // We let the RenderText function calculate the mnemonic's
+ // underline position for us.
+ nsBidiPositionResolve posResolve;
+ posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
+ rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
+ presContext, aRenderingContext,
+ refDrawTarget, *fontMet,
+ baselinePt.x, baselinePt.y,
+ &posResolve,
+ 1);
+ mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
+ mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
+ }
+ else
+ {
+ rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
+ presContext, aRenderingContext,
+ refDrawTarget, *fontMet,
+ baselinePt.x, baselinePt.y);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ fontMet->SetTextRunRTL(false);
+
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // In the simple (non-BiDi) case, we calculate the mnemonic's
+ // underline position by getting the text metric.
+ // XXX are attribute values always two byte?
+ if (mAccessKeyInfo->mAccesskeyIndex > 0)
+ mAccessKeyInfo->mBeforeWidth = nsLayoutUtils::
+ AppUnitWidthOfString(mCroppedTitle.get(),
+ mAccessKeyInfo->mAccesskeyIndex,
+ *fontMet, refDrawTarget);
+ else
+ mAccessKeyInfo->mBeforeWidth = 0;
+ }
+
+ fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
+ baselinePt.x, baselinePt.y, &aRenderingContext,
+ refDrawTarget);
+ }
+
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
+ aTextRect.y + mAccessKeyInfo->mAccessOffset,
+ mAccessKeyInfo->mAccessWidth,
+ mAccessKeyInfo->mAccessUnderlineSize);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+
+ // Strikeout is drawn on top of the text, per
+ // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
+ if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) &&
+ strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
+ fontMet->GetStrikeout(offset, size);
+ params.color = strikeColor;
+ params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
+ params.offset = presContext->AppUnitsToGfxUnits(offset);
+ params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
+ params.style = strikeStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+}
+
+void
+nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics)
+{
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // Calculate all fields of mAccessKeyInfo which
+ // are the same for both BiDi and non-BiDi frames.
+ const char16_t *titleString = mCroppedTitle.get();
+ aFontMetrics.SetTextRunRTL(false);
+ mAccessKeyInfo->mAccessWidth = nsLayoutUtils::
+ AppUnitWidthOfString(titleString[mAccessKeyInfo->mAccesskeyIndex],
+ aFontMetrics, aDrawTarget);
+
+ nscoord offset, baseline;
+ aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
+ baseline = aFontMetrics.MaxAscent();
+ mAccessKeyInfo->mAccessOffset = baseline - offset;
+ }
+}
+
+nscoord
+nsTextBoxFrame::CalculateTitleForWidth(nsRenderingContext& aRenderingContext,
+ nscoord aWidth)
+{
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ if (mTitle.IsEmpty()) {
+ mCroppedTitle.Truncate();
+ return 0;
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ // see if the text will completely fit in the width given
+ nscoord titleWidth =
+ nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
+ aRenderingContext);
+ if (titleWidth <= aWidth) {
+ mCroppedTitle = mTitle;
+ if (HasRTLChars(mTitle) ||
+ StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+ mState |= NS_FRAME_IS_BIDI;
+ }
+ return titleWidth; // fits, done.
+ }
+
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ if (mCropType != CropNone) {
+ // start with an ellipsis
+ mCroppedTitle.Assign(kEllipsis);
+
+ // see if the width is even smaller than the ellipsis
+ // if so, clear the text (XXX set as many '.' as we can?).
+ fm->SetTextRunRTL(false);
+ titleWidth = nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm,
+ drawTarget);
+
+ if (titleWidth > aWidth) {
+ mCroppedTitle.SetLength(0);
+ return 0;
+ }
+
+ // if the ellipsis fits perfectly, no use in trying to insert
+ if (titleWidth == aWidth)
+ return titleWidth;
+
+ aWidth -= titleWidth;
+ } else {
+ mCroppedTitle.Truncate(0);
+ titleWidth = 0;
+ }
+
+ using mozilla::unicode::ClusterIterator;
+ using mozilla::unicode::ClusterReverseIterator;
+
+ // ok crop things
+ switch (mCropType)
+ {
+ case CropAuto:
+ case CropNone:
+ case CropRight:
+ {
+ ClusterIterator iter(mTitle.Data(), mTitle.Length());
+ const char16_t* dataBegin = iter;
+ const char16_t* pos = dataBegin;
+ nscoord charWidth;
+ nscoord totalWidth = 0;
+
+ while (!iter.AtEnd()) {
+ iter.Next();
+ const char16_t* nextPos = iter;
+ ptrdiff_t length = nextPos - pos;
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length,
+ *fm,
+ drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ if (UCS2_CHAR_IS_BIDI(*pos)) {
+ mState |= NS_FRAME_IS_BIDI;
+ }
+ pos = nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (pos == dataBegin) {
+ return titleWidth;
+ }
+
+ // insert what character we can in.
+ nsAutoString title(mTitle);
+ title.Truncate(pos - dataBegin);
+ mCroppedTitle.Insert(title, 0);
+ }
+ break;
+
+ case CropLeft:
+ {
+ ClusterReverseIterator iter(mTitle.Data(), mTitle.Length());
+ const char16_t* dataEnd = iter;
+ const char16_t* prevPos = dataEnd;
+ nscoord charWidth;
+ nscoord totalWidth = 0;
+
+ while (!iter.AtEnd()) {
+ iter.Next();
+ const char16_t* pos = iter;
+ ptrdiff_t length = prevPos - pos;
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length,
+ *fm,
+ drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ if (UCS2_CHAR_IS_BIDI(*pos)) {
+ mState |= NS_FRAME_IS_BIDI;
+ }
+ prevPos = pos;
+ totalWidth += charWidth;
+ }
+
+ if (prevPos == dataEnd) {
+ return titleWidth;
+ }
+
+ nsAutoString copy;
+ mTitle.Right(copy, dataEnd - prevPos);
+ mCroppedTitle += copy;
+ }
+ break;
+
+ case CropCenter:
+ {
+ nscoord stringWidth =
+ nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
+ aRenderingContext);
+ if (stringWidth <= aWidth) {
+ // the entire string will fit in the maximum width
+ mCroppedTitle.Insert(mTitle, 0);
+ break;
+ }
+
+ // determine how much of the string will fit in the max width
+ nscoord charWidth = 0;
+ nscoord totalWidth = 0;
+ ClusterIterator leftIter(mTitle.Data(), mTitle.Length());
+ ClusterReverseIterator rightIter(mTitle.Data(), mTitle.Length());
+ const char16_t* dataBegin = leftIter;
+ const char16_t* dataEnd = rightIter;
+ const char16_t* leftPos = dataBegin;
+ const char16_t* rightPos = dataEnd;
+ const char16_t* pos;
+ ptrdiff_t length;
+ nsAutoString leftString, rightString;
+
+ while (leftPos < rightPos) {
+ leftIter.Next();
+ pos = leftIter;
+ length = pos - leftPos;
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length,
+ *fm,
+ drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ if (UCS2_CHAR_IS_BIDI(*leftPos)) {
+ mState |= NS_FRAME_IS_BIDI;
+ }
+
+ leftString.Append(leftPos, length);
+ leftPos = pos;
+ totalWidth += charWidth;
+
+ if (leftPos >= rightPos) {
+ break;
+ }
+
+ rightIter.Next();
+ pos = rightIter;
+ length = rightPos - pos;
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length,
+ *fm,
+ drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ if (UCS2_CHAR_IS_BIDI(*pos)) {
+ mState |= NS_FRAME_IS_BIDI;
+ }
+
+ rightString.Insert(pos, 0, length);
+ rightPos = pos;
+ totalWidth += charWidth;
+ }
+
+ mCroppedTitle = leftString + kEllipsis + rightString;
+ }
+ break;
+ }
+
+ return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
+ aRenderingContext);
+}
+
+#define OLD_ELLIPSIS NS_LITERAL_STRING("...")
+
+// the following block is to append the accesskey to mTitle if there is an accesskey
+// but the mTitle doesn't have the character
+void
+nsTextBoxFrame::UpdateAccessTitle()
+{
+ /*
+ * Note that if you change appending access key label spec,
+ * you need to maintain same logic in following methods. See bug 324159.
+ * toolkit/content/commonDialog.js (setLabelForNode)
+ * toolkit/content/widgets/text.xml (formatAccessKey)
+ */
+ int32_t menuAccessKey;
+ nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
+ if (!menuAccessKey || mAccessKey.IsEmpty())
+ return;
+
+ if (!AlwaysAppendAccessKey() &&
+ FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator()))
+ return;
+
+ nsAutoString accessKeyLabel;
+ accessKeyLabel += '(';
+ accessKeyLabel += mAccessKey;
+ ToUpperCase(accessKeyLabel);
+ accessKeyLabel += ')';
+
+ if (mTitle.IsEmpty()) {
+ mTitle = accessKeyLabel;
+ return;
+ }
+
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ uint32_t offset = mTitle.Length();
+ if (StringEndsWith(mTitle, kEllipsis)) {
+ offset -= kEllipsis.Length();
+ } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
+ // Try to check with our old ellipsis (for old addons)
+ offset -= OLD_ELLIPSIS.Length();
+ } else {
+ // Try to check with
+ // our default ellipsis (for non-localized addons) or ':'
+ const char16_t kLastChar = mTitle.Last();
+ if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':'))
+ offset--;
+ }
+
+ if (InsertSeparatorBeforeAccessKey() &&
+ offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) {
+ mTitle.Insert(' ', offset);
+ offset++;
+ }
+
+ mTitle.Insert(accessKeyLabel, offset);
+}
+
+void
+nsTextBoxFrame::UpdateAccessIndex()
+{
+ int32_t menuAccessKey;
+ nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
+ if (menuAccessKey) {
+ if (mAccessKey.IsEmpty()) {
+ if (mAccessKeyInfo) {
+ delete mAccessKeyInfo;
+ mAccessKeyInfo = nullptr;
+ }
+ } else {
+ if (!mAccessKeyInfo) {
+ mAccessKeyInfo = new nsAccessKeyInfo();
+ if (!mAccessKeyInfo)
+ return;
+ }
+
+ nsAString::const_iterator start, end;
+
+ mCroppedTitle.BeginReading(start);
+ mCroppedTitle.EndReading(end);
+
+ // remember the beginning of the string
+ nsAString::const_iterator originalStart = start;
+
+ bool found;
+ if (!AlwaysAppendAccessKey()) {
+ // not appending access key - do case-sensitive search
+ // first
+ found = FindInReadable(mAccessKey, start, end);
+ if (!found) {
+ // didn't find it - perform a case-insensitive search
+ start = originalStart;
+ found = FindInReadable(mAccessKey, start, end,
+ nsCaseInsensitiveStringComparator());
+ }
+ } else {
+ found = RFindInReadable(mAccessKey, start, end,
+ nsCaseInsensitiveStringComparator());
+ }
+
+ if (found)
+ mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
+ else
+ mAccessKeyInfo->mAccesskeyIndex = kNotFound;
+ }
+ }
+}
+
+void
+nsTextBoxFrame::RecomputeTitle()
+{
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
+
+ // This doesn't handle language-specific uppercasing/lowercasing
+ // rules, unlike textruns.
+ uint8_t textTransform = StyleText()->mTextTransform;
+ if (textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE) {
+ ToUpperCase(mTitle);
+ } else if (textTransform == NS_STYLE_TEXT_TRANSFORM_LOWERCASE) {
+ ToLowerCase(mTitle);
+ }
+ // We can't handle NS_STYLE_TEXT_TRANSFORM_CAPITALIZE because we
+ // have no clue about word boundaries here. We also don't handle
+ // NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH.
+}
+
+void
+nsTextBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ if (!aOldStyleContext) {
+ // We're just being initialized
+ return;
+ }
+
+ const nsStyleText* oldTextStyle = aOldStyleContext->PeekStyleText();
+ // We should really have oldTextStyle here, since we asked for our
+ // nsStyleText during Init(), but if it's not there for some reason
+ // just assume the worst and recompute mTitle.
+ if (!oldTextStyle ||
+ oldTextStyle->mTextTransform != StyleText()->mTextTransform) {
+ RecomputeTitle();
+ UpdateAccessTitle();
+ }
+}
+
+NS_IMETHODIMP
+nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mNeedsReflowCallback) {
+ nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
+ if (cb) {
+ PresContext()->PresShell()->PostReflowCallback(cb);
+ }
+ mNeedsReflowCallback = false;
+ }
+
+ nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState);
+
+ CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
+
+ const nsStyleText* textStyle = StyleText();
+
+ nsRect scrollBounds(nsPoint(0, 0), GetSize());
+ nsRect textRect = mTextDrawRect;
+
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ nsBoundingMetrics metrics =
+ fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(),
+ mCroppedTitle.Length(),
+ aBoxLayoutState.GetRenderingContext()->GetDrawTarget());
+
+ WritingMode wm = GetWritingMode();
+ LogicalRect tr(wm, textRect, GetSize());
+
+ tr.IStart(wm) -= metrics.leftBearing;
+ tr.ISize(wm) = metrics.width;
+ // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect),
+ tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent;
+ tr.BSize(wm) = metrics.ascent + metrics.descent;
+
+ textRect = tr.GetPhysicalRect(wm, GetSize());
+
+ // Our scrollable overflow is our bounds; our visual overflow may
+ // extend beyond that.
+ nsRect visualBounds;
+ visualBounds.UnionRect(scrollBounds, textRect);
+ nsOverflowAreas overflow(visualBounds, scrollBounds);
+
+ if (textStyle->mTextShadow) {
+ // text-shadow extends our visual but not scrollable bounds
+ nsRect &vis = overflow.VisualOverflow();
+ vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
+ }
+ FinishAndStoreOverflow(overflow, GetSize());
+
+ return rv;
+}
+
+nsRect
+nsTextBoxFrame::GetComponentAlphaBounds()
+{
+ if (StyleText()->mTextShadow) {
+ return GetVisualOverflowRectRelativeToSelf();
+ }
+ return mTextDrawRect;
+}
+
+bool
+nsTextBoxFrame::ComputesOwnOverflowArea()
+{
+ return true;
+}
+
+/* virtual */ void
+nsTextBoxFrame::MarkIntrinsicISizesDirty()
+{
+ mNeedsRecalc = true;
+ nsLeafBoxFrame::MarkIntrinsicISizesDirty();
+}
+
+void
+nsTextBoxFrame::GetTextSize(nsRenderingContext& aRenderingContext,
+ const nsString& aString,
+ nsSize& aSize, nscoord& aAscent)
+{
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ aSize.height = fontMet->MaxHeight();
+ aSize.width =
+ nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
+ aRenderingContext);
+ aAscent = fontMet->MaxAscent();
+}
+
+void
+nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mNeedsRecalc) {
+ nsSize size;
+ nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
+ if (rendContext) {
+ GetTextSize(*rendContext, mTitle, size, mAscent);
+ if (GetWritingMode().IsVertical()) {
+ Swap(size.width, size.height);
+ }
+ mTextSize = size;
+ mNeedsRecalc = false;
+ }
+ }
+}
+
+void
+nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext)
+{
+ WritingMode wm = GetWritingMode();
+
+ LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
+ nsMargin borderPadding;
+ GetXULBorderAndPadding(borderPadding);
+ textRect.Deflate(wm, LogicalMargin(wm, borderPadding));
+
+ // determine (cropped) title and underline position
+ // determine (cropped) title which fits in aRect, and its width
+ // (where "width" is the text measure along its baseline, i.e. actually
+ // a physical height in vertical writing modes)
+ nscoord titleWidth =
+ CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm));
+
+#ifdef ACCESSIBILITY
+ // Make sure to update the accessible tree in case when cropped title is
+ // changed.
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ accService->UpdateLabelValue(PresContext()->PresShell(), mContent,
+ mCroppedTitle);
+ }
+#endif
+
+ // determine if and at which position to put the underline
+ UpdateAccessIndex();
+
+ // make the rect as small as our (cropped) text.
+ nscoord outerISize = textRect.ISize(wm);
+ textRect.ISize(wm) = titleWidth;
+
+ // Align our text within the overall rect by checking our text-align property.
+ const nsStyleText* textStyle = StyleText();
+ if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER) {
+ textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2;
+ } else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END ||
+ (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_LEFT &&
+ !wm.IsBidiLTR()) ||
+ (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT &&
+ wm.IsBidiLTR())) {
+ textRect.IStart(wm) += (outerISize - textRect.ISize(wm));
+ }
+
+ mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize());
+}
+
+/**
+ * Ok return our dimensions
+ */
+nsSize
+nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ CalcTextSize(aBoxLayoutState);
+
+ nsSize size = mTextSize;
+ DISPLAY_PREF_SIZE(this, size);
+
+ AddBorderAndPadding(size);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
+
+ return size;
+}
+
+/**
+ * Ok return our dimensions
+ */
+nsSize
+nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ CalcTextSize(aBoxLayoutState);
+
+ nsSize size = mTextSize;
+ DISPLAY_MIN_SIZE(this, size);
+
+ // if there is cropping our min width becomes our border and padding
+ if (mCropType != CropNone && mCropType != CropAuto) {
+ if (GetWritingMode().IsVertical()) {
+ size.height = 0;
+ } else {
+ size.width = 0;
+ }
+ }
+
+ AddBorderAndPadding(size);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(aBoxLayoutState, this, size, widthSet, heightSet);
+
+ return size;
+}
+
+nscoord
+nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState)
+{
+ CalcTextSize(aBoxLayoutState);
+
+ nscoord ascent = mAscent;
+
+ nsMargin m(0,0,0,0);
+ GetXULBorderAndPadding(m);
+
+ WritingMode wm = GetWritingMode();
+ ascent += LogicalMargin(wm, m).BStart(wm);
+
+ return ascent;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTextBoxFrame::GetFrameName(nsAString& aResult) const
+{
+ MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult);
+ aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]");
+ return NS_OK;
+}
+#endif
+
+// If you make changes to this function, check its counterparts
+// in nsBoxFrame and nsXULLabelFrame
+nsresult
+nsTextBoxFrame::RegUnregAccessKey(bool aDoReg)
+{
+ // if we have no content, we can't do anything
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // check if we have a |control| attribute
+ // do this check first because few elements have control attributes, and we
+ // can weed out most of the elements quickly.
+
+ // XXXjag a side-effect is that we filter out anonymous <label>s
+ // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit
+ // |accesskey| and would otherwise register themselves, overwriting
+ // the content we really meant to be registered.
+ if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control))
+ return NS_OK;
+
+ // see if we even have an access key
+ nsAutoString accessKey;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+
+ if (accessKey.IsEmpty())
+ return NS_OK;
+
+ // With a valid PresContext we can get the ESM
+ // and (un)register the access key
+ EventStateManager* esm = PresContext()->EventStateManager();
+
+ uint32_t key = accessKey.First();
+ if (aDoReg)
+ esm->RegisterAccessKey(mContent, key);
+ else
+ esm->UnregisterAccessKey(mContent, key);
+
+ return NS_OK;
+}
diff --git a/layout/xul/nsTextBoxFrame.h b/layout/xul/nsTextBoxFrame.h
new file mode 100644
index 000000000..ca1b88748
--- /dev/null
+++ b/layout/xul/nsTextBoxFrame.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTextBoxFrame_h___
+#define nsTextBoxFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsLeafBoxFrame.h"
+
+class nsAccessKeyInfo;
+class nsAsyncAccesskeyUpdate;
+class nsFontMetrics;
+
+class nsTextBoxFrame : public nsLeafBoxFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsTextBoxFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override;
+ NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void MarkIntrinsicISizesDirty() override;
+
+ enum CroppingStyle { CropNone, CropLeft, CropRight, CropCenter, CropAuto };
+
+ friend nsIFrame* NS_NewTextBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* asPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ void UpdateAttributes(nsIAtom* aAttribute,
+ bool& aResize,
+ bool& aRedraw);
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual ~nsTextBoxFrame();
+
+ void PaintTitle(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ const nscolor* aOverrideColor);
+
+ nsRect GetComponentAlphaBounds();
+
+ virtual bool ComputesOwnOverflowArea() override;
+
+ void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; }
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+protected:
+ friend class nsAsyncAccesskeyUpdate;
+ friend class nsDisplayXULTextBox;
+ // Should be called only by nsAsyncAccesskeyUpdate.
+ // Returns true if accesskey was updated.
+ bool UpdateAccesskey(nsWeakFrame& aWeakThis);
+ void UpdateAccessTitle();
+ void UpdateAccessIndex();
+
+ // Recompute our title, ignoring the access key but taking into
+ // account text-transform.
+ void RecomputeTitle();
+
+ // REVIEW: SORRY! Couldn't resist devirtualizing these
+ void LayoutTitle(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aRect);
+
+ void CalculateUnderline(DrawTarget* aDrawTarget, nsFontMetrics& aFontMetrics);
+
+ void CalcTextSize(nsBoxLayoutState& aBoxLayoutState);
+
+ void CalcDrawRect(nsRenderingContext &aRenderingContext);
+
+ explicit nsTextBoxFrame(nsStyleContext* aContext);
+
+ nscoord CalculateTitleForWidth(nsRenderingContext& aRenderingContext,
+ nscoord aWidth);
+
+ void GetTextSize(nsRenderingContext& aRenderingContext,
+ const nsString& aString,
+ nsSize& aSize,
+ nscoord& aAscent);
+
+ nsresult RegUnregAccessKey(bool aDoReg);
+
+private:
+
+ bool AlwaysAppendAccessKey();
+ bool InsertSeparatorBeforeAccessKey();
+
+ void DrawText(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aTextRect,
+ const nscolor* aOverrideColor);
+
+ nsString mTitle;
+ nsString mCroppedTitle;
+ nsString mAccessKey;
+ nsSize mTextSize;
+ nsRect mTextDrawRect;
+ nsAccessKeyInfo* mAccessKeyInfo;
+
+ CroppingStyle mCropType;
+ nscoord mAscent;
+ bool mNeedsRecalc;
+ bool mNeedsReflowCallback;
+
+ static bool gAlwaysAppendAccessKey;
+ static bool gAccessKeyPrefInitialized;
+ static bool gInsertSeparatorBeforeAccessKey;
+ static bool gInsertSeparatorPrefInitialized;
+
+}; // class nsTextBoxFrame
+
+#endif /* nsTextBoxFrame_h___ */
diff --git a/layout/xul/nsTitleBarFrame.cpp b/layout/xul/nsTitleBarFrame.cpp
new file mode 100644
index 000000000..2792403dc
--- /dev/null
+++ b/layout/xul/nsTitleBarFrame.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsTitleBarFrame.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsGkAtoms.h"
+#include "nsIWidget.h"
+#include "nsMenuPopupFrame.h"
+#include "nsPresContext.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "nsDisplayList.h"
+#include "nsContentUtils.h"
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+
+//
+// NS_NewTitleBarFrame
+//
+// Creates a new TitleBar frame and returns it
+//
+nsIFrame*
+NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTitleBarFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTitleBarFrame)
+
+nsTitleBarFrame::nsTitleBarFrame(nsStyleContext* aContext)
+:nsBoxFrame(aContext, false)
+{
+ mTrackingMouseMove = false;
+ UpdateMouseThrough();
+}
+
+void
+nsTitleBarFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // override, since we don't want children to get events
+ if (aBuilder->IsForEventDelivery()) {
+ if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents,
+ nsGkAtoms::_true, eCaseMatters))
+ return;
+ }
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+}
+
+nsresult
+nsTitleBarFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ bool doDefault = true;
+
+ switch (aEvent->mMessage) {
+
+ case eMouseDown: {
+ if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+ // titlebar has no effect in non-chrome shells
+ nsCOMPtr<nsIDocShellTreeItem> dsti = aPresContext->GetDocShell();
+ if (dsti) {
+ if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ // we're tracking.
+ mTrackingMouseMove = true;
+
+ // start capture.
+ nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED);
+
+ // remember current mouse coordinates.
+ mLastPoint = aEvent->mRefPoint;
+ }
+ }
+
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ doDefault = false;
+ }
+ }
+ break;
+
+
+ case eMouseUp: {
+ if (mTrackingMouseMove &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+ // we're done tracking.
+ mTrackingMouseMove = false;
+
+ // end capture
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ doDefault = false;
+ }
+ }
+ break;
+
+ case eMouseMove: {
+ if(mTrackingMouseMove)
+ {
+ LayoutDeviceIntPoint nsMoveBy = aEvent->mRefPoint - mLastPoint;
+
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(parent);
+ if (popupFrame)
+ break;
+ parent = parent->GetParent();
+ }
+
+ // if the titlebar is in a popup, move the popup frame, otherwise
+ // move the widget associated with the window
+ if (parent) {
+ nsMenuPopupFrame* menuPopupFrame = static_cast<nsMenuPopupFrame*>(parent);
+ nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget();
+ LayoutDeviceIntRect bounds = widget->GetScreenBounds();
+
+ CSSPoint cssPos = (bounds.TopLeft() + nsMoveBy)
+ / aPresContext->CSSToDevPixelScale();
+ menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
+ }
+ else {
+ nsIPresShell* presShell = aPresContext->PresShell();
+ nsPIDOMWindowOuter *window = presShell->GetDocument()->GetWindow();
+ if (window) {
+ window->MoveBy(nsMoveBy.x, nsMoveBy.y);
+ }
+ }
+
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+
+ doDefault = false;
+ }
+ }
+ break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ MouseClicked(mouseEvent);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if ( doDefault )
+ return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ else
+ return NS_OK;
+}
+
+void
+nsTitleBarFrame::MouseClicked(WidgetMouseEvent* aEvent)
+{
+ // Execute the oncommand event handler.
+ nsContentUtils::DispatchXULCommand(mContent, aEvent && aEvent->IsTrusted());
+}
diff --git a/layout/xul/nsTitleBarFrame.h b/layout/xul/nsTitleBarFrame.h
new file mode 100644
index 000000000..17279c578
--- /dev/null
+++ b/layout/xul/nsTitleBarFrame.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTitleBarFrame_h___
+#define nsTitleBarFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsBoxFrame.h"
+
+class nsTitleBarFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ explicit nsTitleBarFrame(nsStyleContext* aContext);
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void MouseClicked(mozilla::WidgetMouseEvent* aEvent);
+
+ void UpdateMouseThrough() override { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); }
+
+protected:
+ bool mTrackingMouseMove;
+ mozilla::LayoutDeviceIntPoint mLastPoint;
+
+}; // class nsTitleBarFrame
+
+#endif /* nsTitleBarFrame_h___ */
diff --git a/layout/xul/nsXULLabelFrame.cpp b/layout/xul/nsXULLabelFrame.cpp
new file mode 100644
index 000000000..22b875461
--- /dev/null
+++ b/layout/xul/nsXULLabelFrame.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* derived class of nsBlockFrame used for xul:label elements */
+
+#include "mozilla/EventStateManager.h"
+#include "nsXULLabelFrame.h"
+#include "nsHTMLParts.h"
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla;
+
+nsIFrame*
+NS_NewXULLabelFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsXULLabelFrame* it = new (aPresShell) nsXULLabelFrame(aContext);
+
+ it->AddStateBits(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT);
+
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsXULLabelFrame)
+
+// If you make changes to this function, check its counterparts
+// in nsBoxFrame and nsTextBoxFrame
+nsresult
+nsXULLabelFrame::RegUnregAccessKey(bool aDoReg)
+{
+ // if we have no content, we can't do anything
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // To filter out <label>s without a control attribute.
+ // XXXjag a side-effect is that we filter out anonymous <label>s
+ // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit
+ // |accesskey| and would otherwise register themselves, overwriting
+ // the content we really meant to be registered.
+ if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control))
+ return NS_OK;
+
+ nsAutoString accessKey;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+
+ if (accessKey.IsEmpty())
+ return NS_OK;
+
+ // With a valid PresContext we can get the ESM
+ // and register the access key
+ EventStateManager* esm = PresContext()->EventStateManager();
+
+ uint32_t key = accessKey.First();
+ if (aDoReg)
+ esm->RegisterAccessKey(mContent, key);
+ else
+ esm->UnregisterAccessKey(mContent, key);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// nsIFrame
+
+void
+nsXULLabelFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // register access key
+ RegUnregAccessKey(true);
+}
+
+void
+nsXULLabelFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // unregister access key
+ RegUnregAccessKey(false);
+ nsBlockFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsXULLabelFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBlockFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+
+ // If the accesskey changed, register for the new value
+ // The old value has been unregistered in nsXULElement::SetAttr
+ if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control)
+ RegUnregAccessKey(true);
+
+ return rv;
+}
+
+nsIAtom*
+nsXULLabelFrame::GetType() const
+{
+ return nsGkAtoms::XULLabelFrame;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Diagnostics
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsXULLabelFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("XULLabel"), aResult);
+}
+#endif
diff --git a/layout/xul/nsXULLabelFrame.h b/layout/xul/nsXULLabelFrame.h
new file mode 100644
index 000000000..e97fc3fca
--- /dev/null
+++ b/layout/xul/nsXULLabelFrame.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* derived class of nsBlockFrame used for xul:label elements */
+
+#ifndef nsXULLabelFrame_h_
+#define nsXULLabelFrame_h_
+
+#include "mozilla/Attributes.h"
+#include "nsBlockFrame.h"
+
+#ifndef MOZ_XUL
+#error "This file should not be included"
+#endif
+
+class nsXULLabelFrame : public nsBlockFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewXULLabelFrame(nsIPresShell* aPresShell,
+ nsStyleContext *aContext);
+
+ // nsIFrame
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::XULLabelFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+protected:
+ explicit nsXULLabelFrame(nsStyleContext *aContext) : nsBlockFrame(aContext) {}
+
+ nsresult RegUnregAccessKey(bool aDoReg);
+};
+
+nsIFrame*
+NS_NewXULLabelFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+#endif /* !defined(nsXULLabelFrame_h_) */
diff --git a/layout/xul/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp
new file mode 100644
index 000000000..6e8cc3dda
--- /dev/null
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -0,0 +1,2870 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGkAtoms.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsMenuBarFrame.h"
+#include "nsMenuBarListener.h"
+#include "nsContentUtils.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIXULDocument.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsGlobalWindow.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+#include "nsIComponentManager.h"
+#include "nsITimer.h"
+#include "nsFocusManager.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIBaseWindow.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsCaret.h"
+#include "nsIDocument.h"
+#include "nsPIWindowRoot.h"
+#include "nsFrameManager.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 &&
+ nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 &&
+ nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 &&
+ nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 &&
+ nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5,
+ "nsXULPopupManager assumes some keyCode values are consecutive");
+
+const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
+ {
+ eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END
+ eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME
+ eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_LEFT
+ eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
+ eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_RIGHT
+ eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN
+ },
+ {
+ eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END
+ eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME
+ eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_LEFT
+ eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
+ eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_RIGHT
+ eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN
+ }
+};
+
+nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
+
+nsIContent* nsMenuChainItem::Content()
+{
+ return mFrame->GetContent();
+}
+
+void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
+{
+ if (mParent) {
+ NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
+ mParent->mChild = nullptr;
+ }
+ mParent = aParent;
+ if (mParent) {
+ if (mParent->mChild)
+ mParent->mChild->mParent = nullptr;
+ mParent->mChild = this;
+ }
+}
+
+void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
+{
+ // If the item has a child, set the child's parent to this item's parent,
+ // effectively removing the item from the chain. If the item has no child,
+ // just set the parent to null.
+ if (mChild) {
+ NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
+ mChild->SetParent(mParent);
+ }
+ else {
+ // An item without a child should be the first item in the chain, so set
+ // the first item pointer, pointed to by aRoot, to the parent.
+ NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
+ *aRoot = mParent;
+ SetParent(nullptr);
+ }
+}
+
+bool nsXULPopupManager::sDevtoolsDisableAutoHide = false;
+
+const char* kPrefDevtoolsDisableAutoHide =
+ "ui.popup.disable_autohide";
+
+NS_IMPL_ISUPPORTS(nsXULPopupManager,
+ nsIDOMEventListener,
+ nsITimerCallback,
+ nsIObserver)
+
+nsXULPopupManager::nsXULPopupManager() :
+ mRangeOffset(0),
+ mCachedMousePoint(0, 0),
+ mCachedModifiers(0),
+ mActiveMenuBar(nullptr),
+ mPopups(nullptr),
+ mNoHidePanels(nullptr),
+ mTimerMenu(nullptr)
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ }
+ Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide,
+ kPrefDevtoolsDisableAutoHide, false);
+}
+
+nsXULPopupManager::~nsXULPopupManager()
+{
+ NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
+}
+
+nsresult
+nsXULPopupManager::Init()
+{
+ sInstance = new nsXULPopupManager();
+ NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(sInstance);
+ return NS_OK;
+}
+
+void
+nsXULPopupManager::Shutdown()
+{
+ NS_IF_RELEASE(sInstance);
+}
+
+NS_IMETHODIMP
+nsXULPopupManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ if (mKeyListener) {
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
+ mKeyListener = nullptr;
+ }
+ mRangeParent = nullptr;
+ // mOpeningPopup is cleared explicitly soon after using it.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+ }
+
+ return NS_OK;
+}
+
+nsXULPopupManager*
+nsXULPopupManager::GetInstance()
+{
+ MOZ_ASSERT(sInstance);
+ return sInstance;
+}
+
+bool
+nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
+ const nsIntPoint* pos, nsIContent** aLastRolledUp)
+{
+ // We can disable the autohide behavior via a pref to ease debugging.
+ if (nsXULPopupManager::sDevtoolsDisableAutoHide) {
+ // Required on linux to allow events to work on other targets.
+ if (mWidget) {
+ mWidget->CaptureRollupEvents(nullptr, false);
+ }
+ return false;
+ }
+
+ bool consume = false;
+
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (item) {
+ if (aLastRolledUp) {
+ // We need to get the popup that will be closed last, so that widget can
+ // keep track of it so it doesn't reopen if a mousedown event is going to
+ // processed. Keep going up the menu chain to get the first level menu of
+ // the same type. If a different type is encountered it means we have,
+ // for example, a menulist or context menu inside a panel, and we want to
+ // treat these as distinct. It's possible that this menu doesn't end up
+ // closing because the popuphiding event was cancelled, but in that case
+ // we don't need to deal with the menu reopening as it will already still
+ // be open.
+ nsMenuChainItem* first = item;
+ while (first->GetParent()) {
+ nsMenuChainItem* parent = first->GetParent();
+ if (first->Frame()->PopupType() != parent->Frame()->PopupType() ||
+ first->IsContextMenu() != parent->IsContextMenu()) {
+ break;
+ }
+ first = parent;
+ }
+
+
+ *aLastRolledUp = first->Content();
+ }
+
+ ConsumeOutsideClicksResult consumeResult = item->Frame()->ConsumeOutsideClicks();
+ consume = (consumeResult == ConsumeOutsideClicks_True);
+
+ bool rollup = true;
+
+ // If norolluponanchor is true, then don't rollup when clicking the anchor.
+ // This would be used to allow adjusting the caret position in an
+ // autocomplete field without hiding the popup for example.
+ bool noRollupOnAnchor = (!consume && pos &&
+ item->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::norolluponanchor, nsGkAtoms::_true, eCaseMatters));
+
+ // When ConsumeOutsideClicks_ParentOnly is used, always consume the click
+ // when the click was over the anchor. This way, clicking on a menu doesn't
+ // reopen the menu.
+ if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && pos) {
+ nsMenuPopupFrame* popupFrame = item->Frame();
+ nsIntRect anchorRect;
+ if (popupFrame->IsAnchored()) {
+ // Check if the popup has a screen anchor rectangle. If not, get the rectangle
+ // from the anchor element.
+ anchorRect = popupFrame->GetScreenAnchorRect();
+ if (anchorRect.x == -1 || anchorRect.y == -1) {
+ nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor();
+
+ // Check if the anchor has indicated another node to use for checking
+ // for roll-up. That way, we can anchor a popup on anonymous content or
+ // an individual icon, while clicking elsewhere within a button or other
+ // container doesn't result in us re-opening the popup.
+ if (anchor) {
+ nsAutoString consumeAnchor;
+ anchor->GetAttr(kNameSpaceID_None, nsGkAtoms::consumeanchor,
+ consumeAnchor);
+ if (!consumeAnchor.IsEmpty()) {
+ nsIDocument* doc = anchor->GetOwnerDocument();
+ nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
+ if (newAnchor) {
+ anchor = newAnchor;
+ }
+ }
+ }
+
+ if (anchor && anchor->GetPrimaryFrame()) {
+ anchorRect = anchor->GetPrimaryFrame()->GetScreenRect();
+ }
+ }
+ }
+
+ // It's possible that some other element is above the anchor at the same
+ // position, but the only thing that would happen is that the mouse
+ // event will get consumed, so here only a quick coordinates check is
+ // done rather than a slower complete check of what is at that location.
+ nsPresContext* presContext = item->Frame()->PresContext();
+ nsIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x),
+ presContext->DevPixelsToIntCSSPixels(pos->y));
+ if (anchorRect.Contains(posCSSPixels)) {
+ if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
+ consume = true;
+ }
+
+ if (noRollupOnAnchor) {
+ rollup = false;
+ }
+ }
+ }
+
+ if (rollup) {
+ // if a number of popups to close has been specified, determine the last
+ // popup to close
+ nsIContent* lastPopup = nullptr;
+ if (aCount != UINT32_MAX) {
+ nsMenuChainItem* last = item;
+ while (--aCount && last->GetParent()) {
+ last = last->GetParent();
+ }
+ if (last) {
+ lastPopup = last->Content();
+ }
+ }
+
+ nsPresContext* presContext = item->Frame()->PresContext();
+ RefPtr<nsViewManager> viewManager = presContext->PresShell()->GetViewManager();
+
+ HidePopup(item->Content(), true, true, false, true, lastPopup);
+
+ if (aFlush) {
+ // The popup's visibility doesn't update until the minimize animation has
+ // finished, so call UpdateWidgetGeometry to update it right away.
+ viewManager->UpdateWidgetGeometry();
+ }
+ }
+ }
+
+ return consume;
+}
+
+////////////////////////////////////////////////////////////////////////
+bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
+{
+ // should rollup only for autocomplete widgets
+ // XXXndeakin this should really be something the popup has more control over
+
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (!item)
+ return false;
+
+ nsIContent* content = item->Frame()->GetContent();
+ if (!content)
+ return false;
+
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
+ nsGkAtoms::_true, eCaseMatters))
+ return true;
+
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
+ nsGkAtoms::_false, eCaseMatters))
+ return false;
+
+ nsAutoString value;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
+ return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
+}
+
+bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (!item)
+ return false;
+
+ nsMenuPopupFrame* frame = item->Frame();
+ if (frame->PopupType() != ePopupTypePanel)
+ return true;
+
+ nsIContent* content = frame->GetContent();
+ return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::arrow, eCaseMatters));
+}
+
+// a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
+bool nsXULPopupManager::ShouldRollupOnMouseActivate()
+{
+ return false;
+}
+
+uint32_t
+nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain)
+{
+ // this method is used by the widget code to determine the list of popups
+ // that are open. If a mouse click occurs outside one of these popups, the
+ // panels will roll up. If the click is inside a popup, they will not roll up
+ uint32_t count = 0, sameTypeCount = 0;
+
+ NS_ASSERTION(aWidgetChain, "null parameter");
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ while (item) {
+ nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
+ NS_ASSERTION(widget, "open popup has no widget");
+ aWidgetChain->AppendElement(widget.get());
+ // In the case when a menulist inside a panel is open, clicking in the
+ // panel should still roll up the menu, so if a different type is found,
+ // stop scanning.
+ nsMenuChainItem* parent = item->GetParent();
+ if (!sameTypeCount) {
+ count++;
+ if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
+ item->IsContextMenu() != parent->IsContextMenu()) {
+ sameTypeCount = count;
+ }
+ }
+ item = parent;
+ }
+
+ return sameTypeCount;
+}
+
+nsIWidget*
+nsXULPopupManager::GetRollupWidget()
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ return item ? item->Frame()->GetWidget() : nullptr;
+}
+
+void
+nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow)
+{
+ // When the parent window is moved, adjust any child popups. Dismissable
+ // menus and panels are expected to roll up when a window is moved, so there
+ // is no need to check these popups, only the noautohide popups.
+
+ // The items are added to a list so that they can be adjusted bottom to top.
+ nsTArray<nsMenuPopupFrame *> list;
+
+ nsMenuChainItem* item = mNoHidePanels;
+ while (item) {
+ // only move popups that are within the same window and where auto
+ // positioning has not been disabled
+ nsMenuPopupFrame* frame = item->Frame();
+ if (frame->GetAutoPosition()) {
+ nsIContent* popup = frame->GetContent();
+ if (popup) {
+ nsIDocument* document = popup->GetUncomposedDoc();
+ if (document) {
+ if (nsPIDOMWindowOuter* window = document->GetWindow()) {
+ window = window->GetPrivateRoot();
+ if (window == aWindow) {
+ list.AppendElement(frame);
+ }
+ }
+ }
+ }
+ }
+
+ item = item->GetParent();
+ }
+
+ for (int32_t l = list.Length() - 1; l >= 0; l--) {
+ list[l]->SetPopupPosition(nullptr, true, false, true);
+ }
+}
+
+void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell)
+{
+ if (aPresShell->GetDocument()) {
+ AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
+ }
+}
+
+static
+nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
+{
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
+ if (!menuPopupFrame)
+ return nullptr;
+
+ // no point moving or resizing hidden popups
+ if (!menuPopupFrame->IsVisible())
+ return nullptr;
+
+ nsIWidget* widget = menuPopupFrame->GetWidget();
+ if (widget && !widget->IsVisible())
+ return nullptr;
+
+ return menuPopupFrame;
+}
+
+void
+nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
+{
+ nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
+ if (!menuPopupFrame)
+ return;
+
+ nsView* view = menuPopupFrame->GetView();
+ if (!view)
+ return;
+
+ // Don't do anything if the popup is already at the specified location. This
+ // prevents recursive calls when a popup is positioned.
+ LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
+ nsIWidget* widget = menuPopupFrame->GetWidget();
+ if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
+ (!widget || widget->GetClientOffset() ==
+ menuPopupFrame->GetLastClientOffset())) {
+ return;
+ }
+
+ // Update the popup's position using SetPopupPosition if the popup is
+ // anchored and at the parent level as these maintain their position
+ // relative to the parent window. Otherwise, just update the popup to
+ // the specified screen coordinates.
+ if (menuPopupFrame->IsAnchored() &&
+ menuPopupFrame->PopupLevel() == ePopupLevelParent) {
+ menuPopupFrame->SetPopupPosition(nullptr, true, false, true);
+ }
+ else {
+ CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt)
+ / menuPopupFrame->PresContext()->CSSToDevPixelScale();
+ menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
+ }
+}
+
+void
+nsXULPopupManager::PopupResized(nsIFrame* aFrame, LayoutDeviceIntSize aSize)
+{
+ nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
+ if (!menuPopupFrame)
+ return;
+
+ nsView* view = menuPopupFrame->GetView();
+ if (!view)
+ return;
+
+ LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
+ // If the size is what we think it is, we have nothing to do.
+ if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
+ return;
+
+ nsIContent* popup = menuPopupFrame->GetContent();
+
+ // Only set the width and height if the popup already has these attributes.
+ if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) ||
+ !popup->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
+ return;
+ }
+
+ // The size is different. Convert the actual size to css pixels and store it
+ // as 'width' and 'height' attributes on the popup.
+ nsPresContext* presContext = menuPopupFrame->PresContext();
+
+ CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
+ presContext->DevPixelsToIntCSSPixels(aSize.height));
+
+ nsAutoString width, height;
+ width.AppendInt(newCSS.width);
+ height.AppendInt(newCSS.height);
+ popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
+ popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
+}
+
+nsMenuPopupFrame*
+nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
+{
+ if (aShouldFlush) {
+ nsIDocument *document = aContent->GetUncomposedDoc();
+ if (document) {
+ nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+ if (presShell)
+ presShell->FlushPendingNotifications(Flush_Layout);
+ }
+ }
+
+ return do_QueryFrame(aContent->GetPrimaryFrame());
+}
+
+nsMenuChainItem*
+nsXULPopupManager::GetTopVisibleMenu()
+{
+ nsMenuChainItem* item = mPopups;
+ while (item && item->Frame()->PopupState() == ePopupInvisible)
+ item = item->GetParent();
+ return item;
+}
+
+void
+nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset)
+{
+ *aNode = mRangeParent;
+ NS_IF_ADDREF(*aNode);
+ *aOffset = mRangeOffset;
+}
+
+void
+nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
+ nsIContent** aTriggerContent)
+{
+ mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
+
+ if (aTriggerContent) {
+ *aTriggerContent = nullptr;
+ if (aEvent) {
+ // get the trigger content from the event
+ nsCOMPtr<nsIContent> target = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ target.forget(aTriggerContent);
+ }
+ }
+
+ mCachedModifiers = 0;
+
+ nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aEvent);
+ if (uiEvent) {
+ uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
+ uiEvent->GetRangeOffset(&mRangeOffset);
+
+ // get the event coordinates relative to the root frame of the document
+ // containing the popup.
+ NS_ASSERTION(aPopup, "Expected a popup node");
+ WidgetEvent* event = aEvent->WidgetEventPtr();
+ if (event) {
+ WidgetInputEvent* inputEvent = event->AsInputEvent();
+ if (inputEvent) {
+ mCachedModifiers = inputEvent->mModifiers;
+ }
+ nsIDocument* doc = aPopup->GetUncomposedDoc();
+ if (doc) {
+ nsIPresShell* presShell = doc->GetShell();
+ nsPresContext* presContext;
+ if (presShell && (presContext = presShell->GetPresContext())) {
+ nsPresContext* rootDocPresContext =
+ presContext->GetRootPresContext();
+ if (!rootDocPresContext)
+ return;
+ nsIFrame* rootDocumentRootFrame = rootDocPresContext->
+ PresShell()->FrameManager()->GetRootFrame();
+ if ((event->mClass == eMouseEventClass ||
+ event->mClass == eMouseScrollEventClass ||
+ event->mClass == eWheelEventClass) &&
+ !event->AsGUIEvent()->mWidget) {
+ // no widget, so just use the client point if available
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
+ nsIntPoint clientPt;
+ mouseEvent->GetClientX(&clientPt.x);
+ mouseEvent->GetClientY(&clientPt.y);
+
+ // XXX this doesn't handle IFRAMEs in transforms
+ nsPoint thisDocToRootDocOffset = presShell->FrameManager()->
+ GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
+ // convert to device pixels
+ mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
+ nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x);
+ mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
+ nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y);
+ }
+ else if (rootDocumentRootFrame) {
+ nsPoint pnt =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
+ mCachedMousePoint = LayoutDeviceIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
+ rootDocPresContext->AppUnitsToDevPixels(pnt.y));
+ }
+ }
+ }
+ }
+ }
+ else {
+ mRangeParent = nullptr;
+ mRangeOffset = 0;
+ }
+}
+
+void
+nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
+{
+ if (aActivate)
+ mActiveMenuBar = aMenuBar;
+ else if (mActiveMenuBar == aMenuBar)
+ mActiveMenuBar = nullptr;
+
+ UpdateKeyboardListeners();
+}
+
+void
+nsXULPopupManager::ShowMenu(nsIContent *aMenu,
+ bool aSelectFirstItem,
+ bool aAsynchronous)
+{
+ // generate any template content first. Otherwise, the menupopup may not
+ // have been created yet.
+ if (aMenu) {
+ nsIContent* element = aMenu;
+ do {
+ nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element);
+ if (xulelem) {
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xulelem->GetBuilder(getter_AddRefs(builder));
+ if (builder) {
+ builder->CreateContents(aMenu, true);
+ break;
+ }
+ }
+ element = element->GetParent();
+ } while (element);
+ }
+
+ nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
+ if (!menuFrame || !menuFrame->IsMenu())
+ return;
+
+ nsMenuPopupFrame* popupFrame = menuFrame->GetPopup();
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ // inherit whether or not we're a context menu from the parent
+ bool parentIsContextMenu = false;
+ bool onMenuBar = false;
+ bool onmenu = menuFrame->IsOnMenu();
+
+ nsMenuParent* parent = menuFrame->GetMenuParent();
+ if (parent && onmenu) {
+ parentIsContextMenu = parent->IsContextMenu();
+ onMenuBar = parent->IsMenuBar();
+ }
+
+ nsAutoString position;
+
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu);
+ bool isNonEditableMenulist = false;
+ if (menulist) {
+ bool editable;
+ menulist->GetEditable(&editable);
+ isNonEditableMenulist = !editable;
+ }
+
+ if (isNonEditableMenulist) {
+ position.AssignLiteral("selection");
+ }
+ else
+#endif
+
+ if (onMenuBar || !onmenu)
+ position.AssignLiteral("after_start");
+ else
+ position.AssignLiteral("end_before");
+
+ // there is no trigger event for menus
+ InitTriggerEvent(nullptr, nullptr, nullptr);
+ popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0,
+ MenuPopupAnchorType_Node, true);
+
+ if (aAsynchronous) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsXULPopupShowingEvent(popupFrame->GetContent(),
+ parentIsContextMenu, aSelectFirstItem);
+ NS_DispatchToCurrentThread(event);
+ }
+ else {
+ nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
+ FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem);
+ }
+}
+
+void
+nsXULPopupManager::ShowPopup(nsIContent* aPopup,
+ nsIContent* aAnchorContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ bool aSelectFirstItem,
+ nsIDOMEvent* aTriggerEvent)
+{
+ nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ nsCOMPtr<nsIContent> triggerContent;
+ InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
+
+ popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
+ aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
+
+ FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem);
+}
+
+void
+nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ nsIDOMEvent* aTriggerEvent)
+{
+ nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ nsCOMPtr<nsIContent> triggerContent;
+ InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
+
+ popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
+ FirePopupShowingEvent(aPopup, aIsContextMenu, false);
+}
+
+void
+nsXULPopupManager::ShowPopupAtScreenRect(nsIContent* aPopup,
+ const nsAString& aPosition,
+ const nsIntRect& aRect,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ nsIDOMEvent* aTriggerEvent)
+{
+ nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ nsCOMPtr<nsIContent> triggerContent;
+ InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
+
+ popupFrame->InitializePopupAtRect(triggerContent, aPosition,
+ aRect, aAttributesOverride);
+
+ FirePopupShowingEvent(aPopup, aIsContextMenu, false);
+}
+
+void
+nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
+ nsIContent* aTriggerContent,
+ int32_t aXPos, int32_t aYPos)
+{
+ nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ InitTriggerEvent(nullptr, nullptr, nullptr);
+
+ nsPresContext* pc = popupFrame->PresContext();
+ mCachedMousePoint = LayoutDeviceIntPoint(pc->CSSPixelsToDevPixels(aXPos),
+ pc->CSSPixelsToDevPixels(aYPos));
+
+ // coordinates are relative to the root widget
+ nsPresContext* rootPresContext = pc->GetRootPresContext();
+ if (rootPresContext) {
+ nsIWidget *rootWidget = rootPresContext->GetRootWidget();
+ if (rootWidget) {
+ mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
+ }
+ }
+
+ popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
+
+ FirePopupShowingEvent(aPopup, false, false);
+}
+
+void
+nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
+ nsIContent* aAnchorContent,
+ nsAString& aAnchor,
+ nsAString& aAlign,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu)
+{
+ nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
+ if (!popupFrame || !MayShowPopup(popupFrame))
+ return;
+
+ InitTriggerEvent(nullptr, nullptr, nullptr);
+
+ popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
+ aAlign, aXPos, aYPos);
+ FirePopupShowingEvent(aPopup, aIsContextMenu, false);
+}
+
+static void
+CheckCaretDrawingState()
+{
+ // There is 1 caret per document, we need to find the focused
+ // document and erase its caret.
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ fm->GetFocusedWindow(getter_AddRefs(window));
+ if (!window)
+ return;
+
+ auto* piWindow = nsPIDOMWindowOuter::From(window);
+ MOZ_ASSERT(piWindow);
+
+ nsCOMPtr<nsIDocument> focusedDoc = piWindow->GetDoc();
+ if (!focusedDoc)
+ return;
+
+ nsIPresShell* presShell = focusedDoc->GetShell();
+ if (!presShell)
+ return;
+
+ RefPtr<nsCaret> caret = presShell->GetCaret();
+ if (!caret)
+ return;
+ caret->SchedulePaint();
+ }
+}
+
+void
+nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
+ nsMenuPopupFrame* aPopupFrame,
+ bool aIsContextMenu,
+ bool aSelectFirstItem)
+{
+ nsPopupType popupType = aPopupFrame->PopupType();
+ bool ismenu = (popupType == ePopupTypeMenu);
+
+ nsMenuChainItem* item =
+ new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
+ if (!item)
+ return;
+
+ // install keyboard event listeners for navigating menus. For panels, the
+ // escape key may be used to close the panel. However, the ignorekeys
+ // attribute may be used to disable adding these event listeners for popups
+ // that want to handle their own keyboard events.
+ nsAutoString ignorekeys;
+ aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
+ if (ignorekeys.EqualsLiteral("true")) {
+ item->SetIgnoreKeys(eIgnoreKeys_True);
+ } else if (ignorekeys.EqualsLiteral("shortcuts")) {
+ item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
+ }
+
+ if (ismenu) {
+ // if the menu is on a menubar, use the menubar's listener instead
+ nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
+ if (menuFrame) {
+ item->SetOnMenuBar(menuFrame->IsOnMenuBar());
+ }
+ }
+
+ // use a weak frame as the popup will set an open attribute if it is a menu
+ nsWeakFrame weakFrame(aPopupFrame);
+ aPopupFrame->ShowPopup(aIsContextMenu);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // popups normally hide when an outside click occurs. Panels may use
+ // the noautohide attribute to disable this behaviour. It is expected
+ // that the application will hide these popups manually. The tooltip
+ // listener will handle closing the tooltip also.
+ if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) {
+ item->SetParent(mNoHidePanels);
+ mNoHidePanels = item;
+ }
+ else {
+ nsIContent* oldmenu = nullptr;
+ if (mPopups)
+ oldmenu = mPopups->Content();
+ item->SetParent(mPopups);
+ mPopups = item;
+ SetCaptureState(oldmenu);
+ }
+
+ if (aSelectFirstItem) {
+ nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true);
+ aPopupFrame->SetCurrentMenuItem(next);
+ }
+
+ if (ismenu)
+ UpdateMenuItems(aPopup);
+
+ // Caret visibility may have been affected, ensure that
+ // the caret isn't now drawn when it shouldn't be.
+ CheckCaretDrawingState();
+}
+
+void
+nsXULPopupManager::HidePopup(nsIContent* aPopup,
+ bool aHideChain,
+ bool aDeselectMenu,
+ bool aAsynchronous,
+ bool aIsCancel,
+ nsIContent* aLastPopup)
+{
+ // if the popup is on the nohide panels list, remove it but don't close any
+ // other panels
+ nsMenuPopupFrame* popupFrame = nullptr;
+ bool foundPanel = false;
+ nsMenuChainItem* item = mNoHidePanels;
+ while (item) {
+ if (item->Content() == aPopup) {
+ foundPanel = true;
+ popupFrame = item->Frame();
+ break;
+ }
+ item = item->GetParent();
+ }
+
+ // when removing a menu, all of the child popups must be closed
+ nsMenuChainItem* foundMenu = nullptr;
+ item = mPopups;
+ while (item) {
+ if (item->Content() == aPopup) {
+ foundMenu = item;
+ break;
+ }
+ item = item->GetParent();
+ }
+
+ nsPopupType type = ePopupTypePanel;
+ bool deselectMenu = false;
+ nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
+ if (foundMenu) {
+ // at this point, foundMenu will be set to the found item in the list. If
+ // foundMenu is the topmost menu, the one to remove, then there are no other
+ // popups to hide. If foundMenu is not the topmost menu, then there may be
+ // open submenus below it. In this case, we need to make sure that those
+ // submenus are closed up first. To do this, we scan up the menu list to
+ // find the topmost popup with only menus between it and foundMenu and
+ // close that menu first. In synchronous mode, the FirePopupHidingEvent
+ // method will be called which in turn calls HidePopupCallback to close up
+ // the next popup in the chain. These two methods will be called in
+ // sequence recursively to close up all the necessary popups. In
+ // asynchronous mode, a similar process occurs except that the
+ // FirePopupHidingEvent method is called asynchronously. In either case,
+ // nextPopup is set to the content node of the next popup to close, and
+ // lastPopup is set to the last popup in the chain to close, which will be
+ // aPopup, or null to close up all menus.
+
+ nsMenuChainItem* topMenu = foundMenu;
+ // Use IsMenu to ensure that foundMenu is a menu and scan down the child
+ // list until a non-menu is found. If foundMenu isn't a menu at all, don't
+ // scan and just close up this menu.
+ if (foundMenu->IsMenu()) {
+ item = topMenu->GetChild();
+ while (item && item->IsMenu()) {
+ topMenu = item;
+ item = item->GetChild();
+ }
+ }
+
+ deselectMenu = aDeselectMenu;
+ popupToHide = topMenu->Content();
+ popupFrame = topMenu->Frame();
+ type = popupFrame->PopupType();
+
+ nsMenuChainItem* parent = topMenu->GetParent();
+
+ // close up another popup if there is one, and we are either hiding the
+ // entire chain or the item to hide isn't the topmost popup.
+ if (parent && (aHideChain || topMenu != foundMenu))
+ nextPopup = parent->Content();
+
+ lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
+ }
+ else if (foundPanel) {
+ popupToHide = aPopup;
+ } else {
+ // When the popup is in the popuppositioning state, it will not be in the
+ // mPopups list. We need another way to find it and make sure it does not
+ // continue the popup showing process.
+ popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+ if (popupFrame) {
+ if (popupFrame->PopupState() == ePopupPositioning) {
+ // Do basically the same thing we would have done if we had found the
+ // popup in the mPopups list.
+ deselectMenu = aDeselectMenu;
+ popupToHide = aPopup;
+ type = popupFrame->PopupType();
+ } else {
+ // The popup is not positioning. If we were supposed to have handled
+ // closing it, it should have been in mPopups or mNoHidePanels
+ popupFrame = nullptr;
+ }
+ }
+ }
+
+ if (popupFrame) {
+ nsPopupState state = popupFrame->PopupState();
+ // if the popup is already being hidden, don't attempt to hide it again
+ if (state == ePopupHiding)
+ return;
+ // change the popup state to hiding. Don't set the hiding state if the
+ // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
+ // run again. In the invisible state, we just want the events to fire.
+ if (state != ePopupInvisible)
+ popupFrame->SetPopupState(ePopupHiding);
+
+ // for menus, popupToHide is always the frontmost item in the list to hide.
+ if (aAsynchronous) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
+ type, deselectMenu, aIsCancel);
+ NS_DispatchToCurrentThread(event);
+ }
+ else {
+ FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
+ popupFrame->PresContext(), type, deselectMenu, aIsCancel);
+ }
+ }
+}
+
+// This is used to hide the popup after a transition finishes.
+class TransitionEnder : public nsIDOMEventListener
+{
+protected:
+ virtual ~TransitionEnder() { }
+
+public:
+
+ nsCOMPtr<nsIContent> mContent;
+ bool mDeselectMenu;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
+
+ TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
+ : mContent(aContent), mDeselectMenu(aDeselectMenu)
+ {
+ }
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
+ {
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
+
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+
+ // Now hide the popup. There could be other properties transitioning, but
+ // we'll assume they all end at the same time and just hide the popup upon
+ // the first one ending.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && popupFrame) {
+ pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
+ popupFrame->PopupType(), mDeselectMenu);
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
+
+void
+nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
+ nsMenuPopupFrame* aPopupFrame,
+ nsIContent* aNextPopup,
+ nsIContent* aLastPopup,
+ nsPopupType aPopupType,
+ bool aDeselectMenu)
+{
+ if (mCloseTimer && mTimerMenu == aPopupFrame) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ mTimerMenu = nullptr;
+ }
+
+ // The popup to hide is aPopup. Search the list again to find the item that
+ // corresponds to the popup to hide aPopup. This is done because it's
+ // possible someone added another item (attempted to open another popup)
+ // or removed a popup frame during the event processing so the item isn't at
+ // the front anymore.
+ nsMenuChainItem* item = mNoHidePanels;
+ while (item) {
+ if (item->Content() == aPopup) {
+ item->Detach(&mNoHidePanels);
+ break;
+ }
+ item = item->GetParent();
+ }
+
+ if (!item) {
+ item = mPopups;
+ while (item) {
+ if (item->Content() == aPopup) {
+ item->Detach(&mPopups);
+ SetCaptureState(aPopup);
+ break;
+ }
+ item = item->GetParent();
+ }
+ }
+
+ delete item;
+
+ nsWeakFrame weakFrame(aPopupFrame);
+ aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // send the popuphidden event synchronously. This event has no default
+ // behaviour.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
+ WidgetMouseEvent::eReal);
+ EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
+ &event, nullptr, &status);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // if there are more popups to close, look for the next one
+ if (aNextPopup && aPopup != aLastPopup) {
+ nsMenuChainItem* foundMenu = nullptr;
+ nsMenuChainItem* item = mPopups;
+ while (item) {
+ if (item->Content() == aNextPopup) {
+ foundMenu = item;
+ break;
+ }
+ item = item->GetParent();
+ }
+
+ // continue hiding the chain of popups until the last popup aLastPopup
+ // is reached, or until a popup of a different type is reached. This
+ // last check is needed so that a menulist inside a non-menu panel only
+ // closes the menu and not the panel as well.
+ if (foundMenu &&
+ (aLastPopup || aPopupType == foundMenu->PopupType())) {
+
+ nsCOMPtr<nsIContent> popupToHide = item->Content();
+ nsMenuChainItem* parent = item->GetParent();
+
+ nsCOMPtr<nsIContent> nextPopup;
+ if (parent && popupToHide != aLastPopup)
+ nextPopup = parent->Content();
+
+ nsMenuPopupFrame* popupFrame = item->Frame();
+ nsPopupState state = popupFrame->PopupState();
+ if (state == ePopupHiding)
+ return;
+ if (state != ePopupInvisible)
+ popupFrame->SetPopupState(ePopupHiding);
+
+ FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
+ popupFrame->PresContext(),
+ foundMenu->PopupType(), aDeselectMenu, false);
+ }
+ }
+}
+
+void
+nsXULPopupManager::HidePopup(nsIFrame* aFrame)
+{
+ nsMenuPopupFrame* popup = do_QueryFrame(aFrame);
+ if (popup)
+ HidePopup(aFrame->GetContent(), false, true, false, false);
+}
+
+void
+nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
+{
+ // Don't close up immediately.
+ // Kick off a close timer.
+ KillMenuTimer();
+
+ int32_t menuDelay =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
+
+ // Kick off the timer.
+ mCloseTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT);
+
+ // the popup will call PopupDestroyed if it is destroyed, which checks if it
+ // is set to mTimerMenu, so it should be safe to keep a reference to it
+ mTimerMenu = aPopup;
+}
+
+void
+nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames)
+{
+ // Create a weak frame list. This is done in a separate array with the
+ // right capacity predetermined, otherwise the array would get resized and
+ // move the weak frame pointers around.
+ nsTArray<nsWeakFrame> weakPopups(aFrames.Length());
+ uint32_t f;
+ for (f = 0; f < aFrames.Length(); f++) {
+ nsWeakFrame* wframe = weakPopups.AppendElement();
+ if (wframe)
+ *wframe = aFrames[f];
+ }
+
+ for (f = 0; f < weakPopups.Length(); f++) {
+ // check to ensure that the frame is still alive before hiding it.
+ if (weakPopups[f].IsAlive()) {
+ nsMenuPopupFrame* frame =
+ static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame());
+ frame->HidePopup(true, ePopupInvisible);
+ }
+ }
+
+ SetCaptureState(nullptr);
+}
+
+void
+nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup)
+{
+#ifndef MOZ_GTK
+ if (aShouldRollup) {
+ nsMenuChainItem* item = mNoHidePanels;
+ while (item) {
+ if (item->Content() == aPopup) {
+ item->Detach(&mNoHidePanels);
+ nsIContent* oldmenu = nullptr;
+ if (mPopups)
+ oldmenu = mPopups->Content();
+ item->SetParent(mPopups);
+ mPopups = item;
+ SetCaptureState(oldmenu);
+ return;
+ }
+ item = item->GetParent();
+ }
+ } else {
+ nsMenuChainItem* item = mPopups;
+ while (item) {
+ if (item->Content() == aPopup) {
+ item->Detach(&mPopups);
+ item->SetParent(mNoHidePanels);
+ mNoHidePanels = item;
+ SetCaptureState(nullptr);
+ return;
+ }
+ item = item->GetParent();
+ }
+ }
+#endif
+}
+
+bool
+nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
+ while(docShellItem) {
+ if (docShellItem == aExpected)
+ return true;
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShellItem->GetParent(getter_AddRefs(parent));
+ docShellItem = parent;
+ }
+
+ return false;
+}
+
+void
+nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
+{
+ nsTArray<nsMenuPopupFrame *> popupsToHide;
+
+ // iterate to get the set of popup frames to hide
+ nsMenuChainItem* item = mPopups;
+ while (item) {
+ nsMenuChainItem* parent = item->GetParent();
+ if (item->Frame()->PopupState() != ePopupInvisible &&
+ IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
+ nsMenuPopupFrame* frame = item->Frame();
+ item->Detach(&mPopups);
+ delete item;
+ popupsToHide.AppendElement(frame);
+ }
+ item = parent;
+ }
+
+ // now look for panels to hide
+ item = mNoHidePanels;
+ while (item) {
+ nsMenuChainItem* parent = item->GetParent();
+ if (item->Frame()->PopupState() != ePopupInvisible &&
+ IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
+ nsMenuPopupFrame* frame = item->Frame();
+ item->Detach(&mNoHidePanels);
+ delete item;
+ popupsToHide.AppendElement(frame);
+ }
+ item = parent;
+ }
+
+ HidePopupsInList(popupsToHide);
+}
+
+void
+nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
+{
+ CloseMenuMode cmm = CloseMenuMode_Auto;
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
+
+ switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
+ strings, eCaseMatters)) {
+ case 0:
+ cmm = CloseMenuMode_None;
+ break;
+ case 1:
+ cmm = CloseMenuMode_Single;
+ break;
+ default:
+ break;
+ }
+
+ // When a menuitem is selected to be executed, first hide all the open
+ // popups, but don't remove them yet. This is needed when a menu command
+ // opens a modal dialog. The views associated with the popups needed to be
+ // hidden and the accesibility events fired before the command executes, but
+ // the popuphiding/popuphidden events are fired afterwards.
+ nsTArray<nsMenuPopupFrame *> popupsToHide;
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (cmm != CloseMenuMode_None) {
+ while (item) {
+ // if it isn't a <menupopup>, don't close it automatically
+ if (!item->IsMenu())
+ break;
+ nsMenuChainItem* next = item->GetParent();
+ popupsToHide.AppendElement(item->Frame());
+ if (cmm == CloseMenuMode_Single) // only close one level of menu
+ break;
+ item = next;
+ }
+
+ // Now hide the popups. If the closemenu mode is auto, deselect the menu,
+ // otherwise only one popup is closing, so keep the parent menu selected.
+ HidePopupsInList(popupsToHide);
+ }
+
+ aEvent->SetCloseMenuMode(cmm);
+ nsCOMPtr<nsIRunnable> event = aEvent;
+ NS_DispatchToCurrentThread(event);
+}
+
+void
+nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem)
+{
+ nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
+
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+ if (!popupFrame)
+ return;
+
+ nsPresContext *presContext = popupFrame->PresContext();
+ nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+ nsPopupType popupType = popupFrame->PopupType();
+
+ // generate the child frames if they have not already been generated
+ if (!popupFrame->HasGeneratedChildren()) {
+ popupFrame->SetGeneratedChildren();
+ presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
+ }
+
+ // get the frame again
+ nsIFrame* frame = aPopup->GetPrimaryFrame();
+ if (!frame)
+ return;
+
+ presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // cache the popup so that document.popupNode can retrieve the trigger node
+ // during the popupshowing event. It will be cleared below after the event
+ // has fired.
+ mOpeningPopup = aPopup;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
+ WidgetMouseEvent::eReal);
+
+ // coordinates are relative to the root widget
+ nsPresContext* rootPresContext =
+ presShell->GetPresContext()->GetRootPresContext();
+ if (rootPresContext) {
+ rootPresContext->PresShell()->GetViewManager()->
+ GetRootWidget(getter_AddRefs(event.mWidget));
+ }
+ else {
+ event.mWidget = nullptr;
+ }
+
+ event.mRefPoint = mCachedMousePoint;
+ event.mModifiers = mCachedModifiers;
+ EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
+
+ mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
+ mOpeningPopup = nullptr;
+
+ mCachedModifiers = 0;
+
+ // if a panel, blur whatever has focus so that the panel can take the focus.
+ // This is done after the popupshowing event in case that event is cancelled.
+ // Using noautofocus="true" will disable this behaviour, which is needed for
+ // the autocomplete widget as it manages focus itself.
+ if (popupType == ePopupTypePanel &&
+ !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsIDocument* doc = popup->GetUncomposedDoc();
+
+ // Only remove the focus if the currently focused item is ouside the
+ // popup. It isn't a big deal if the current focus is in a child popup
+ // inside the popup as that shouldn't be visible. This check ensures that
+ // a node inside the popup that is focused during a popupshowing event
+ // remains focused.
+ nsCOMPtr<nsIDOMElement> currentFocusElement;
+ fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
+ nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
+ if (doc && currentFocus &&
+ !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
+ fm->ClearFocus(doc->GetWindow());
+ }
+ }
+ }
+
+ // clear these as they are no longer valid
+ mRangeParent = nullptr;
+ mRangeOffset = 0;
+
+ // get the frame again in case it went away
+ popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+ if (popupFrame) {
+ // if the event was cancelled, don't open the popup, reset its state back
+ // to closed and clear its trigger content.
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ popupFrame->SetPopupState(ePopupClosed);
+ popupFrame->ClearTriggerContent();
+ }
+ else {
+ // Now check if we need to fire the popuppositioned event. If not, call
+ // ShowPopupCallback directly.
+
+ // The popuppositioned event only fires on arrow panels for now.
+ if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::arrow, eCaseMatters)) {
+ popupFrame->ShowWithPositionedEvent();
+ presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ else {
+ ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
+ }
+ }
+ }
+}
+
+void
+nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
+ nsIContent* aNextPopup,
+ nsIContent* aLastPopup,
+ nsPresContext *aPresContext,
+ nsPopupType aPopupType,
+ bool aDeselectMenu,
+ bool aIsCancel)
+{
+ nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
+ mozilla::Unused << presShell; // This presShell may be keeping things alive on non GTK platforms
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
+ WidgetMouseEvent::eReal);
+ EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
+
+ // when a panel is closed, blur whatever has focus inside the popup
+ if (aPopupType == ePopupTypePanel &&
+ !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsIDocument* doc = aPopup->GetUncomposedDoc();
+
+ // Remove the focus from the focused node only if it is inside the popup.
+ nsCOMPtr<nsIDOMElement> currentFocusElement;
+ fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
+ nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
+ if (doc && currentFocus &&
+ nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
+ fm->ClearFocus(doc->GetWindow());
+ }
+ }
+ }
+
+ // get frame again in case it went away
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+ if (popupFrame) {
+ // if the event was cancelled, don't hide the popup, and reset its
+ // state back to open. Only popups in chrome shells can prevent a popup
+ // from hiding.
+ if (status == nsEventStatus_eConsumeNoDefault &&
+ !popupFrame->IsInContentShell()) {
+ // XXXndeakin
+ // If an attempt was made to hide this popup before the popupshown event
+ // fired, then ePopupShown is set here even though it should be
+ // ePopupVisible. This probably isn't worth the hassle of handling.
+ popupFrame->SetPopupState(ePopupShown);
+ }
+ else {
+ // If the popup has an animate attribute and it is not set to false, check
+ // if it has a closing transition and wait for it to finish. The transition
+ // may still occur either way, but the view will be hidden and you won't be
+ // able to see it. If there is a next popup, indicating that mutliple popups
+ // are rolling up, don't wait and hide the popup right away since the effect
+ // would likely be undesirable. Transitions are currently disabled on Linux
+ // due to rendering issues on certain configurations.
+#ifndef MOZ_WIDGET_GTK
+ if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) {
+ // If animate="false" then don't transition at all. If animate="cancel",
+ // only show the transition if cancelling the popup or rolling up.
+ // Otherwise, always show the transition.
+ nsAutoString animate;
+ aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate);
+
+ if (!animate.EqualsLiteral("false") &&
+ (!animate.EqualsLiteral("cancel") || aIsCancel)) {
+ presShell->FlushPendingNotifications(Flush_Layout);
+
+ // Get the frame again in case the flush caused it to go away
+ popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+ if (!popupFrame)
+ return;
+
+ if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) {
+ RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
+ aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
+ ender, false, false);
+ return;
+ }
+ }
+ }
+#endif
+
+ HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
+ aPopupType, aDeselectMenu);
+ }
+ }
+}
+
+bool
+nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
+{
+ // a popup is open if it is in the open list. The assertions ensure that the
+ // frame is in the correct state. If the popup is in the hiding or invisible
+ // state, it will still be in the open popup list until it is closed.
+ nsMenuChainItem* item = mPopups;
+ while (item) {
+ if (item->Content() == aPopup) {
+ NS_ASSERTION(item->Frame()->IsOpen() ||
+ item->Frame()->PopupState() == ePopupHiding ||
+ item->Frame()->PopupState() == ePopupInvisible,
+ "popup in open list not actually open");
+ return true;
+ }
+ item = item->GetParent();
+ }
+
+ item = mNoHidePanels;
+ while (item) {
+ if (item->Content() == aPopup) {
+ NS_ASSERTION(item->Frame()->IsOpen() ||
+ item->Frame()->PopupState() == ePopupHiding ||
+ item->Frame()->PopupState() == ePopupInvisible,
+ "popup in open list not actually open");
+ return true;
+ }
+ item = item->GetParent();
+ }
+
+ return false;
+}
+
+bool
+nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ while (item) {
+ nsMenuPopupFrame* popup = item->Frame();
+ if (popup && popup->IsOpen()) {
+ nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
+ if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
+ return true;
+ }
+ }
+ item = item->GetParent();
+ }
+
+ return false;
+}
+
+nsIFrame*
+nsXULPopupManager::GetTopPopup(nsPopupType aType)
+{
+ if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
+ return mNoHidePanels->Frame();
+
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ while (item) {
+ if (item->PopupType() == aType || aType == ePopupTypeAny)
+ return item->Frame();
+ item = item->GetParent();
+ }
+
+ return nullptr;
+}
+
+void
+nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
+{
+ aPopups.Clear();
+
+ // Iterate over both lists of popups
+ nsMenuChainItem* item = mPopups;
+ for (int32_t list = 0; list < 2; list++) {
+ while (item) {
+ // Skip panels which are not visible as well as popups that
+ // are transparent to mouse events.
+ if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
+ aPopups.AppendElement(item->Frame());
+ }
+
+ item = item->GetParent();
+ }
+
+ item = mNoHidePanels;
+ }
+}
+
+already_AddRefed<nsIDOMNode>
+nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
+{
+ if (!aDocument)
+ return nullptr;
+
+ nsCOMPtr<nsIDOMNode> node;
+
+ // if mOpeningPopup is set, it means that a popupshowing event is being
+ // fired. In this case, just use the cached node, as the popup is not yet in
+ // the list of open popups.
+ if (mOpeningPopup && mOpeningPopup->GetUncomposedDoc() == aDocument &&
+ aIsTooltip == mOpeningPopup->IsXULElement(nsGkAtoms::tooltip)) {
+ node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false)));
+ }
+ else {
+ nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
+ while (item) {
+ // look for a popup of the same type and document.
+ if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
+ item->Content()->GetUncomposedDoc() == aDocument) {
+ node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame()));
+ if (node)
+ break;
+ }
+ item = item->GetParent();
+ }
+ }
+
+ return node.forget();
+}
+
+bool
+nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
+{
+ // if a popup's IsOpen method returns true, then the popup must always be in
+ // the popup chain scanned in IsPopupOpen.
+ NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
+ "popup frame state doesn't match XULPopupManager open state");
+
+ nsPopupState state = aPopup->PopupState();
+
+ // if the popup is not in the open popup chain, then it must have a state that
+ // is either closed, in the process of being shown, or invisible.
+ NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
+ state == ePopupShowing || state == ePopupPositioning ||
+ state == ePopupInvisible,
+ "popup not in XULPopupManager open list is open");
+
+ // don't show popups unless they are closed or invisible
+ if (state != ePopupClosed && state != ePopupInvisible)
+ return false;
+
+ // Don't show popups that we already have in our popup chain
+ if (IsPopupOpen(aPopup->GetContent())) {
+ NS_WARNING("Refusing to show duplicate popup");
+ return false;
+ }
+
+ // if the popup was just rolled up, don't reopen it
+ nsCOMPtr<nsIWidget> widget = aPopup->GetWidget();
+ if (widget && widget->GetLastRollup() == aPopup->GetContent())
+ return false;
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
+ nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
+ if (!baseWin)
+ return false;
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ dsti->GetRootTreeItem(getter_AddRefs(root));
+ if (!root) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow();
+
+ // chrome shells can always open popups, but other types of shells can only
+ // open popups when they are focused and visible
+ if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ // only allow popups in active windows
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm || !rootWin)
+ return false;
+
+ nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+ fm->GetActiveWindow(getter_AddRefs(activeWindow));
+ if (activeWindow != rootWin)
+ return false;
+
+ // only allow popups in visible frames
+ bool visible;
+ baseWin->GetVisibility(&visible);
+ if (!visible)
+ return false;
+ }
+
+ // platforms respond differently when an popup is opened in a minimized
+ // window, so this is always disabled.
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWin->GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
+ return false;
+ }
+
+#ifdef XP_MACOSX
+ if (rootWin) {
+ auto globalWin = nsGlobalWindow::Cast(rootWin.get());
+ if (globalWin->IsInModalState()) {
+ return false;
+ }
+ }
+#endif
+
+ // cannot open a popup that is a submenu of a menupopup that isn't open.
+ nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
+ if (menuFrame) {
+ nsMenuParent* parentPopup = menuFrame->GetMenuParent();
+ if (parentPopup && !parentPopup->IsOpen())
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
+{
+ // when a popup frame is destroyed, just unhook it from the list of popups
+ if (mTimerMenu == aPopup) {
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+ mTimerMenu = nullptr;
+ }
+
+ nsMenuChainItem* item = mNoHidePanels;
+ while (item) {
+ if (item->Frame() == aPopup) {
+ item->Detach(&mNoHidePanels);
+ delete item;
+ break;
+ }
+ item = item->GetParent();
+ }
+
+ nsTArray<nsMenuPopupFrame *> popupsToHide;
+
+ item = mPopups;
+ while (item) {
+ nsMenuPopupFrame* frame = item->Frame();
+ if (frame == aPopup) {
+ if (frame->PopupState() != ePopupInvisible) {
+ // Iterate through any child menus and hide them as well, since the
+ // parent is going away. We won't remove them from the list yet, just
+ // hide them, as they will be removed from the list when this function
+ // gets called for that child frame.
+ nsMenuChainItem* child = item->GetChild();
+ while (child) {
+ // if the popup is a child frame of the menu that was destroyed, add
+ // it to the list of popups to hide. Don't bother with the events
+ // since the frames are going away. If the child menu is not a child
+ // frame, for example, a context menu, use HidePopup instead, but call
+ // it asynchronously since we are in the middle of frame destruction.
+ nsMenuPopupFrame* childframe = child->Frame();
+ if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
+ popupsToHide.AppendElement(childframe);
+ }
+ else {
+ // HidePopup will take care of hiding any of its children, so
+ // break out afterwards
+ HidePopup(child->Content(), false, false, true, false);
+ break;
+ }
+
+ child = child->GetChild();
+ }
+ }
+
+ item->Detach(&mPopups);
+ delete item;
+ break;
+ }
+
+ item = item->GetParent();
+ }
+
+ HidePopupsInList(popupsToHide);
+}
+
+bool
+nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ while (item && item->Frame() != aPopup) {
+ if (item->IsContextMenu())
+ return true;
+ item = item->GetParent();
+ }
+
+ return false;
+}
+
+void
+nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (item && aOldPopup == item->Content())
+ return;
+
+ if (mWidget) {
+ mWidget->CaptureRollupEvents(nullptr, false);
+ mWidget = nullptr;
+ }
+
+ if (item) {
+ nsMenuPopupFrame* popup = item->Frame();
+ mWidget = popup->GetWidget();
+ if (mWidget) {
+ mWidget->CaptureRollupEvents(nullptr, true);
+ popup->AttachedDismissalListener();
+ }
+ }
+
+ UpdateKeyboardListeners();
+}
+
+void
+nsXULPopupManager::UpdateKeyboardListeners()
+{
+ nsCOMPtr<EventTarget> newTarget;
+ bool isForMenu = false;
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (item) {
+ if (item->IgnoreKeys() != eIgnoreKeys_True) {
+ newTarget = item->Content()->GetComposedDoc();
+ }
+ isForMenu = item->PopupType() == ePopupTypeMenu;
+ }
+ else if (mActiveMenuBar) {
+ newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
+ isForMenu = true;
+ }
+
+ if (mKeyListener != newTarget) {
+ if (mKeyListener) {
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
+ mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
+ mKeyListener = nullptr;
+ nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
+ }
+
+ if (newTarget) {
+ newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
+ newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
+ newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
+ nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
+ mKeyListener = newTarget;
+ }
+ }
+}
+
+void
+nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
+{
+ // Walk all of the menu's children, checking to see if any of them has a
+ // command attribute. If so, then several attributes must potentially be updated.
+
+ nsCOMPtr<nsIDocument> document = aPopup->GetUncomposedDoc();
+ if (!document) {
+ return;
+ }
+
+ for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
+ grandChild;
+ grandChild = grandChild->GetNextSibling()) {
+ if (grandChild->IsXULElement(nsGkAtoms::menugroup)) {
+ if (grandChild->GetChildCount() == 0) {
+ continue;
+ }
+ grandChild = grandChild->GetFirstChild();
+ }
+ if (grandChild->IsXULElement(nsGkAtoms::menuitem)) {
+ // See if we have a command attribute.
+ nsAutoString command;
+ grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
+ if (!command.IsEmpty()) {
+ // We do! Look it up in our document
+ RefPtr<dom::Element> commandElement =
+ document->GetElementById(command);
+ if (commandElement) {
+ nsAutoString commandValue;
+ // The menu's disabled state needs to be updated to match the command.
+ if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
+ grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true);
+ else
+ grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+
+ // The menu's label, accesskey checked and hidden states need to be updated
+ // to match the command. Note that unlike the disabled state if the
+ // command has *no* value, we assume the menu is supplying its own.
+ if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
+ grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true);
+
+ if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
+ grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true);
+
+ if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
+ grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true);
+
+ if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue))
+ grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true);
+ }
+ }
+ }
+ if (!grandChild->GetNextSibling() &&
+ grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
+ grandChild = grandChild->GetParent();
+ }
+ }
+}
+
+// Notify
+//
+// The item selection timer has fired, we might have to readjust the
+// selected item. There are two cases here that we are trying to deal with:
+// (1) diagonal movement from a parent menu to a submenu passing briefly over
+// other items, and
+// (2) moving out from a submenu to a parent or grandparent menu.
+// In both cases, |mTimerMenu| is the menu item that might have an open submenu and
+// the first item in |mPopups| is the item the mouse is currently over, which could be
+// none of them.
+//
+// case (1):
+// As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
+// submenu, it probably passes through one or more sibilings (B). As the mouse passes
+// through B, it becomes the current menu item and the timer is set and mTimerMenu is
+// set to A. Before the timer fires, the mouse leaves the menu containing A and B and
+// enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
+// so we have to see if anything in A's children is selected (recall that even disabled
+// items are selected, the style just doesn't show it). If that is the case, we need to
+// set the selected item back to A.
+//
+// case (2);
+// Item A has an open submenu, and in it there is an item (B) which also has an open
+// submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
+// submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
+// the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
+// the mouse is still within C. The correct behavior is to set the current item to C
+// and close up the chain parented at A.
+//
+// This brings up the question of is the logic of case (1) enough? The answer is no,
+// and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
+// child, and if it does, set the selected item to A. Because B has a submenu open, it
+// is selected and as a result, A is set to be the selected item even though the mouse
+// rests in C -- very wrong.
+//
+// The solution is to use the same idea, but instead of only checking one level,
+// drill all the way down to the deepest open submenu and check if it has something
+// selected. Since the mouse is in a grandparent, it won't, and we know that we can
+// safely close up A and all its children.
+//
+// The code below melds the two cases together.
+//
+nsresult
+nsXULPopupManager::Notify(nsITimer* aTimer)
+{
+ if (aTimer == mCloseTimer)
+ KillMenuTimer();
+
+ return NS_OK;
+}
+
+void
+nsXULPopupManager::KillMenuTimer()
+{
+ if (mCloseTimer && mTimerMenu) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+
+ if (mTimerMenu->IsOpen())
+ HidePopup(mTimerMenu->GetContent(), false, false, true, false);
+ }
+
+ mTimerMenu = nullptr;
+}
+
+void
+nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
+{
+ if (mCloseTimer && mTimerMenu == aMenuParent) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ mTimerMenu = nullptr;
+ }
+}
+
+bool
+nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
+ nsMenuPopupFrame* aFrame)
+{
+ // On Windows, don't check shortcuts when the accelerator key is down.
+#ifdef XP_WIN
+ WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
+ if (evt && evt->IsAccel()) {
+ return false;
+ }
+#endif
+
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (!aFrame && item)
+ aFrame = item->Frame();
+
+ if (aFrame) {
+ bool action;
+ nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
+ if (result) {
+ aFrame->ChangeMenuItem(result, false, true);
+ if (action) {
+ WidgetGUIEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsGUIEvent();
+ nsMenuFrame* menuToOpen = result->Enter(evt);
+ if (menuToOpen) {
+ nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
+ ShowMenu(content, true, false);
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ if (mActiveMenuBar) {
+ nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
+ if (result) {
+ mActiveMenuBar->SetActive(true);
+ result->OpenMenu(true);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool
+nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode)
+{
+ // navigate up through the open menus, looking for the topmost one
+ // in the same hierarchy
+ nsMenuChainItem* item = nullptr;
+ nsMenuChainItem* nextitem = GetTopVisibleMenu();
+
+ while (nextitem) {
+ item = nextitem;
+ nextitem = item->GetParent();
+
+ if (nextitem) {
+ // stop if the parent isn't a menu
+ if (!nextitem->IsMenu())
+ break;
+
+ // check to make sure that the parent is actually the parent menu. It won't
+ // be if the parent is in a different frame hierarchy, for example, for a
+ // context menu opened on another menu.
+ nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
+ nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
+ if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
+ break;
+ }
+ }
+ }
+
+ nsIFrame* itemFrame;
+ if (item)
+ itemFrame = item->Frame();
+ else if (mActiveMenuBar)
+ itemFrame = mActiveMenuBar;
+ else
+ return false;
+
+ nsNavigationDirection theDirection;
+ NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END &&
+ aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code");
+ theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
+
+ // if a popup is open, first check for navigation within the popup
+ if (item && HandleKeyboardNavigationInPopup(item, theDirection))
+ return true;
+
+ // no popup handled the key, so check the active menubar, if any
+ if (mActiveMenuBar) {
+ nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
+
+ if (NS_DIRECTION_IS_INLINE(theDirection)) {
+ nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
+ GetNextMenuItem(mActiveMenuBar, currentMenu, false) :
+ GetPreviousMenuItem(mActiveMenuBar, currentMenu, false);
+ mActiveMenuBar->ChangeMenuItem(nextItem, true, true);
+ return true;
+ }
+ else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
+ // Open the menu and select its first item.
+ if (currentMenu) {
+ nsCOMPtr<nsIContent> content = currentMenu->GetContent();
+ ShowMenu(content, true, false);
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
+ nsMenuPopupFrame* aFrame,
+ nsNavigationDirection aDir)
+{
+ NS_ASSERTION(aFrame, "aFrame is null");
+ NS_ASSERTION(!item || item->Frame() == aFrame,
+ "aFrame is expected to be equal to item->Frame()");
+
+ nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
+
+ aFrame->ClearIncrementalString();
+
+ // This method only gets called if we're open.
+ if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
+ // We've been opened, but we haven't had anything selected.
+ // We can handle End, but our parent handles Start.
+ if (aDir == eNavigationDirection_End) {
+ nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true);
+ if (nextItem) {
+ aFrame->ChangeMenuItem(nextItem, false, true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool isContainer = false;
+ bool isOpen = false;
+ if (currentMenu) {
+ isOpen = currentMenu->IsOpen();
+ isContainer = currentMenu->IsMenu();
+ if (isOpen) {
+ // for an open popup, have the child process the event
+ nsMenuChainItem* child = item ? item->GetChild() : nullptr;
+ if (child && HandleKeyboardNavigationInPopup(child, aDir))
+ return true;
+ }
+ else if (aDir == eNavigationDirection_End &&
+ isContainer && !currentMenu->IsDisabled()) {
+ // The menu is not yet open. Open it and select the first item.
+ nsCOMPtr<nsIContent> content = currentMenu->GetContent();
+ ShowMenu(content, true, false);
+ return true;
+ }
+ }
+
+ // For block progression, we can move in either direction
+ if (NS_DIRECTION_IS_BLOCK(aDir) ||
+ NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
+ nsMenuFrame* nextItem;
+
+ if (aDir == eNavigationDirection_Before)
+ nextItem = GetPreviousMenuItem(aFrame, currentMenu, true);
+ else if (aDir == eNavigationDirection_After)
+ nextItem = GetNextMenuItem(aFrame, currentMenu, true);
+ else if (aDir == eNavigationDirection_First)
+ nextItem = GetNextMenuItem(aFrame, nullptr, true);
+ else
+ nextItem = GetPreviousMenuItem(aFrame, nullptr, true);
+
+ if (nextItem) {
+ aFrame->ChangeMenuItem(nextItem, false, true);
+ return true;
+ }
+ }
+ else if (currentMenu && isContainer && isOpen) {
+ if (aDir == eNavigationDirection_Start) {
+ // close a submenu when Left is pressed
+ nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
+ if (popupFrame)
+ HidePopup(popupFrame->GetContent(), false, false, false, false);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsXULPopupManager::HandleKeyboardEventWithKeyCode(
+ nsIDOMKeyEvent* aKeyEvent,
+ nsMenuChainItem* aTopVisibleMenuItem)
+{
+ uint32_t keyCode;
+ aKeyEvent->GetKeyCode(&keyCode);
+
+ // Escape should close panels, but the other keys should have no effect.
+ if (aTopVisibleMenuItem &&
+ aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
+ if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) {
+ HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
+ aKeyEvent->AsEvent()->StopPropagation();
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ aKeyEvent->AsEvent()->PreventDefault();
+ }
+ return true;
+ }
+
+ bool consume = (mPopups || mActiveMenuBar);
+ switch (keyCode) {
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+#ifndef XP_MACOSX
+ // roll up the popup when alt+up/down are pressed within a menulist.
+ bool alt;
+ aKeyEvent->GetAltKey(&alt);
+ if (alt && aTopVisibleMenuItem && aTopVisibleMenuItem->Frame()->IsMenuList()) {
+ Rollup(0, false, nullptr, nullptr);
+ break;
+ }
+ MOZ_FALLTHROUGH;
+#endif
+
+ case nsIDOMKeyEvent::DOM_VK_LEFT:
+ case nsIDOMKeyEvent::DOM_VK_RIGHT:
+ case nsIDOMKeyEvent::DOM_VK_HOME:
+ case nsIDOMKeyEvent::DOM_VK_END:
+ HandleKeyboardNavigation(keyCode);
+ break;
+
+ case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
+ case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
+ if (aTopVisibleMenuItem) {
+ aTopVisibleMenuItem->Frame()->ChangeByPage(keyCode == nsIDOMKeyEvent::DOM_VK_PAGE_UP);
+ }
+ break;
+
+ case nsIDOMKeyEvent::DOM_VK_ESCAPE:
+ // Pressing Escape hides one level of menus only. If no menu is open,
+ // check if a menubar is active and inform it that a menu closed. Even
+ // though in this latter case, a menu didn't actually close, the effect
+ // ends up being the same. Similar for the tab key below.
+ if (aTopVisibleMenuItem) {
+ HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
+ } else if (mActiveMenuBar) {
+ mActiveMenuBar->MenuClosed();
+ }
+ break;
+
+ case nsIDOMKeyEvent::DOM_VK_TAB:
+#ifndef XP_MACOSX
+ case nsIDOMKeyEvent::DOM_VK_F10:
+#endif
+ if (aTopVisibleMenuItem &&
+ !aTopVisibleMenuItem->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::activateontab, nsGkAtoms::_true, eCaseMatters)) {
+ // close popups or deactivate menubar when Tab or F10 are pressed
+ Rollup(0, false, nullptr, nullptr);
+ break;
+ } else if (mActiveMenuBar) {
+ mActiveMenuBar->MenuClosed();
+ break;
+ }
+ // Intentional fall-through to RETURN case
+ MOZ_FALLTHROUGH;
+
+ case nsIDOMKeyEvent::DOM_VK_RETURN: {
+ // If there is a popup open, check if the current item needs to be opened.
+ // Otherwise, tell the active menubar, if any, to activate the menu. The
+ // Enter method will return a menu if one needs to be opened as a result.
+ nsMenuFrame* menuToOpen = nullptr;
+ WidgetGUIEvent* GUIEvent = aKeyEvent->AsEvent()->
+ WidgetEventPtr()->AsGUIEvent();
+
+ if (aTopVisibleMenuItem) {
+ menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
+ } else if (mActiveMenuBar) {
+ menuToOpen = mActiveMenuBar->Enter(GUIEvent);
+ }
+ if (menuToOpen) {
+ nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
+ ShowMenu(content, true, false);
+ }
+ break;
+ }
+
+ default:
+ return false;
+ }
+
+ if (consume) {
+ aKeyEvent->AsEvent()->StopPropagation();
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ aKeyEvent->AsEvent()->PreventDefault();
+ }
+ return true;
+}
+
+nsMenuFrame*
+nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent,
+ nsMenuFrame* aStart,
+ bool aIsPopup)
+{
+ nsPresContext* presContext = aParent->PresContext();
+ auto insertion = presContext->PresShell()->
+ FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
+ nsContainerFrame* immediateParent = insertion.mParentFrame;
+ if (!immediateParent)
+ immediateParent = aParent;
+
+ nsIFrame* currFrame = nullptr;
+ if (aStart) {
+ if (aStart->GetNextSibling())
+ currFrame = aStart->GetNextSibling();
+ else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = aStart->GetParent()->GetNextSibling();
+ }
+ else
+ currFrame = immediateParent->PrincipalChildList().FirstChild();
+
+ while (currFrame) {
+ // See if it's a menu item.
+ nsIContent* currFrameContent = currFrame->GetContent();
+ if (IsValidMenuItem(currFrameContent, aIsPopup)) {
+ return do_QueryFrame(currFrame);
+ }
+ if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
+ currFrameContent->GetChildCount() > 0)
+ currFrame = currFrame->PrincipalChildList().FirstChild();
+ else if (!currFrame->GetNextSibling() &&
+ currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = currFrame->GetParent()->GetNextSibling();
+ else
+ currFrame = currFrame->GetNextSibling();
+ }
+
+ currFrame = immediateParent->PrincipalChildList().FirstChild();
+
+ // Still don't have anything. Try cycling from the beginning.
+ while (currFrame && currFrame != aStart) {
+ // See if it's a menu item.
+ nsIContent* currFrameContent = currFrame->GetContent();
+ if (IsValidMenuItem(currFrameContent, aIsPopup)) {
+ return do_QueryFrame(currFrame);
+ }
+ if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
+ currFrameContent->GetChildCount() > 0)
+ currFrame = currFrame->PrincipalChildList().FirstChild();
+ else if (!currFrame->GetNextSibling() &&
+ currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = currFrame->GetParent()->GetNextSibling();
+ else
+ currFrame = currFrame->GetNextSibling();
+ }
+
+ // No luck. Just return our start value.
+ return aStart;
+}
+
+nsMenuFrame*
+nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent,
+ nsMenuFrame* aStart,
+ bool aIsPopup)
+{
+ nsPresContext* presContext = aParent->PresContext();
+ auto insertion = presContext->PresShell()->
+ FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
+ nsContainerFrame* immediateParent = insertion.mParentFrame;
+ if (!immediateParent)
+ immediateParent = aParent;
+
+ const nsFrameList& frames(immediateParent->PrincipalChildList());
+
+ nsIFrame* currFrame = nullptr;
+ if (aStart) {
+ if (aStart->GetPrevSibling())
+ currFrame = aStart->GetPrevSibling();
+ else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = aStart->GetParent()->GetPrevSibling();
+ }
+ else
+ currFrame = frames.LastChild();
+
+ while (currFrame) {
+ // See if it's a menu item.
+ nsIContent* currFrameContent = currFrame->GetContent();
+ if (IsValidMenuItem(currFrameContent, aIsPopup)) {
+ return do_QueryFrame(currFrame);
+ }
+ if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
+ currFrameContent->GetChildCount() > 0) {
+ const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
+ currFrame = menugroupFrames.LastChild();
+ }
+ else if (!currFrame->GetPrevSibling() &&
+ currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = currFrame->GetParent()->GetPrevSibling();
+ else
+ currFrame = currFrame->GetPrevSibling();
+ }
+
+ currFrame = frames.LastChild();
+
+ // Still don't have anything. Try cycling from the end.
+ while (currFrame && currFrame != aStart) {
+ // See if it's a menu item.
+ nsIContent* currFrameContent = currFrame->GetContent();
+ if (IsValidMenuItem(currFrameContent, aIsPopup)) {
+ return do_QueryFrame(currFrame);
+ }
+ if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
+ currFrameContent->GetChildCount() > 0) {
+ const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
+ currFrame = menugroupFrames.LastChild();
+ }
+ else if (!currFrame->GetPrevSibling() &&
+ currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
+ currFrame = currFrame->GetParent()->GetPrevSibling();
+ else
+ currFrame = currFrame->GetPrevSibling();
+ }
+
+ // No luck. Just return our start value.
+ return aStart;
+}
+
+bool
+nsXULPopupManager::IsValidMenuItem(nsIContent* aContent, bool aOnPopup)
+{
+ if (aContent->IsXULElement()) {
+ if (!aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
+ return false;
+ }
+ }
+ else if (!aOnPopup || !aContent->IsHTMLElement(nsGkAtoms::option)) {
+ return false;
+ }
+
+ nsMenuFrame* menuFrame = do_QueryFrame(aContent->GetPrimaryFrame());
+
+ bool skipNavigatingDisabledMenuItem = true;
+ if (aOnPopup && (!menuFrame || menuFrame->GetParentMenuListType() == eNotMenuList)) {
+ skipNavigatingDisabledMenuItem =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
+ 0) != 0;
+ }
+
+ return !(skipNavigatingDisabledMenuItem &&
+ aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+nsresult
+nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
+
+ //handlers shouldn't be triggered by non-trusted events.
+ bool trustedEvent = false;
+ aEvent->GetIsTrusted(&trustedEvent);
+ if (!trustedEvent) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("keyup")) {
+ return KeyUp(keyEvent);
+ }
+ if (eventType.EqualsLiteral("keydown")) {
+ return KeyDown(keyEvent);
+ }
+ if (eventType.EqualsLiteral("keypress")) {
+ return KeyPress(keyEvent);
+ }
+
+ NS_ABORT();
+
+ return NS_OK;
+}
+
+nsresult
+nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent)
+{
+ // don't do anything if a menu isn't open or a menubar isn't active
+ if (!mActiveMenuBar) {
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (!item || item->PopupType() != ePopupTypeMenu)
+ return NS_OK;
+
+ if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) {
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ return NS_OK;
+ }
+ }
+
+ aKeyEvent->AsEvent()->StopPropagation();
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ aKeyEvent->AsEvent()->PreventDefault();
+
+ return NS_OK; // I am consuming event
+}
+
+nsresult
+nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
+{
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (item && item->Frame()->IsMenuLocked())
+ return NS_OK;
+
+ if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
+ return NS_OK;
+ }
+
+ // don't do anything if a menu isn't open or a menubar isn't active
+ if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
+ return NS_OK;
+
+ // Since a menu was open, stop propagation of the event to keep other event
+ // listeners from becoming confused.
+ if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) {
+ aKeyEvent->AsEvent()->StopPropagation();
+ }
+
+ int32_t menuAccessKey = -1;
+
+ // If the key just pressed is the access key (usually Alt),
+ // dismiss and unfocus the menu.
+
+ nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
+ if (menuAccessKey) {
+ uint32_t theChar;
+ aKeyEvent->GetKeyCode(&theChar);
+
+ if (theChar == (uint32_t)menuAccessKey) {
+ bool ctrl = false;
+ if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
+ aKeyEvent->GetCtrlKey(&ctrl);
+ bool alt=false;
+ if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
+ aKeyEvent->GetAltKey(&alt);
+ bool shift=false;
+ if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
+ aKeyEvent->GetShiftKey(&shift);
+ bool meta=false;
+ if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META)
+ aKeyEvent->GetMetaKey(&meta);
+ if (!(ctrl || alt || shift || meta)) {
+ // The access key just went down and no other
+ // modifiers are already down.
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (mPopups && item && !item->Frame()->IsMenuList()) {
+ Rollup(0, false, nullptr, nullptr);
+ } else if (mActiveMenuBar) {
+ mActiveMenuBar->MenuClosed();
+ }
+
+ // Clear the item to avoid bugs as it may have been deleted during rollup.
+ item = nullptr;
+ }
+ aKeyEvent->AsEvent()->StopPropagation();
+ aKeyEvent->AsEvent()->PreventDefault();
+ }
+ }
+
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ return NS_OK;
+}
+
+nsresult
+nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
+{
+ // Don't check prevent default flag -- menus always get first shot at key events.
+
+ nsMenuChainItem* item = GetTopVisibleMenu();
+ if (item &&
+ (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+ NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
+ // if a menu is open or a menubar is active, it consumes the key event
+ bool consume = (mPopups || mActiveMenuBar);
+
+ WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
+ bool isAccel = evt && evt->IsAccel();
+
+ // When ignorekeys="shortcuts" is used, we don't call preventDefault on the
+ // key event when the accelerator key is pressed. This allows another
+ // listener to handle keys. For instance, this allows global shortcuts to
+ // still apply while a menu is open.
+ if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) {
+ consume = false;
+ }
+
+ HandleShortcutNavigation(keyEvent, nullptr);
+
+ aKeyEvent->AsEvent()->StopCrossProcessForwarding();
+ if (consume) {
+ aKeyEvent->AsEvent()->StopPropagation();
+ aKeyEvent->AsEvent()->PreventDefault();
+ }
+
+ return NS_OK; // I am consuming event
+}
+
+NS_IMETHODIMP
+nsXULPopupShowingEvent::Run()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULPopupHidingEvent::Run()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+
+ nsIDocument *document = mPopup->GetUncomposedDoc();
+ if (pm && document) {
+ nsIPresShell* presShell = document->GetShell();
+ if (presShell) {
+ nsPresContext* context = presShell->GetPresContext();
+ if (context) {
+ pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
+ context, mPopupType, mDeselectMenu, mIsRollup);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent *aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem)
+{
+ // The popuppositioned event only fires on arrow panels for now.
+ if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::arrow, eCaseMatters)) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem);
+ NS_DispatchToCurrentThread(event);
+
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsXULPopupPositionedEvent::Run()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
+ if (popupFrame) {
+ // At this point, hidePopup may have been called but it currently has no
+ // way to stop this event. However, if hidePopup was called, the popup
+ // will now be in the hiding or closed state. If we are in the shown or
+ // positioning state instead, we can assume that we are still clear to
+ // open/move the popup
+ nsPopupState state = popupFrame->PopupState();
+ if (state != ePopupPositioning && state != ePopupShown) {
+ return NS_OK;
+ }
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupPositioned, nullptr,
+ WidgetMouseEvent::eReal);
+ EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(),
+ &event, nullptr, &status);
+
+ // Get the popup frame and make sure it is still in the positioning
+ // state. If it isn't, someone may have tried to reshow or hide it
+ // during the popuppositioned event.
+ // Alternately, this event may have been fired in reponse to moving the
+ // popup rather than opening it. In that case, we are done.
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
+ if (popupFrame && popupFrame->PopupState() == ePopupPositioning) {
+ pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu, mSelectFirstItem);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULMenuCommandEvent::Run()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm)
+ return NS_OK;
+
+ // The order of the nsViewManager and nsIPresShell COM pointers is
+ // important below. We want the pres shell to get released before the
+ // associated view manager on exit from this function.
+ // See bug 54233.
+ // XXXndeakin is this still needed?
+
+ nsCOMPtr<nsIContent> popup;
+ nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
+ nsWeakFrame weakFrame(menuFrame);
+ if (menuFrame && mFlipChecked) {
+ if (menuFrame->IsChecked()) {
+ mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
+ } else {
+ mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ NS_LITERAL_STRING("true"), true);
+ }
+ }
+
+ if (menuFrame && weakFrame.IsAlive()) {
+ // Find the popup that the menu is inside. Below, this popup will
+ // need to be hidden.
+ nsIFrame* frame = menuFrame->GetParent();
+ while (frame) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+ if (popupFrame) {
+ popup = popupFrame->GetContent();
+ break;
+ }
+ frame = frame->GetParent();
+ }
+
+ nsPresContext* presContext = menuFrame->PresContext();
+ nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
+ RefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
+ mozilla::Unused << kungFuDeathGrip; // Not referred to directly within this function
+
+ // Deselect ourselves.
+ if (mCloseMenuMode != CloseMenuMode_None)
+ menuFrame->SelectMenu(false);
+
+ AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
+ shell->GetDocument());
+ nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
+ mControl, mAlt, mShift, mMeta);
+ }
+
+ if (popup && mCloseMenuMode != CloseMenuMode_None)
+ pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false);
+
+ return NS_OK;
+}
diff --git a/layout/xul/nsXULPopupManager.h b/layout/xul/nsXULPopupManager.h
new file mode 100644
index 000000000..41644d7e9
--- /dev/null
+++ b/layout/xul/nsXULPopupManager.h
@@ -0,0 +1,823 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The XUL Popup Manager keeps track of all open popups.
+ */
+
+#ifndef nsXULPopupManager_h__
+#define nsXULPopupManager_h__
+
+#include "mozilla/Logging.h"
+#include "nsIContent.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMEventListener.h"
+#include "nsPoint.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsIReflowCallback.h"
+#include "nsThreadUtils.h"
+#include "nsStyleConsts.h"
+#include "nsWidgetInitData.h"
+#include "mozilla/Attributes.h"
+#include "Units.h"
+
+// X.h defines KeyPress
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+/**
+ * There are two types that are used:
+ * - dismissable popups such as menus, which should close up when there is a
+ * click outside the popup. In this situation, the entire chain of menus
+ * above should also be closed.
+ * - panels, which stay open until a request is made to close them. This
+ * type is used by tooltips.
+ *
+ * When a new popup is opened, it is appended to the popup chain, stored in a
+ * linked list in mPopups for dismissable menus and panels or mNoHidePanels
+ * for tooltips and panels with noautohide="true".
+ * Popups are stored in this list linked from newest to oldest. When a click
+ * occurs outside one of the open dismissable popups, the chain is closed by
+ * calling Rollup.
+ */
+
+class nsContainerFrame;
+class nsMenuFrame;
+class nsMenuPopupFrame;
+class nsMenuBarFrame;
+class nsMenuParent;
+class nsIDOMKeyEvent;
+class nsIDocShellTreeItem;
+class nsPIDOMWindowOuter;
+
+// when a menu command is executed, the closemenu attribute may be used
+// to define how the menu should be closed up
+enum CloseMenuMode {
+ CloseMenuMode_Auto, // close up the chain of menus, default value
+ CloseMenuMode_None, // don't close up any menus
+ CloseMenuMode_Single // close up only the menu the command is inside
+};
+
+/**
+ * nsNavigationDirection: an enum expressing navigation through the menus in
+ * terms which are independent of the directionality of the chrome. The
+ * terminology, derived from XSL-FO and CSS3 (e.g.
+ * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start,
+ * End), with the addition of First and Last (mapped to Home and End
+ * respectively).
+ *
+ * In languages such as English where the inline progression is left-to-right
+ * and the block progression is top-to-bottom (lr-tb), these terms will map out
+ * as in the following diagram
+ *
+ * --- inline progression --->
+ *
+ * First |
+ * ... |
+ * Before |
+ * +--------+ block
+ * Start | | End progression
+ * +--------+ |
+ * After |
+ * ... |
+ * Last V
+ *
+ */
+
+enum nsNavigationDirection {
+ eNavigationDirection_Last,
+ eNavigationDirection_First,
+ eNavigationDirection_Start,
+ eNavigationDirection_Before,
+ eNavigationDirection_End,
+ eNavigationDirection_After
+};
+
+enum nsIgnoreKeys {
+ eIgnoreKeys_False,
+ eIgnoreKeys_True,
+ eIgnoreKeys_Shortcuts,
+};
+
+#define NS_DIRECTION_IS_INLINE(dir) (dir == eNavigationDirection_Start || \
+ dir == eNavigationDirection_End)
+#define NS_DIRECTION_IS_BLOCK(dir) (dir == eNavigationDirection_Before || \
+ dir == eNavigationDirection_After)
+#define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) (dir == eNavigationDirection_First || \
+ dir == eNavigationDirection_Last)
+
+static_assert(NS_STYLE_DIRECTION_LTR == 0 && NS_STYLE_DIRECTION_RTL == 1,
+ "Left to Right should be 0 and Right to Left should be 1");
+
+/**
+ * DirectionFromKeyCodeTable: two arrays, the first for left-to-right and the
+ * other for right-to-left, that map keycodes to values of
+ * nsNavigationDirection.
+ */
+extern const nsNavigationDirection DirectionFromKeyCodeTable[2][6];
+
+#define NS_DIRECTION_FROM_KEY_CODE(frame, keycode) \
+ (DirectionFromKeyCodeTable[frame->StyleVisibility()->mDirection] \
+ [keycode - nsIDOMKeyEvent::DOM_VK_END])
+
+// nsMenuChainItem holds info about an open popup. Items are stored in a
+// doubly linked list. Note that the linked list is stored beginning from
+// the lowest child in a chain of menus, as this is the active submenu.
+class nsMenuChainItem
+{
+private:
+ nsMenuPopupFrame* mFrame; // the popup frame
+ nsPopupType mPopupType; // the popup type of the frame
+ bool mIsContext; // true for context menus
+ bool mOnMenuBar; // true if the menu is on a menu bar
+ nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used
+
+ nsMenuChainItem* mParent;
+ nsMenuChainItem* mChild;
+
+public:
+ nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType)
+ : mFrame(aFrame),
+ mPopupType(aPopupType),
+ mIsContext(aIsContext),
+ mOnMenuBar(false),
+ mIgnoreKeys(eIgnoreKeys_False),
+ mParent(nullptr),
+ mChild(nullptr)
+ {
+ NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor");
+ MOZ_COUNT_CTOR(nsMenuChainItem);
+ }
+
+ ~nsMenuChainItem()
+ {
+ MOZ_COUNT_DTOR(nsMenuChainItem);
+ }
+
+ nsIContent* Content();
+ nsMenuPopupFrame* Frame() { return mFrame; }
+ nsPopupType PopupType() { return mPopupType; }
+ bool IsMenu() { return mPopupType == ePopupTypeMenu; }
+ bool IsContextMenu() { return mIsContext; }
+ nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; }
+ void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; }
+ bool IsOnMenuBar() { return mOnMenuBar; }
+ void SetOnMenuBar(bool aOnMenuBar) { mOnMenuBar = aOnMenuBar; }
+ nsMenuChainItem* GetParent() { return mParent; }
+ nsMenuChainItem* GetChild() { return mChild; }
+
+ // set the parent of this item to aParent, also changing the parent
+ // to have this as a child.
+ void SetParent(nsMenuChainItem* aParent);
+
+ // removes an item from the chain. The root pointer must be supplied in case
+ // the item is the first item in the chain in which case the pointer will be
+ // set to the next item, or null if there isn't another item. After detaching,
+ // this item will not have a parent or a child.
+ void Detach(nsMenuChainItem** aRoot);
+};
+
+// this class is used for dispatching popupshowing events asynchronously.
+class nsXULPopupShowingEvent : public mozilla::Runnable
+{
+public:
+ nsXULPopupShowingEvent(nsIContent *aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem)
+ : mPopup(aPopup),
+ mIsContextMenu(aIsContextMenu),
+ mSelectFirstItem(aSelectFirstItem)
+ {
+ NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor");
+ }
+
+ NS_IMETHOD Run() override;
+
+private:
+ nsCOMPtr<nsIContent> mPopup;
+ bool mIsContextMenu;
+ bool mSelectFirstItem;
+};
+
+// this class is used for dispatching popuphiding events asynchronously.
+class nsXULPopupHidingEvent : public mozilla::Runnable
+{
+public:
+ nsXULPopupHidingEvent(nsIContent *aPopup,
+ nsIContent* aNextPopup,
+ nsIContent* aLastPopup,
+ nsPopupType aPopupType,
+ bool aDeselectMenu,
+ bool aIsCancel)
+ : mPopup(aPopup),
+ mNextPopup(aNextPopup),
+ mLastPopup(aLastPopup),
+ mPopupType(aPopupType),
+ mDeselectMenu(aDeselectMenu),
+ mIsRollup(aIsCancel)
+ {
+ NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor");
+ // aNextPopup and aLastPopup may be null
+ }
+
+ NS_IMETHOD Run() override;
+
+private:
+ nsCOMPtr<nsIContent> mPopup;
+ nsCOMPtr<nsIContent> mNextPopup;
+ nsCOMPtr<nsIContent> mLastPopup;
+ nsPopupType mPopupType;
+ bool mDeselectMenu;
+ bool mIsRollup;
+};
+
+// this class is used for dispatching popuppositioned events asynchronously.
+class nsXULPopupPositionedEvent : public mozilla::Runnable
+{
+public:
+ explicit nsXULPopupPositionedEvent(nsIContent *aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem)
+ : mPopup(aPopup)
+ , mIsContextMenu(aIsContextMenu)
+ , mSelectFirstItem(aSelectFirstItem)
+ {
+ NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor");
+ }
+
+ NS_IMETHOD Run() override;
+
+ // Asynchronously dispatch a popuppositioned event at aPopup if this is a
+ // panel that should receieve such events. Return true if the event was sent.
+ static bool DispatchIfNeeded(nsIContent *aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem);
+
+private:
+ nsCOMPtr<nsIContent> mPopup;
+ bool mIsContextMenu;
+ bool mSelectFirstItem;
+};
+
+// this class is used for dispatching menu command events asynchronously.
+class nsXULMenuCommandEvent : public mozilla::Runnable
+{
+public:
+ nsXULMenuCommandEvent(nsIContent *aMenu,
+ bool aIsTrusted,
+ bool aShift,
+ bool aControl,
+ bool aAlt,
+ bool aMeta,
+ bool aUserInput,
+ bool aFlipChecked)
+ : mMenu(aMenu),
+ mIsTrusted(aIsTrusted),
+ mShift(aShift),
+ mControl(aControl),
+ mAlt(aAlt),
+ mMeta(aMeta),
+ mUserInput(aUserInput),
+ mFlipChecked(aFlipChecked),
+ mCloseMenuMode(CloseMenuMode_Auto)
+ {
+ NS_ASSERTION(aMenu, "null menu supplied to nsXULMenuCommandEvent constructor");
+ }
+
+ NS_IMETHOD Run() override;
+
+ void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { mCloseMenuMode = aCloseMenuMode; }
+
+private:
+ nsCOMPtr<nsIContent> mMenu;
+ bool mIsTrusted;
+ bool mShift;
+ bool mControl;
+ bool mAlt;
+ bool mMeta;
+ bool mUserInput;
+ bool mFlipChecked;
+ CloseMenuMode mCloseMenuMode;
+};
+
+class nsXULPopupManager final : public nsIDOMEventListener,
+ public nsIRollupListener,
+ public nsITimerCallback,
+ public nsIObserver
+{
+
+public:
+ friend class nsXULPopupShowingEvent;
+ friend class nsXULPopupHidingEvent;
+ friend class nsXULPopupPositionedEvent;
+ friend class nsXULMenuCommandEvent;
+ friend class TransitionEnder;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ // nsIRollupListener
+ virtual bool Rollup(uint32_t aCount, bool aFlush,
+ const nsIntPoint* pos, nsIContent** aLastRolledUp) override;
+ virtual bool ShouldRollupOnMouseWheelEvent() override;
+ virtual bool ShouldConsumeOnMouseWheelEvent() override;
+ virtual bool ShouldRollupOnMouseActivate() override;
+ virtual uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) override;
+ virtual void NotifyGeometryChange() override {}
+ virtual nsIWidget* GetRollupWidget() override;
+
+ static nsXULPopupManager* sInstance;
+
+ // initialize and shutdown methods called by nsLayoutStatics
+ static nsresult Init();
+ static void Shutdown();
+
+ // returns a weak reference to the popup manager instance, could return null
+ // if a popup manager could not be allocated
+ static nsXULPopupManager* GetInstance();
+
+ // This should be called when a window is moved or resized to adjust the
+ // popups accordingly.
+ void AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow);
+ void AdjustPopupsOnWindowChange(nsIPresShell* aPresShell);
+
+ // given a menu frame, find the prevous or next menu frame. If aPopup is
+ // true then navigate a menupopup, from one item on the menu to the previous
+ // or next one. This is used for cursor navigation between items in a popup
+ // menu. If aIsPopup is false, the navigation is on a menubar, so navigate
+ // between menus on the menubar. This is used for left/right cursor navigation.
+ //
+ // Items that are not valid, such as non-menu or non-menuitem elements are
+ // skipped, and the next or previous item after that is checked.
+ //
+ // If aStart is null, the first valid item is retrieved by GetNextMenuItem
+ // and the last valid item is retrieved by GetPreviousMenuItem.
+ //
+ // Both methods will loop around the beginning or end if needed.
+ //
+ // aParent - the parent menubar or menupopup
+ // aStart - the menu/menuitem to start navigation from. GetPreviousMenuItem
+ // returns the item before it, while GetNextMenuItem returns the
+ // item after it.
+ // aIsPopup - true for menupopups, false for menubars
+ static nsMenuFrame* GetPreviousMenuItem(nsContainerFrame* aParent,
+ nsMenuFrame* aStart,
+ bool aIsPopup);
+ static nsMenuFrame* GetNextMenuItem(nsContainerFrame* aParent,
+ nsMenuFrame* aStart,
+ bool aIsPopup);
+
+ // returns true if the menu item aContent is a valid menuitem which may
+ // be navigated to. aIsPopup should be true for items on a popup, or false
+ // for items on a menubar.
+ static bool IsValidMenuItem(nsIContent* aContent, bool aOnPopup);
+
+ // inform the popup manager that a menu bar has been activated or deactivated,
+ // either because one of its menus has opened or closed, or that the menubar
+ // has been focused such that its menus may be navigated with the keyboard.
+ // aActivate should be true when the menubar should be focused, and false
+ // when the active menu bar should be defocused. In the latter case, if
+ // aMenuBar isn't currently active, yet another menu bar is, that menu bar
+ // will remain active.
+ void SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate);
+
+ // retrieve the node and offset of the last mouse event used to open a
+ // context menu. This information is determined from the rangeParent and
+ // the rangeOffset of the event supplied to ShowPopup or ShowPopupAtScreen.
+ // This is used by the implementation of nsIDOMXULDocument::GetPopupRangeParent
+ // and nsIDOMXULDocument::GetPopupRangeOffset.
+ void GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset);
+
+ /**
+ * Open a <menu> given its content node. If aSelectFirstItem is
+ * set to true, the first item on the menu will automatically be
+ * selected. If aAsynchronous is true, the event will be dispatched
+ * asynchronously. This should be true when called from frame code.
+ */
+ void ShowMenu(nsIContent *aMenu, bool aSelectFirstItem, bool aAsynchronous);
+
+ /**
+ * Open a popup, either anchored or unanchored. If aSelectFirstItem is
+ * true, then the first item in the menu is selected. The arguments are
+ * similar to those for nsIPopupBoxObject::OpenPopup.
+ *
+ * aTriggerEvent should be the event that triggered the event. This is used
+ * to determine the coordinates and trigger node for the popup. This may be
+ * null if the popup was not triggered by an event.
+ *
+ * This fires the popupshowing event synchronously.
+ */
+ void ShowPopup(nsIContent* aPopup,
+ nsIContent* aAnchorContent,
+ const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ bool aSelectFirstItem,
+ nsIDOMEvent* aTriggerEvent);
+
+ /**
+ * Open a popup at a specific screen position specified by aXPos and aYPos,
+ * measured in CSS pixels.
+ *
+ * This fires the popupshowing event synchronously.
+ *
+ * If aIsContextMenu is true, the popup is positioned at a slight
+ * offset from aXPos/aYPos to ensure that it is not under the mouse
+ * cursor.
+ */
+ void ShowPopupAtScreen(nsIContent* aPopup,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ nsIDOMEvent* aTriggerEvent);
+
+ /* Open a popup anchored at a screen rectangle specified by aRect.
+ * The remaining arguments are similar to ShowPopup.
+ */
+ void ShowPopupAtScreenRect(nsIContent* aPopup,
+ const nsAString& aPosition,
+ const nsIntRect& aRect,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ nsIDOMEvent* aTriggerEvent);
+
+ /**
+ * Open a tooltip at a specific screen position specified by aXPos and aYPos,
+ * measured in CSS pixels.
+ *
+ * This fires the popupshowing event synchronously.
+ */
+ void ShowTooltipAtScreen(nsIContent* aPopup,
+ nsIContent* aTriggerContent,
+ int32_t aXPos, int32_t aYPos);
+
+ /**
+ * This method is provided only for compatibility with an older popup API.
+ * New code should not call this function and should call ShowPopup instead.
+ *
+ * This fires the popupshowing event synchronously.
+ */
+ void ShowPopupWithAnchorAlign(nsIContent* aPopup,
+ nsIContent* aAnchorContent,
+ nsAString& aAnchor,
+ nsAString& aAlign,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu);
+
+ /*
+ * Hide a popup aPopup. If the popup is in a <menu>, then also inform the
+ * menu that the popup is being hidden.
+ *
+ * aHideChain - true if the entire chain of menus should be closed. If false,
+ * only this popup is closed.
+ * aDeselectMenu - true if the parent <menu> of the popup should be deselected.
+ * This will be false when the menu is closed by pressing the
+ * Escape key.
+ * aAsynchronous - true if the first popuphiding event should be sent
+ * asynchrously. This should be true if HidePopup is called
+ * from a frame.
+ * aIsCancel - true if this popup is hiding due to being cancelled.
+ * aLastPopup - optional popup to close last when hiding a chain of menus.
+ * If null, then all popups will be closed.
+ */
+ void HidePopup(nsIContent* aPopup,
+ bool aHideChain,
+ bool aDeselectMenu,
+ bool aAsynchronous,
+ bool aIsCancel,
+ nsIContent* aLastPopup = nullptr);
+
+ /**
+ * Hide the popup aFrame. This method is called by the view manager when the
+ * close button is pressed.
+ */
+ void HidePopup(nsIFrame* aFrame);
+
+ /**
+ * Hide a popup after a short delay. This is used when rolling over menu items.
+ * This timer is stored in mCloseTimer. The timer may be cancelled and the popup
+ * closed by calling KillMenuTimer.
+ */
+ void HidePopupAfterDelay(nsMenuPopupFrame* aPopup);
+
+ /**
+ * Hide all of the popups from a given docshell. This should be called when the
+ * document is hidden.
+ */
+ void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide);
+
+ /**
+ * Enable or disable the dynamic noautohide state of a panel.
+ *
+ * aPanel - the panel whose state is to change
+ * aShouldRollup - whether the panel is no longer noautohide
+ */
+ void EnableRollup(nsIContent* aPopup, bool aShouldRollup);
+
+ /**
+ * Execute a menu command from the triggering event aEvent.
+ *
+ * aMenu - a menuitem to execute
+ * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse
+ * event which triggered the menu to be executed, may not be null
+ */
+ void ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent);
+
+ /**
+ * Return true if the popup for the supplied content node is open.
+ */
+ bool IsPopupOpen(nsIContent* aPopup);
+
+ /**
+ * Return true if the popup for the supplied menu parent is open.
+ */
+ bool IsPopupOpenForMenuParent(nsMenuParent* aMenuParent);
+
+ /**
+ * Return the frame for the topmost open popup of a given type, or null if
+ * no popup of that type is open. If aType is ePopupTypeAny, a menu of any
+ * type is returned, except for popups in the mNoHidePanels list.
+ */
+ nsIFrame* GetTopPopup(nsPopupType aType);
+
+ /**
+ * Return an array of all the open and visible popup frames for
+ * menus, in order from top to bottom.
+ */
+ void GetVisiblePopups(nsTArray<nsIFrame *>& aPopups);
+
+ /**
+ * Get the node that last triggered a popup or tooltip in the document
+ * aDocument. aDocument must be non-null and be a document contained within
+ * the same window hierarchy as the popup to retrieve.
+ */
+ already_AddRefed<nsIDOMNode> GetLastTriggerPopupNode(nsIDocument* aDocument)
+ {
+ return GetLastTriggerNode(aDocument, false);
+ }
+
+ already_AddRefed<nsIDOMNode> GetLastTriggerTooltipNode(nsIDocument* aDocument)
+ {
+ return GetLastTriggerNode(aDocument, true);
+ }
+
+ /**
+ * Return false if a popup may not be opened. This will return false if the
+ * popup is already open, if the popup is in a content shell that is not
+ * focused, or if it is a submenu of another menu that isn't open.
+ */
+ bool MayShowPopup(nsMenuPopupFrame* aFrame);
+
+ /**
+ * Indicate that the popup associated with aView has been moved to the
+ * specified screen coordiates.
+ */
+ void PopupMoved(nsIFrame* aFrame, nsIntPoint aPoint);
+
+ /**
+ * Indicate that the popup associated with aView has been resized to the
+ * given device pixel size aSize.
+ */
+ void PopupResized(nsIFrame* aFrame, mozilla::LayoutDeviceIntSize aSize);
+
+ /**
+ * Called when a popup frame is destroyed. In this case, just remove the
+ * item and later popups from the list. No point going through HidePopup as
+ * the frames have gone away.
+ */
+ void PopupDestroyed(nsMenuPopupFrame* aFrame);
+
+ /**
+ * Returns true if there is a context menu open. If aPopup is specified,
+ * then the context menu must be later in the chain than aPopup. If aPopup
+ * is null, returns true if any context menu at all is open.
+ */
+ bool HasContextMenu(nsMenuPopupFrame* aPopup);
+
+ /**
+ * Update the commands for the menus within the menu popup for a given
+ * content node. aPopup should be a XUL menupopup element. This method
+ * changes attributes on the children of aPopup, and deals only with the
+ * content of the popup, not the frames.
+ */
+ void UpdateMenuItems(nsIContent* aPopup);
+
+ /**
+ * Stop the timer which hides a popup after a delay, started by a previous
+ * call to HidePopupAfterDelay. In addition, the popup awaiting to be hidden
+ * is closed asynchronously.
+ */
+ void KillMenuTimer();
+
+ /**
+ * Cancel the timer which closes menus after delay, but only if the menu to
+ * close is aMenuParent. When a submenu is opened, the user might move the
+ * mouse over a sibling menuitem which would normally close the menu. This
+ * menu is closed via a timer. However, if the user moves the mouse over the
+ * submenu before the timer fires, we should instead cancel the timer. This
+ * ensures that the user can move the mouse diagonally over a menu.
+ */
+ void CancelMenuTimer(nsMenuParent* aMenuParent);
+
+ /**
+ * Handles navigation for menu accelkeys. If aFrame is specified, then the
+ * key is handled by that popup, otherwise if aFrame is null, the key is
+ * handled by the active popup or menubar.
+ */
+ bool HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
+ nsMenuPopupFrame* aFrame);
+
+ /**
+ * Handles cursor navigation within a menu. Returns true if the key has
+ * been handled.
+ */
+ bool HandleKeyboardNavigation(uint32_t aKeyCode);
+
+ /**
+ * Handle keyboard navigation within a menu popup specified by aFrame.
+ * Returns true if the key was handled and other default handling
+ * should not occur.
+ */
+ bool HandleKeyboardNavigationInPopup(nsMenuPopupFrame* aFrame,
+ nsNavigationDirection aDir)
+ {
+ return HandleKeyboardNavigationInPopup(nullptr, aFrame, aDir);
+ }
+
+ /**
+ * Handles the keyboard event with keyCode value. Returns true if the event
+ * has been handled.
+ */
+ bool HandleKeyboardEventWithKeyCode(nsIDOMKeyEvent* aKeyEvent,
+ nsMenuChainItem* aTopVisibleMenuItem);
+
+ nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent);
+ nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent);
+ nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent);
+
+protected:
+ nsXULPopupManager();
+ ~nsXULPopupManager();
+
+ // get the nsMenuPopupFrame, if any, for the given content node
+ nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush);
+
+ // return the topmost menu, skipping over invisible popups
+ nsMenuChainItem* GetTopVisibleMenu();
+
+ // Hide all of the visible popups from the given list. This function can
+ // cause style changes and frame destruction.
+ void HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames);
+
+ // set the event that was used to trigger the popup, or null to clear the
+ // event details. aTriggerContent will be set to the target of the event.
+ void InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, nsIContent** aTriggerContent);
+
+ // callbacks for ShowPopup and HidePopup as events may be done asynchronously
+ void ShowPopupCallback(nsIContent* aPopup,
+ nsMenuPopupFrame* aPopupFrame,
+ bool aIsContextMenu,
+ bool aSelectFirstItem);
+ void HidePopupCallback(nsIContent* aPopup,
+ nsMenuPopupFrame* aPopupFrame,
+ nsIContent* aNextPopup,
+ nsIContent* aLastPopup,
+ nsPopupType aPopupType,
+ bool aDeselectMenu);
+
+ /**
+ * Fire a popupshowing event on the popup and then open the popup.
+ *
+ * aPopup - the popup to open
+ * aIsContextMenu - true for context menus
+ * aSelectFirstItem - true to select the first item in the menu
+ */
+ void FirePopupShowingEvent(nsIContent* aPopup,
+ bool aIsContextMenu,
+ bool aSelectFirstItem);
+
+ /**
+ * Fire a popuphiding event and then hide the popup. This will be called
+ * recursively if aNextPopup and aLastPopup are set in order to hide a chain
+ * of open menus. If these are not set, only one popup is closed. However,
+ * if the popup type indicates a menu, yet the next popup is not a menu,
+ * then this ends the closing of popups. This allows a menulist inside a
+ * non-menu to close up the menu but not close up the panel it is contained
+ * within.
+ *
+ * The caller must keep a strong reference to aPopup, aNextPopup and aLastPopup.
+ *
+ * aPopup - the popup to hide
+ * aNextPopup - the next popup to hide
+ * aLastPopup - the last popup in the chain to hide
+ * aPresContext - nsPresContext for the popup's frame
+ * aPopupType - the PopupType of the frame.
+ * aDeselectMenu - true to unhighlight the menu when hiding it
+ * aIsCancel - true if this popup is hiding due to being cancelled.
+ */
+ void FirePopupHidingEvent(nsIContent* aPopup,
+ nsIContent* aNextPopup,
+ nsIContent* aLastPopup,
+ nsPresContext *aPresContext,
+ nsPopupType aPopupType,
+ bool aDeselectMenu,
+ bool aIsCancel);
+
+ /**
+ * Handle keyboard navigation within a menu popup specified by aItem.
+ */
+ bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem,
+ nsNavigationDirection aDir)
+ {
+ return HandleKeyboardNavigationInPopup(aItem, aItem->Frame(), aDir);
+ }
+
+private:
+ /**
+ * Handle keyboard navigation within a menu popup aFrame. If aItem is
+ * supplied, then it is expected to have a frame equal to aFrame.
+ * If aItem is non-null, then the navigation may be redirected to
+ * an open submenu if one exists. Returns true if the key was
+ * handled and other default handling should not occur.
+ */
+ bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem,
+ nsMenuPopupFrame* aFrame,
+ nsNavigationDirection aDir);
+
+protected:
+
+ already_AddRefed<nsIDOMNode> GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip);
+
+ /**
+ * Set mouse capturing for the current popup. This traps mouse clicks that
+ * occur outside the popup so that it can be closed up. aOldPopup should be
+ * set to the popup that was previously the current popup.
+ */
+ void SetCaptureState(nsIContent *aOldPopup);
+
+ /**
+ * Key event listeners are attached to the document containing the current
+ * menu for menu and shortcut navigation. Only one listener is needed at a
+ * time, stored in mKeyListener, so switch it only if the document changes.
+ * Having menus in different documents is very rare, so the listeners will
+ * usually only be attached when the first menu opens and removed when all
+ * menus have closed.
+ *
+ * This is also used when only a menubar is active without any open menus,
+ * so that keyboard navigation between menus on the menubar may be done.
+ */
+ void UpdateKeyboardListeners();
+
+ /*
+ * Returns true if the docshell for aDoc is aExpected or a child of aExpected.
+ */
+ bool IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected);
+
+ // the document the key event listener is attached to
+ nsCOMPtr<mozilla::dom::EventTarget> mKeyListener;
+
+ // widget that is currently listening to rollup events
+ nsCOMPtr<nsIWidget> mWidget;
+
+ // range parent and offset set in SetTriggerEvent
+ nsCOMPtr<nsIDOMNode> mRangeParent;
+ int32_t mRangeOffset;
+ // Device pixels relative to the showing popup's presshell's
+ // root prescontext's root frame.
+ mozilla::LayoutDeviceIntPoint mCachedMousePoint;
+
+ // cached modifiers
+ mozilla::Modifiers mCachedModifiers;
+
+ // set to the currently active menu bar, if any
+ nsMenuBarFrame* mActiveMenuBar;
+
+ // linked list of normal menus and panels.
+ nsMenuChainItem* mPopups;
+
+ // linked list of noautohide panels and tooltips.
+ nsMenuChainItem* mNoHidePanels;
+
+ // timer used for HidePopupAfterDelay
+ nsCOMPtr<nsITimer> mCloseTimer;
+
+ // a popup that is waiting on the timer
+ nsMenuPopupFrame* mTimerMenu;
+
+ // the popup that is currently being opened, stored only during the
+ // popupshowing event
+ nsCOMPtr<nsIContent> mOpeningPopup;
+
+ // If true, all popups won't hide automatically on blur
+ static bool sDevtoolsDisableAutoHide;
+};
+
+#endif
diff --git a/layout/xul/nsXULTooltipListener.cpp b/layout/xul/nsXULTooltipListener.cpp
new file mode 100644
index 000000000..110f8c0e0
--- /dev/null
+++ b/layout/xul/nsXULTooltipListener.cpp
@@ -0,0 +1,729 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULTooltipListener.h"
+
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocument.h"
+#include "nsGkAtoms.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIServiceManager.h"
+#include "nsIDragService.h"
+#include "nsIDragSession.h"
+#ifdef MOZ_XUL
+#include "nsITreeView.h"
+#endif
+#include "nsIScriptContext.h"
+#include "nsPIDOMWindow.h"
+#ifdef MOZ_XUL
+#include "nsXULPopupManager.h"
+#endif
+#include "nsIRootBox.h"
+#include "nsIBoxObject.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "mozilla/dom/BoxObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXULTooltipListener* nsXULTooltipListener::mInstance = nullptr;
+
+//////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+nsXULTooltipListener::nsXULTooltipListener()
+ : mMouseScreenX(0)
+ , mMouseScreenY(0)
+ , mTooltipShownOnce(false)
+#ifdef MOZ_XUL
+ , mIsSourceTree(false)
+ , mNeedTitletip(false)
+ , mLastTreeRow(-1)
+#endif
+{
+ if (sTooltipListenerCount++ == 0) {
+ // register the callback so we get notified of updates
+ Preferences::RegisterCallback(ToolbarTipsPrefChanged,
+ "browser.chrome.toolbar_tips");
+
+ // Call the pref callback to initialize our state.
+ ToolbarTipsPrefChanged("browser.chrome.toolbar_tips", nullptr);
+ }
+}
+
+nsXULTooltipListener::~nsXULTooltipListener()
+{
+ if (nsXULTooltipListener::mInstance == this) {
+ ClearTooltipCache();
+ }
+ HideTooltip();
+
+ if (--sTooltipListenerCount == 0) {
+ // Unregister our pref observer
+ Preferences::UnregisterCallback(ToolbarTipsPrefChanged,
+ "browser.chrome.toolbar_tips");
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsXULTooltipListener, nsIDOMEventListener)
+
+void
+nsXULTooltipListener::MouseOut(nsIDOMEvent* aEvent)
+{
+ // reset flag so that tooltip will display on the next MouseMove
+ mTooltipShownOnce = false;
+
+ // if the timer is running and no tooltip is shown, we
+ // have to cancel the timer here so that it doesn't
+ // show the tooltip if we move the mouse out of the window
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (mTooltipTimer && !currentTooltip) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ return;
+ }
+
+#ifdef DEBUG_crap
+ if (mNeedTitletip)
+ return;
+#endif
+
+#ifdef MOZ_XUL
+ // check to see if the mouse left the targetNode, and if so,
+ // hide the tooltip
+ if (currentTooltip) {
+ // which node did the mouse leave?
+ nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsCOMPtr<nsIDOMNode> tooltipNode =
+ pm->GetLastTriggerTooltipNode(currentTooltip->GetUncomposedDoc());
+ if (tooltipNode == targetNode) {
+ // if the target node is the current tooltip target node, the mouse
+ // left the node the tooltip appeared on, so close the tooltip.
+ HideTooltip();
+ // reset special tree tracking
+ if (mIsSourceTree) {
+ mLastTreeRow = -1;
+ mLastTreeCol = nullptr;
+ }
+ }
+ }
+ }
+#endif
+}
+
+void
+nsXULTooltipListener::MouseMove(nsIDOMEvent* aEvent)
+{
+ if (!sShowTooltips)
+ return;
+
+ // stash the coordinates of the event so that we can still get back to it from within the
+ // timer callback. On win32, we'll get a MouseMove event even when a popup goes away --
+ // even when the mouse doesn't change position! To get around this, we make sure the
+ // mouse has really moved before proceeding.
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent));
+ if (!mouseEvent)
+ return;
+ int32_t newMouseX, newMouseY;
+ mouseEvent->GetScreenX(&newMouseX);
+ mouseEvent->GetScreenY(&newMouseY);
+
+ // filter out false win32 MouseMove event
+ if (mMouseScreenX == newMouseX && mMouseScreenY == newMouseY)
+ return;
+
+ // filter out minor movements due to crappy optical mice and shaky hands
+ // to prevent tooltips from hiding prematurely.
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+
+ if ((currentTooltip) &&
+ (abs(mMouseScreenX - newMouseX) <= kTooltipMouseMoveTolerance) &&
+ (abs(mMouseScreenY - newMouseY) <= kTooltipMouseMoveTolerance))
+ return;
+ mMouseScreenX = newMouseX;
+ mMouseScreenY = newMouseY;
+
+ nsCOMPtr<nsIContent> sourceContent = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetCurrentTarget());
+ mSourceNode = do_GetWeakReference(sourceContent);
+#ifdef MOZ_XUL
+ mIsSourceTree = sourceContent->IsXULElement(nsGkAtoms::treechildren);
+ if (mIsSourceTree)
+ CheckTreeBodyMove(mouseEvent);
+#endif
+
+ // as the mouse moves, we want to make sure we reset the timer to show it,
+ // so that the delay is from when the mouse stops moving, not when it enters
+ // the node.
+ KillTooltipTimer();
+
+ // If the mouse moves while the tooltip is up, hide it. If nothing is
+ // showing and the tooltip hasn't been displayed since the mouse entered
+ // the node, then start the timer to show the tooltip.
+ if (!currentTooltip && !mTooltipShownOnce) {
+ nsCOMPtr<EventTarget> eventTarget = aEvent->InternalDOMEvent()->GetTarget();
+
+ // don't show tooltips attached to elements outside of a menu popup
+ // when hovering over an element inside it. The popupsinherittooltip
+ // attribute may be used to disable this behaviour, which is useful for
+ // large menu hierarchies such as bookmarks.
+ if (!sourceContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::popupsinherittooltip,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsCOMPtr<nsIContent> targetContent = do_QueryInterface(eventTarget);
+ while (targetContent && targetContent != sourceContent) {
+ if (targetContent->IsAnyOfXULElements(nsGkAtoms::menupopup,
+ nsGkAtoms::panel,
+ nsGkAtoms::tooltip)) {
+ mSourceNode = nullptr;
+ return;
+ }
+
+ targetContent = targetContent->GetParent();
+ }
+ }
+
+ mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTooltipTimer) {
+ mTargetNode = do_GetWeakReference(eventTarget);
+ if (mTargetNode) {
+ nsresult rv =
+ mTooltipTimer->InitWithFuncCallback(sTooltipCallback, this,
+ LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500),
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ mTargetNode = nullptr;
+ mSourceNode = nullptr;
+ }
+ }
+ }
+ return;
+ }
+
+#ifdef MOZ_XUL
+ if (mIsSourceTree)
+ return;
+#endif
+
+ HideTooltip();
+ // set a flag so that the tooltip is only displayed once until the mouse
+ // leaves the node
+ mTooltipShownOnce = true;
+}
+
+NS_IMETHODIMP
+nsXULTooltipListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (type.EqualsLiteral("DOMMouseScroll") ||
+ type.EqualsLiteral("keydown") ||
+ type.EqualsLiteral("mousedown") ||
+ type.EqualsLiteral("mouseup") ||
+ type.EqualsLiteral("dragstart")) {
+ HideTooltip();
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("popuphiding")) {
+ DestroyTooltip();
+ return NS_OK;
+ }
+
+ // Note that mousemove, mouseover and mouseout might be
+ // fired even during dragging due to widget's bug.
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ NS_ENSURE_TRUE(dragService, NS_OK);
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (dragSession) {
+ return NS_OK;
+ }
+
+ // Not dragging.
+
+ if (type.EqualsLiteral("mousemove")) {
+ MouseMove(aEvent);
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("mouseout")) {
+ MouseOut(aEvent);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//// nsXULTooltipListener
+
+// static
+void
+nsXULTooltipListener::ToolbarTipsPrefChanged(const char *aPref,
+ void *aClosure)
+{
+ sShowTooltips =
+ Preferences::GetBool("browser.chrome.toolbar_tips", sShowTooltips);
+}
+
+//////////////////////////////////////////////////////////////////////////
+//// nsXULTooltipListener
+
+bool nsXULTooltipListener::sShowTooltips = false;
+uint32_t nsXULTooltipListener::sTooltipListenerCount = 0;
+
+nsresult
+nsXULTooltipListener::AddTooltipSupport(nsIContent* aNode)
+{
+ if (!aNode)
+ return NS_ERROR_NULL_POINTER;
+
+ aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), this,
+ false, false);
+ aNode->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), this,
+ false, false);
+ aNode->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), this,
+ false, false);
+ aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), this,
+ false, false);
+ aNode->AddSystemEventListener(NS_LITERAL_STRING("dragstart"), this,
+ true, false);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTooltipListener::RemoveTooltipSupport(nsIContent* aNode)
+{
+ if (!aNode)
+ return NS_ERROR_NULL_POINTER;
+
+ aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), this, false);
+ aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"), this, false);
+ aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this, false);
+ aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, false);
+ aNode->RemoveSystemEventListener(NS_LITERAL_STRING("dragstart"), this, true);
+
+ return NS_OK;
+}
+
+#ifdef MOZ_XUL
+void
+nsXULTooltipListener::CheckTreeBodyMove(nsIDOMMouseEvent* aMouseEvent)
+{
+ nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode);
+ if (!sourceNode)
+ return;
+
+ // get the boxObject of the documentElement of the document the tree is in
+ nsCOMPtr<nsIBoxObject> bx;
+ nsIDocument* doc = sourceNode->GetComposedDoc();
+ if (doc) {
+ ErrorResult ignored;
+ bx = doc->GetBoxObjectFor(doc->GetRootElement(), ignored);
+ }
+
+ nsCOMPtr<nsITreeBoxObject> obx;
+ GetSourceTreeBoxObject(getter_AddRefs(obx));
+ if (bx && obx) {
+ int32_t x, y;
+ aMouseEvent->GetScreenX(&x);
+ aMouseEvent->GetScreenY(&y);
+
+ int32_t row;
+ nsCOMPtr<nsITreeColumn> col;
+ nsAutoString obj;
+
+ // subtract off the documentElement's boxObject
+ int32_t boxX, boxY;
+ bx->GetScreenX(&boxX);
+ bx->GetScreenY(&boxY);
+ x -= boxX;
+ y -= boxY;
+
+ obx->GetCellAt(x, y, &row, getter_AddRefs(col), obj);
+
+ // determine if we are going to need a titletip
+ // XXX check the disabletitletips attribute on the tree content
+ mNeedTitletip = false;
+ int16_t colType = -1;
+ if (col) {
+ col->GetType(&colType);
+ }
+ if (row >= 0 && obj.EqualsLiteral("text") &&
+ colType != nsITreeColumn::TYPE_PASSWORD) {
+ obx->IsCellCropped(row, col, &mNeedTitletip);
+ }
+
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (currentTooltip && (row != mLastTreeRow || col != mLastTreeCol)) {
+ HideTooltip();
+ }
+
+ mLastTreeRow = row;
+ mLastTreeCol = col;
+ }
+}
+#endif
+
+nsresult
+nsXULTooltipListener::ShowTooltip()
+{
+ nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode);
+
+ // get the tooltip content designated for the target node
+ nsCOMPtr<nsIContent> tooltipNode;
+ GetTooltipFor(sourceNode, getter_AddRefs(tooltipNode));
+ if (!tooltipNode || sourceNode == tooltipNode)
+ return NS_ERROR_FAILURE; // the target node doesn't need a tooltip
+
+ // set the node in the document that triggered the tooltip and show it
+ nsCOMPtr<nsIDOMXULDocument> xulDoc =
+ do_QueryInterface(tooltipNode->GetComposedDoc());
+ if (xulDoc) {
+ // Make sure the target node is still attached to some document.
+ // It might have been deleted.
+ if (sourceNode->IsInComposedDoc()) {
+#ifdef MOZ_XUL
+ if (!mIsSourceTree) {
+ mLastTreeRow = -1;
+ mLastTreeCol = nullptr;
+ }
+#endif
+
+ mCurrentTooltip = do_GetWeakReference(tooltipNode);
+ LaunchTooltip();
+ mTargetNode = nullptr;
+
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (!currentTooltip)
+ return NS_OK;
+
+ // listen for popuphidden on the tooltip node, so that we can
+ // be sure DestroyPopup is called even if someone else closes the tooltip
+ currentTooltip->AddSystemEventListener(NS_LITERAL_STRING("popuphiding"),
+ this, false, false);
+
+ // listen for mousedown, mouseup, keydown, and DOMMouseScroll events at document level
+ nsIDocument* doc = sourceNode->GetComposedDoc();
+ if (doc) {
+ // Probably, we should listen to untrusted events for hiding tooltips
+ // on content since tooltips might disturb something of web
+ // applications. If we don't specify the aWantsUntrusted of
+ // AddSystemEventListener(), the event target sets it to TRUE if the
+ // target is in content.
+ doc->AddSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"),
+ this, true);
+ doc->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
+ this, true);
+ doc->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
+ this, true);
+ doc->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
+ this, true);
+ }
+ mSourceNode = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+#ifdef MOZ_XUL
+// XXX: "This stuff inside DEBUG_crap could be used to make tree tooltips work
+// in the future."
+#ifdef DEBUG_crap
+static void
+GetTreeCellCoords(nsITreeBoxObject* aTreeBox, nsIContent* aSourceNode,
+ int32_t aRow, nsITreeColumn* aCol, int32_t* aX, int32_t* aY)
+{
+ int32_t junk;
+ aTreeBox->GetCoordsForCellItem(aRow, aCol, EmptyCString(), aX, aY, &junk, &junk);
+ nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aSourceNode));
+ nsCOMPtr<nsIBoxObject> bx;
+ xulEl->GetBoxObject(getter_AddRefs(bx));
+ int32_t myX, myY;
+ bx->GetX(&myX);
+ bx->GetY(&myY);
+ *aX += myX;
+ *aY += myY;
+}
+#endif
+
+static void
+SetTitletipLabel(nsITreeBoxObject* aTreeBox, nsIContent* aTooltip,
+ int32_t aRow, nsITreeColumn* aCol)
+{
+ nsCOMPtr<nsITreeView> view;
+ aTreeBox->GetView(getter_AddRefs(view));
+ if (view) {
+ nsAutoString label;
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ view->GetCellText(aRow, aCol, label);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Couldn't get the cell text!");
+ aTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, true);
+ }
+}
+#endif
+
+void
+nsXULTooltipListener::LaunchTooltip()
+{
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (!currentTooltip)
+ return;
+
+#ifdef MOZ_XUL
+ if (mIsSourceTree && mNeedTitletip) {
+ nsCOMPtr<nsITreeBoxObject> obx;
+ GetSourceTreeBoxObject(getter_AddRefs(obx));
+
+ SetTitletipLabel(obx, currentTooltip, mLastTreeRow, mLastTreeCol);
+ if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) {
+ // Because of mutation events, currentTooltip can be null.
+ return;
+ }
+ currentTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::titletip, NS_LITERAL_STRING("true"), true);
+ } else {
+ currentTooltip->UnsetAttr(kNameSpaceID_None, nsGkAtoms::titletip, true);
+ }
+ if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) {
+ // Because of mutation events, currentTooltip can be null.
+ return;
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsCOMPtr<nsIContent> target = do_QueryReferent(mTargetNode);
+ pm->ShowTooltipAtScreen(currentTooltip, target, mMouseScreenX, mMouseScreenY);
+
+ // Clear the current tooltip if the popup was not opened successfully.
+ if (!pm->IsPopupOpen(currentTooltip))
+ mCurrentTooltip = nullptr;
+ }
+#endif
+
+}
+
+nsresult
+nsXULTooltipListener::HideTooltip()
+{
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (currentTooltip) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->HidePopup(currentTooltip, false, false, false, false);
+ }
+#endif
+
+ DestroyTooltip();
+ return NS_OK;
+}
+
+static void
+GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult)
+{
+ *aResult = nullptr;
+ uint32_t childCount = aContent->GetChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsIContent *child = aContent->GetChildAt(i);
+
+ if (child->IsXULElement(aTag)) {
+ *aResult = child;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+
+ return;
+}
+
+nsresult
+nsXULTooltipListener::FindTooltip(nsIContent* aTarget, nsIContent** aTooltip)
+{
+ if (!aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ // before we go on, make sure that target node still has a window
+ nsIDocument *document = aTarget->GetComposedDoc();
+ if (!document) {
+ NS_WARNING("Unable to retrieve the tooltip node document.");
+ return NS_ERROR_FAILURE;
+ }
+ nsPIDOMWindowOuter *window = document->GetWindow();
+ if (!window) {
+ return NS_OK;
+ }
+
+ if (window->Closed()) {
+ return NS_OK;
+ }
+
+ nsAutoString tooltipText;
+ aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, tooltipText);
+ if (!tooltipText.IsEmpty()) {
+ // specifying tooltiptext means we will always use the default tooltip
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell());
+ NS_ENSURE_STATE(rootBox);
+ *aTooltip = rootBox->GetDefaultTooltip();
+ if (*aTooltip) {
+ NS_ADDREF(*aTooltip);
+ (*aTooltip)->SetAttr(kNameSpaceID_None, nsGkAtoms::label, tooltipText, true);
+ }
+ return NS_OK;
+ }
+
+ nsAutoString tooltipId;
+ aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltip, tooltipId);
+
+ // if tooltip == _child, look for first <tooltip> child
+ if (tooltipId.EqualsLiteral("_child")) {
+ GetImmediateChild(aTarget, nsGkAtoms::tooltip, aTooltip);
+ return NS_OK;
+ }
+
+ if (!tooltipId.IsEmpty() && aTarget->IsInUncomposedDoc()) {
+ // tooltip must be an id, use getElementById to find it
+ //XXXsmaug If aTarget is in shadow dom, should we use
+ // ShadowRoot::GetElementById()?
+ nsCOMPtr<nsIContent> tooltipEl = document->GetElementById(tooltipId);
+
+ if (tooltipEl) {
+#ifdef MOZ_XUL
+ mNeedTitletip = false;
+#endif
+ tooltipEl.forget(aTooltip);
+ return NS_OK;
+ }
+ }
+
+#ifdef MOZ_XUL
+ // titletips should just use the default tooltip
+ if (mIsSourceTree && mNeedTitletip) {
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell());
+ NS_ENSURE_STATE(rootBox);
+ NS_IF_ADDREF(*aTooltip = rootBox->GetDefaultTooltip());
+ }
+#endif
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULTooltipListener::GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip)
+{
+ *aTooltip = nullptr;
+ nsCOMPtr<nsIContent> tooltip;
+ nsresult rv = FindTooltip(aTarget, getter_AddRefs(tooltip));
+ if (NS_FAILED(rv) || !tooltip) {
+ return rv;
+ }
+
+#ifdef MOZ_XUL
+ // Submenus can't be used as tooltips, see bug 288763.
+ nsIContent* parent = tooltip->GetParent();
+ if (parent) {
+ nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame());
+ if (menu) {
+ NS_WARNING("Menu cannot be used as a tooltip");
+ return NS_ERROR_FAILURE;
+ }
+ }
+#endif
+
+ tooltip.swap(*aTooltip);
+ return rv;
+}
+
+nsresult
+nsXULTooltipListener::DestroyTooltip()
+{
+ nsCOMPtr<nsIDOMEventListener> kungFuDeathGrip(this);
+ nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
+ if (currentTooltip) {
+ // release tooltip before removing listener to prevent our destructor from
+ // being called recursively (bug 120863)
+ mCurrentTooltip = nullptr;
+
+ // clear out the tooltip node on the document
+ nsCOMPtr<nsIDocument> doc = currentTooltip->GetComposedDoc();
+ if (doc) {
+ // remove the mousedown and keydown listener from document
+ doc->RemoveSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"), this,
+ true);
+ doc->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this,
+ true);
+ doc->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, true);
+ doc->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this, true);
+ }
+
+ // remove the popuphidden listener from tooltip
+ currentTooltip->RemoveSystemEventListener(NS_LITERAL_STRING("popuphiding"), this, false);
+ }
+
+ // kill any ongoing timers
+ KillTooltipTimer();
+ mSourceNode = nullptr;
+#ifdef MOZ_XUL
+ mLastTreeCol = nullptr;
+#endif
+
+ return NS_OK;
+}
+
+void
+nsXULTooltipListener::KillTooltipTimer()
+{
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ mTargetNode = nullptr;
+ }
+}
+
+void
+nsXULTooltipListener::sTooltipCallback(nsITimer *aTimer, void *aListener)
+{
+ RefPtr<nsXULTooltipListener> instance = mInstance;
+ if (instance)
+ instance->ShowTooltip();
+}
+
+#ifdef MOZ_XUL
+nsresult
+nsXULTooltipListener::GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject)
+{
+ *aBoxObject = nullptr;
+
+ nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode);
+ if (mIsSourceTree && sourceNode) {
+ nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(sourceNode->GetParent()));
+ if (xulEl) {
+ nsCOMPtr<nsIBoxObject> bx;
+ xulEl->GetBoxObject(getter_AddRefs(bx));
+ nsCOMPtr<nsITreeBoxObject> obx(do_QueryInterface(bx));
+ if (obx) {
+ *aBoxObject = obx;
+ NS_ADDREF(*aBoxObject);
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+#endif
diff --git a/layout/xul/nsXULTooltipListener.h b/layout/xul/nsXULTooltipListener.h
new file mode 100644
index 000000000..51e0a2c3f
--- /dev/null
+++ b/layout/xul/nsXULTooltipListener.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTooltipListener_h__
+#define nsXULTooltipListener_h__
+
+#include "nsIDOMEventListener.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMElement.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#ifdef MOZ_XUL
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#endif
+#include "nsWeakPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsIContent;
+
+class nsXULTooltipListener final : public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void MouseOut(nsIDOMEvent* aEvent);
+ void MouseMove(nsIDOMEvent* aEvent);
+
+ nsresult AddTooltipSupport(nsIContent* aNode);
+ nsresult RemoveTooltipSupport(nsIContent* aNode);
+ static nsXULTooltipListener* GetInstance() {
+ if (!mInstance)
+ mInstance = new nsXULTooltipListener();
+ return mInstance;
+ }
+ static void ClearTooltipCache() { mInstance = nullptr; }
+
+protected:
+
+ nsXULTooltipListener();
+ ~nsXULTooltipListener();
+
+ // pref callback for when the "show tooltips" pref changes
+ static bool sShowTooltips;
+ static uint32_t sTooltipListenerCount;
+
+ void KillTooltipTimer();
+
+#ifdef MOZ_XUL
+ void CheckTreeBodyMove(nsIDOMMouseEvent* aMouseEvent);
+ nsresult GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject);
+#endif
+
+ nsresult ShowTooltip();
+ void LaunchTooltip();
+ nsresult HideTooltip();
+ nsresult DestroyTooltip();
+ // This method tries to find a tooltip for aTarget.
+ nsresult FindTooltip(nsIContent* aTarget, nsIContent** aTooltip);
+ // This method calls FindTooltip and checks that the tooltip
+ // can be really used (i.e. tooltip is not a menu).
+ nsresult GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip);
+
+ static nsXULTooltipListener* mInstance;
+ static void ToolbarTipsPrefChanged(const char *aPref, void *aClosure);
+
+ nsWeakPtr mSourceNode;
+ nsWeakPtr mTargetNode;
+ nsWeakPtr mCurrentTooltip;
+
+ // a timer for showing the tooltip
+ nsCOMPtr<nsITimer> mTooltipTimer;
+ static void sTooltipCallback (nsITimer* aTimer, void* aListener);
+
+ // screen coordinates of the last mousemove event, stored so that the
+ // tooltip can be opened at this location.
+ int32_t mMouseScreenX, mMouseScreenY;
+
+ // various constants for tooltips
+ enum {
+ kTooltipMouseMoveTolerance = 7 // 7 pixel tolerance for mousemove event
+ };
+
+ // flag specifying if the tooltip has already been displayed by a MouseMove
+ // event. The flag is reset on MouseOut so that the tooltip will display
+ // the next time the mouse enters the node (bug #395668).
+ bool mTooltipShownOnce;
+
+#ifdef MOZ_XUL
+ // special members for handling trees
+ bool mIsSourceTree;
+ bool mNeedTitletip;
+ int32_t mLastTreeRow;
+ nsCOMPtr<nsITreeColumn> mLastTreeCol;
+#endif
+};
+
+#endif // nsXULTooltipListener
diff --git a/layout/xul/reftest/image-scaling-min-height-1-ref.xul b/layout/xul/reftest/image-scaling-min-height-1-ref.xul
new file mode 100644
index 000000000..595450fe4
--- /dev/null
+++ b/layout/xul/reftest/image-scaling-min-height-1-ref.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<html:style><![CDATA[
+
+window { -moz-box-align: start; -moz-box-pack: start }
+hbox { background: yellow }
+vbox { background: blue; width: 15px; height: 15px }
+
+]]></html:style>
+
+<hbox><vbox /><label value="a b c d e f" /></hbox>
+
+</window>
diff --git a/layout/xul/reftest/image-scaling-min-height-1.xul b/layout/xul/reftest/image-scaling-min-height-1.xul
new file mode 100644
index 000000000..5c45d6b0c
--- /dev/null
+++ b/layout/xul/reftest/image-scaling-min-height-1.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<html:style><![CDATA[
+
+window { -moz-box-align: start; -moz-box-pack: start }
+hbox { background: yellow }
+image { background: blue; min-width: 15px; min-height: 15px }
+
+]]></html:style>
+
+<hbox><image /><label value="a b c d e f" /></hbox>
+
+</window>
diff --git a/layout/xul/reftest/image-size-ref.xul b/layout/xul/reftest/image-size-ref.xul
new file mode 100644
index 000000000..c67364686
--- /dev/null
+++ b/layout/xul/reftest/image-size-ref.xul
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+<html:style>
+div { margin: 0px; line-height: 0px; }
+div div { background: blue; display: inline; float: left; }
+</html:style>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 40px; height: 30px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 20px;"/><html:img
+ src="image4x3.png" style="width: 10px; height: 70px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 60px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 60px;"/><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/><html:img
+ src="image4x3.png" style="width: 40px; height: 30px; border: 8px solid green;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 80px; height: 64px; border: 8px solid yellow;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 72px; height: 58px; border: 8px solid green;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid yellow;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 74px; height: 53px; border: solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 18px; height: 11px; border: solid green; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 40px; height: 30px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 20px;"/><html:img
+ src="image4x3.png" style="width: 10px; height: 70px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 60px;"/><html:img
+ src="image4x3.png" style="height: 80px; height: 60px;"/><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/><html:img
+ src="image4x3.png" style="width: 60px; height: 25px;"/><html:img
+ src="image4x3.png" style="width: 20px; height: 75px;"/><html:img
+ src="image4x3.png" style="width: 80px; height: 64px; padding: 8px; box-sizing: border-box;"/><html:img
+ src="image4x3.png" style="width: 72px; height: 58px; padding: 8px; box-sizing: border-box;"/><html:img
+ src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; box-sizing: border-box;"/><html:img
+ src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; box-sizing: border-box;"/><html:img
+ src="image4x3.png" style="width: 67px; height: 60px; padding: 4px 2px 8px 1px; box-sizing: border-box;"/><html:img
+ src="image4x3.png" style="width: 11px; height: 18px; padding: 4px 2px 8px 1px; box-sizing: border-box;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 30px; height: 22.5px"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 20px; height: 15px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width 30px; height: 22.5px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 40px; height: 30px;"/><html:img
+ src="image4x3.png" style="width: 40px; height: 30px;"/><html:img
+ src="image4x3.png" style="width: 40px; height: 30px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 60px; height: 49px; border: 8px solid green;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 96px; height: 76px; border: 8px solid green;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 106px; height: 77px; border: solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/>
+</html:div>
+
+<html:div><html:img
+ src="image4x3.png" style="width: 60px; height: 45px;"/><html:img
+ src="image4x3.png" style="width: 120px; height: 90px;"/><html:img
+ src="image4x3.png" style="width 60px; height: 45px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 60px; height: 49px; padding: 8px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; padding: 8px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 96px; height: 76px; padding: 8px;"/><html:img
+ src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; padding: 8px;"/>
+</html:div>
+
+<html:div><html:div
+ style="width: 20px; height: 15px;"/><html:div
+ style="width: 80px; height: 60px;"/><html:div
+ style="width: 40px; height: 30px;"/><html:div
+ style="width: 10px; height: 8px;"/><html:div
+ style="width: 10px; height: 8px;"/>
+</html:div>
+
+<html:div><html:div style="width: 20px; height: 15px;"/></html:div>
+
+<html:div><html:div style="width: 20px; height: 15px;"/></html:div>
+
+<html:div><html:div style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div>
+
+<html:div><html:div style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div>
+
+</window>
diff --git a/layout/xul/reftest/image-size.xul b/layout/xul/reftest/image-size.xul
new file mode 100644
index 000000000..732dc51f3
--- /dev/null
+++ b/layout/xul/reftest/image-size.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<hbox align="end">
+ <image src="image4x3.png"/>
+ <image src="image4x3.png" width="80" height="20"/>
+ <image src="image4x3.png" width="10" height="70"/>
+ <image src="image4x3.png" width="80"/>
+ <image src="image4x3.png" height="60"/>
+ <image src="image4x3.png" width="20"/>
+ <image src="image4x3.png" height="15"/>
+ <image src="image4x3.png" style="border: 8px solid green;"/>
+ <image src="image4x3.png" width="80" style="border: 8px solid yellow;"/>
+ <image src="image4x3.png" height="58" style="border: 8px solid green;"/>
+ <image src="image4x3.png" width="24" style="border: 8px solid yellow;"/>
+ <image src="image4x3.png" height="22" style="border: 8px solid green;"/>
+ <image src="image4x3.png" width="74"
+ style="border: 1px solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/>
+ <image src="image4x3.png" height="11"
+ style="border: 1px solid green; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" style="width: auto; height: auto;"/>
+ <image src="image4x3.png" style="width: 80px; height: 20px;"/>
+ <image src="image4x3.png" style="width: 10px; height: 70px;"/>
+ <image src="image4x3.png" style="width: 80px;"/>
+ <image src="image4x3.png" style="height: 60px;"/>
+ <image src="image4x3.png" style="width: 20px;"/>
+ <image src="image4x3.png" style="height: 15px;"/>
+ <image src="image4x3.png" style="width: 80px; height: 20px;" width="60" height="25"/>
+ <image src="image4x3.png" style="width: 10px; height: 70px;" width="20" height="75"/>
+ <image src="image4x3.png" style="width: 80px; padding: 8px;"/>
+ <image src="image4x3.png" style="height: 58px; padding: 8px;"/>
+ <image src="image4x3.png" style="width: 24px; padding: 8px;"/>
+ <image src="image4x3.png" style="height: 22px; padding: 8px;"/>
+ <image src="image4x3.png" style="width: 67px; padding: 4px 2px 8px 1px"/>
+ <image src="image4x3.png" style="height: 18px; padding: 4px 2px 8px 1px"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" maxwidth="20"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" maxheight="15"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" maxwidth="30" maxheight="25"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" style="max-width: 20px;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" style="max-height: 15px;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" style="max-width: 30px; max-height: 25px;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" maxwidth="24" style="border: 8px solid green;"/>
+</hbox>
+<hbox align="end">
+ <image src="image4x3.png" maxheight="22" style="border: 8px solid green;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" minwidth="20"/>
+ <image src="image4x3.png" minheight="20"/>
+ <image src="image4x3.png" minwidth="20" minheight="25"/>
+ <image src="image4x3.png" minwidth="60" style="border: 8px solid green;"/>
+ <image src="image4x3.png" minheight="88" style="border: 8px solid yellow;"/>
+ <image src="image4x3.png" minwidth="90" minheight="76" style="border: 8px solid green;"/>
+ <image src="image4x3.png" minwidth="112" minheight="76" style="border: 8px solid yellow;"/>
+ <image src="image4x3.png" minwidth="106"
+ style="border: 1px solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/>
+</hbox>
+
+<hbox align="end">
+ <image src="image4x3.png" style="min-width: 60px;"/>
+ <image src="image4x3.png" style="min-height: 90px;"/>
+ <image src="image4x3.png" style="min-width 41px; min-height: 45px;"/>
+ <image src="image4x3.png" style="min-width: 60px; padding: 8px;"/>
+ <image src="image4x3.png" style="min-height: 88px; padding: 8px;"/>
+ <image src="image4x3.png" style="min-width: 90px; min-height: 76px; padding: 8px;"/>
+ <image src="image4x3.png" style="min-width: 112px; min-height: 76px; padding: 8px;"/>
+</hbox>
+
+<hbox align="start">
+ <image style="width: auto; height: auto; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/>
+ <image width="80" style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/>
+ <image height="30" style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/>
+ <image style="width: 10px; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 21px, 5px);"/>
+ <image style="height: 8px; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 21px, 5px);"/>
+</hbox>
+
+<hbox align="end">
+ <image maxwidth="20"
+ style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/>
+</hbox>
+
+<hbox align="end">
+ <image maxheight="15"
+ style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/>
+</hbox>
+
+<hbox align="end">
+ <image maxwidth="24"
+ style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px); border: 8px solid green;"/>
+</hbox>
+
+<hbox align="end">
+ <image maxheight="22"
+ style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px); border: 8px solid green;"/>
+</hbox>
+
+</window>
diff --git a/layout/xul/reftest/image4x3.png b/layout/xul/reftest/image4x3.png
new file mode 100644
index 000000000..6719bf5ce
--- /dev/null
+++ b/layout/xul/reftest/image4x3.png
Binary files differ
diff --git a/layout/xul/reftest/popup-explicit-size-ref.xul b/layout/xul/reftest/popup-explicit-size-ref.xul
new file mode 100644
index 000000000..4c864f103
--- /dev/null
+++ b/layout/xul/reftest/popup-explicit-size-ref.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <label value="One"/>
+ <label value="Two"/>
+</window>
diff --git a/layout/xul/reftest/popup-explicit-size.xul b/layout/xul/reftest/popup-explicit-size.xul
new file mode 100644
index 000000000..6f1bf8582
--- /dev/null
+++ b/layout/xul/reftest/popup-explicit-size.xul
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <label value="One"/>
+ <popup height="40"/>
+ <label value="Two"/>
+</window>
diff --git a/layout/xul/reftest/reftest-stylo.list b/layout/xul/reftest/reftest-stylo.list
new file mode 100644
index 000000000..5a96bfb11
--- /dev/null
+++ b/layout/xul/reftest/reftest-stylo.list
@@ -0,0 +1,14 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == textbox-multiline-noresize.xul textbox-multiline-noresize.xul
+# reference is blank on Android (due to no native theme support?)
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == textbox-multiline-resize.xul textbox-multiline-resize.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == popup-explicit-size.xul popup-explicit-size.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+random-if(Android) skip-if((B2G&&browserIsRemote)||Mulet) == image-size.xul image-size.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == image-scaling-min-height-1.xul image-scaling-min-height-1.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == textbox-text-transform.xul textbox-text-transform.xul
+# Initial mulet triage: parity with B2G/B2G Desktop
diff --git a/layout/xul/reftest/reftest.list b/layout/xul/reftest/reftest.list
new file mode 100644
index 000000000..39f353577
--- /dev/null
+++ b/layout/xul/reftest/reftest.list
@@ -0,0 +1,6 @@
+fails-if(Android) == textbox-multiline-noresize.xul textbox-multiline-ref.xul # reference is blank on Android (due to no native theme support?)
+!= textbox-multiline-resize.xul textbox-multiline-ref.xul
+== popup-explicit-size.xul popup-explicit-size-ref.xul
+random-if(Android) == image-size.xul image-size-ref.xul
+== image-scaling-min-height-1.xul image-scaling-min-height-1-ref.xul
+== textbox-text-transform.xul textbox-text-transform-ref.xul
diff --git a/layout/xul/reftest/textbox-multiline-noresize.xul b/layout/xul/reftest/textbox-multiline-noresize.xul
new file mode 100644
index 000000000..27945ae9a
--- /dev/null
+++ b/layout/xul/reftest/textbox-multiline-noresize.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<textbox style="margin: 0;" multiline="true" width="100" height="100"/>
+</window>
diff --git a/layout/xul/reftest/textbox-multiline-ref.xul b/layout/xul/reftest/textbox-multiline-ref.xul
new file mode 100644
index 000000000..23d279a75
--- /dev/null
+++ b/layout/xul/reftest/textbox-multiline-ref.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<vbox style="-moz-appearance: textfield;" width="100" height="100"/>
+</window>
diff --git a/layout/xul/reftest/textbox-multiline-resize.xul b/layout/xul/reftest/textbox-multiline-resize.xul
new file mode 100644
index 000000000..d7a25a669
--- /dev/null
+++ b/layout/xul/reftest/textbox-multiline-resize.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<textbox style="margin: 0;" resizable="true" multiline="true" width="100" height="100"/>
+</window>
diff --git a/layout/xul/reftest/textbox-text-transform-ref.xul b/layout/xul/reftest/textbox-text-transform-ref.xul
new file mode 100644
index 000000000..cbe95e079
--- /dev/null
+++ b/layout/xul/reftest/textbox-text-transform-ref.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<label value="UPPERCASE"/>
+<label value="lowercase"/>
+</window>
diff --git a/layout/xul/reftest/textbox-text-transform.xul b/layout/xul/reftest/textbox-text-transform.xul
new file mode 100644
index 000000000..5de1c6af2
--- /dev/null
+++ b/layout/xul/reftest/textbox-text-transform.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<label style="text-transform: uppercase" value="uppercase"/>
+<label style="text-transform: lowercase" value="LOWERCASE"/>
+</window>
diff --git a/layout/xul/test/browser.ini b/layout/xul/test/browser.ini
new file mode 100644
index 000000000..3fa28b153
--- /dev/null
+++ b/layout/xul/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+
+[browser_bug685470.js]
+[browser_bug703210.js]
+[browser_bug706743.js]
+skip-if = (os == 'linux') # Bug 1157576
+[browser_bug1163304.js]
+skip-if = os != 'linux' && os != 'win' // Due to testing menubar behavior with keyboard
diff --git a/layout/xul/test/browser_bug1163304.js b/layout/xul/test/browser_bug1163304.js
new file mode 100644
index 000000000..b8e9409dd
--- /dev/null
+++ b/layout/xul/test/browser_bug1163304.js
@@ -0,0 +1,35 @@
+function test() {
+ waitForExplicitFinish();
+
+ let searchBar = BrowserSearch.searchBar;
+ searchBar.focus();
+
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils();
+ is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED,
+ "IME should be available when searchbar has focus");
+
+ let searchPopup = document.getElementById("PopupSearchAutoComplete");
+ searchPopup.addEventListener("popupshown", function (aEvent) {
+ searchPopup.removeEventListener("popupshown", arguments.callee);
+ setTimeout(function () {
+ is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED,
+ "IME should be available even when the popup of searchbar is open");
+ searchPopup.addEventListener("popuphidden", function (aEvent) {
+ searchPopup.removeEventListener("popuphidden", arguments.callee);
+ setTimeout(function () {
+ is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_DISABLED,
+ "IME should not be available when menubar is active");
+ // Inactivate the menubar (and restore the focus to the searchbar
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED,
+ "IME should be available after focus is back to the searchbar");
+ finish();
+ }, 0);
+ });
+ // Activate the menubar, then, the popup should be closed
+ EventUtils.synthesizeKey("VK_ALT", {});
+ }, 0);
+ });
+ // Open popup of the searchbar
+ EventUtils.synthesizeKey("VK_F4", {});
+}
diff --git a/layout/xul/test/browser_bug685470.js b/layout/xul/test/browser_bug685470.js
new file mode 100644
index 000000000..c86231bc7
--- /dev/null
+++ b/layout/xul/test/browser_bug685470.js
@@ -0,0 +1,19 @@
+add_task(function* () {
+ const html = "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html," + html);
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" },
+ gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { }, gBrowser.selectedBrowser);
+
+ // Wait until the tooltip timeout triggers that would normally have opened the popup.
+ yield new Promise(resolve => setTimeout(resolve, 0));
+ is(document.getElementById("aHTMLTooltip").state, "closed", "local tooltip is closed");
+ is(document.getElementById("remoteBrowserTooltip").state, "closed", "remote tooltip is closed");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/layout/xul/test/browser_bug703210.js b/layout/xul/test/browser_bug703210.js
new file mode 100644
index 000000000..cfd626b1c
--- /dev/null
+++ b/layout/xul/test/browser_bug703210.js
@@ -0,0 +1,33 @@
+add_task(function* () {
+ const url = "data:text/html," +
+ "<html onmousemove='event.stopPropagation()'" +
+ " onmouseenter='event.stopPropagation()' onmouseleave='event.stopPropagation()'" +
+ " onmouseover='event.stopPropagation()' onmouseout='event.stopPropagation()'>" +
+ "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>" +
+ "<p id=\"p2\">This paragraph doesn't have tooltip.</p></html>";
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = gBrowser.selectedBrowser;
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
+ });
+
+ // Send a mousemove at a known position to start the test.
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser);
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ is(event.originalTarget.localName, "tooltip", "tooltip is showing");
+ return true;
+ });
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, browser);
+ yield popupShownPromise;
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden", false, event => {
+ is(event.originalTarget.localName, "tooltip", "tooltip is hidden");
+ return true;
+ });
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser);
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/layout/xul/test/browser_bug706743.js b/layout/xul/test/browser_bug706743.js
new file mode 100644
index 000000000..b7262e812
--- /dev/null
+++ b/layout/xul/test/browser_bug706743.js
@@ -0,0 +1,84 @@
+add_task(function* () {
+ const url = "data:text/html,<html><head></head><body>" +
+ "<a id=\"target\" href=\"about:blank\" title=\"This is tooltip text\" " +
+ "style=\"display:block;height:20px;margin:10px;\" " +
+ "onclick=\"return false;\">here is an anchor element</a></body></html>";
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = gBrowser.selectedBrowser;
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
+ });
+
+ // Send a mousemove at a known position to start the test.
+ yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser);
+
+ // show tooltip by mousemove into target.
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
+ yield popupShownPromise;
+
+ // hide tooltip by mousemove to outside.
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
+ yield popupHiddenPromise;
+
+ // mousemove into the target and start drag by emulation via nsIDragService.
+ // Note that on some platforms, we cannot actually start the drag by
+ // synthesized events. E.g., Windows waits an actual mousemove event after
+ // dragstart.
+
+ // Emulate a buggy mousemove event. widget might dispatch mousemove event
+ // during drag.
+
+ function tooltipNotExpected()
+ {
+ ok(false, "tooltip is shown during drag");
+ }
+ addEventListener("popupshown", tooltipNotExpected, true);
+
+ let dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
+ getService(Components.interfaces.nsIDragService);
+ dragService.startDragSession();
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
+
+ yield new Promise(resolve => setTimeout(resolve, 100));
+ removeEventListener("popupshown", tooltipNotExpected, true);
+ dragService.endDragSession(true);
+
+ yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser);
+
+ // If tooltip listener used a flag for managing D&D state, we would need
+ // to test if the tooltip is shown after drag.
+
+ // show tooltip by mousemove into target.
+ popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
+ yield popupShownPromise;
+
+ // hide tooltip by mousemove to outside.
+ popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
+ yield popupHiddenPromise;
+
+ // Show tooltip after mousedown
+ popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
+ yield popupShownPromise;
+
+ popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousedown" }, browser);
+ yield popupHiddenPromise;
+
+ yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mouseup" }, browser);
+ yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
+
+ ok(true, "tooltips appear properly");
+
+ gBrowser.removeCurrentTab();
+});
+
+
+
+
diff --git a/layout/xul/test/chrome.ini b/layout/xul/test/chrome.ini
new file mode 100644
index 000000000..2a647b6b7
--- /dev/null
+++ b/layout/xul/test/chrome.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ window_resizer.xul
+ window_resizer_element.xul
+
+[test_bug159346.xul]
+[test_bug372685.xul]
+[test_bug381167.xhtml]
+[test_bug393970.xul]
+[test_bug398982-1.xul]
+[test_bug398982-2.xul]
+[test_bug467442.xul]
+[test_bug477754.xul]
+[test_bug703150.xul]
+[test_bug987230.xul]
+skip-if = os == 'linux' # No native mousedown event on Linux
+[test_popupReflowPos.xul]
+[test_popupSizeTo.xul]
+[test_popupZoom.xul]
+[test_resizer.xul]
+[test_stack.xul]
+[test_submenuClose.xul]
+[test_windowminmaxsize.xul]
diff --git a/layout/xul/test/mochitest.ini b/layout/xul/test/mochitest.ini
new file mode 100644
index 000000000..540abc730
--- /dev/null
+++ b/layout/xul/test/mochitest.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+
+[test_bug386386.html]
+[test_bug394800.xhtml]
+[test_bug511075.html]
+skip-if = toolkit == 'android' #bug 798806
+[test_bug563416.html]
+[test_bug1197913.xul]
+skip-if = toolkit == 'android'
+[test_resizer_incontent.xul]
+[test_splitter.xul]
+skip-if = toolkit == 'android' # no XUL theme
diff --git a/layout/xul/test/test_bug1197913.xul b/layout/xul/test/test_bug1197913.xul
new file mode 100644
index 000000000..6f922402d
--- /dev/null
+++ b/layout/xul/test/test_bug1197913.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1197913
+-->
+<window title="Mozilla Bug 1197913"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="SimpleTest.waitForFocus(nextTest, window)">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1197913"
+ target="_blank">Mozilla Bug 1197913</a>
+ </body>
+
+ <hbox align="center" pack="center">
+ <menulist>
+ <menupopup>
+ <menuitem label="Car" />
+ <menuitem label="Taxi" id="target" />
+ <menuitem label="Bus" />
+ </menupopup>
+ </menulist>
+ </hbox>
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ let menulist = document.getElementsByTagName("menulist")[0];
+ let menuitem = document.getElementById("target");
+
+ function onDOMMenuItemActive(e) {
+ menuitem.removeEventListener("DOMMenuItemActive", onDOMMenuItemActive);
+
+ synthesizeMouse(menuitem, 0, 0, { type: "mousemove" });
+ synthesizeMouse(menuitem, -1, 0, { type: "mousemove" });
+
+ setTimeout(() => {
+ if (navigator.platform.toLowerCase().startsWith("win")) {
+ ok(menuitem.getAttribute("_moz-menuactive"));
+ } else {
+ ok(!menuitem.getAttribute("_moz-menuactive"));
+ }
+
+ SimpleTest.finish();
+ });
+ }
+
+ function onPopupShown(e) {
+ menulist.removeEventListener("popupshown", onPopupShown);
+ menuitem.addEventListener("DOMMenuItemActive", onDOMMenuItemActive);
+ synthesizeMouse(menuitem, 0, 0, { type: "mousemove" });
+ synthesizeMouse(menuitem, 1, 0, { type: "mousemove" });
+ }
+
+ function nextTest(e) {
+ menulist.addEventListener("popupshown", onPopupShown);
+ synthesizeMouseAtCenter(menulist, {});
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/layout/xul/test/test_bug159346.xul b/layout/xul/test/test_bug159346.xul
new file mode 100644
index 000000000..4ef34fe32
--- /dev/null
+++ b/layout/xul/test/test_bug159346.xul
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 159346">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=159346
+-->
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+<scrollbar id="scrollbar" curpos="0" maxpos="500"/>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var scrollbar = document.getElementById("scrollbar");
+var downButton =
+ document.getAnonymousElementByAttribute(scrollbar, "sbattr",
+ "scrollbar-down-bottom");
+
+function init()
+{
+ downButton.style.display = "-moz-box";
+ SimpleTest.executeSoon(doTest1);
+}
+
+function getCurrentPos()
+{
+ return Number(scrollbar.getAttribute("curpos"));
+}
+
+function doTest1()
+{
+ var lastPos = 0;
+
+ synthesizeMouseAtCenter(downButton, { type: "mousedown" });
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by mousedown #1");
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by auto repeat #1");
+ synthesizeMouseAtCenter(downButton, { type: "mouseup" });
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ is(getCurrentPos(), lastPos,
+ "scrollbar changed curpos after mouseup #1");
+ SimpleTest.executeSoon(doTest2);
+ }, 1000);
+ }, 1000);
+}
+
+function doTest2()
+{
+ SpecialPowers.setIntPref("ui.scrollbarButtonAutoRepeatBehavior", 0);
+
+ scrollbar.setAttribute("curpos", 0);
+ var lastPos = 0;
+
+ synthesizeMouseAtCenter(downButton, { type: "mousedown" });
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by mousedown #2");
+ lastPos = getCurrentPos();
+
+ synthesizeMouse(downButton, -10, -10, { type: "mousemove" });
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ is(getCurrentPos(), lastPos,
+ "scrollbar changed curpos by auto repeat when cursor is outside of scrollbar button #2");
+ synthesizeMouseAtCenter(downButton, { type: "mousemove" });
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by mousemove after cursor is back on the scrollbar button #2");
+ synthesizeMouseAtCenter(downButton, { type: "mouseup" });
+ SimpleTest.executeSoon(doTest3);
+ }, 1000);
+ }, 1000);
+}
+
+function doTest3()
+{
+ SpecialPowers.setIntPref("ui.scrollbarButtonAutoRepeatBehavior", 1);
+
+ scrollbar.setAttribute("curpos", 0);
+ var lastPos = 0;
+
+ synthesizeMouseAtCenter(downButton, { type: "mousedown" });
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by mousedown #3");
+ synthesizeMouse(downButton, -10, -10, { type: "mousemove" });
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by auto repeat when cursor is outside of scrollbar button #3");
+ synthesizeMouseAtCenter(downButton, { type: "mousemove" });
+ lastPos = getCurrentPos();
+
+ setTimeout(function () {
+ ok(getCurrentPos() > lastPos,
+ "scrollbar didn't change curpos by mousemove after cursor is back on the scrollbar button #3");
+ synthesizeMouseAtCenter(downButton, { type: "mouseup" });
+
+ SpecialPowers.clearUserPref("ui.scrollbarButtonAutoRepeatBehavior");
+ SimpleTest.finish();
+ }, 1000);
+ }, 1000);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+<body id="html_body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=159346">Mozilla Bug 159346</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+<script>
+addLoadEvent(init);
+</script>
+</body>
+
+
+</window>
diff --git a/layout/xul/test/test_bug372685.xul b/layout/xul/test/test_bug372685.xul
new file mode 100644
index 000000000..09cc2be20
--- /dev/null
+++ b/layout/xul/test/test_bug372685.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 372685">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372685
+-->
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<menuitem id="a" style="display: -moz-stack;">
+<box id="b" style="display: -moz-popup; ">
+<box id="c" style="position: fixed;"></box>
+</box>
+</menuitem>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function removestyles(i){
+ document.getElementById('a').removeAttribute('style');
+ var x=document.getElementById('html_body').offsetHeight;
+ is(0, 0, "this is a crash test, so always ok if we survive this far");
+ SimpleTest.finish();
+}
+function do_test() {
+ setTimeout(removestyles,200);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+<body id="html_body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372685">Mozilla Bug 372685</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+<script>
+addLoadEvent(do_test);
+</script>
+</body>
+
+
+</window>
diff --git a/layout/xul/test/test_bug381167.xhtml b/layout/xul/test/test_bug381167.xhtml
new file mode 100644
index 000000000..3f98d7a4d
--- /dev/null
+++ b/layout/xul/test/test_bug381167.xhtml
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=381167
+-->
+<head>
+ <title>Test for Bug 381167</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=381167">Mozilla Bug 381167</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<xul:tree>
+ <xul:tree>
+ <xul:treechildren/>
+ <xul:treecol/>
+ </xul:tree>
+</xul:tree>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 381167 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function closeit() {
+ var evt = document.createEvent('KeyboardEvent');
+ evt.initKeyEvent('keypress', true, true,
+ window,
+ true, false, false, false,
+ 'W'.charCodeAt(0), 0);
+ window.dispatchEvent(evt);
+
+ setTimeout(finish, 200);
+}
+window.addEventListener('load', closeit, false);
+
+function finish()
+{
+ ok(true, "This is a mochikit version of a crash test. To complete is to pass.");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/xul/test/test_bug386386.html b/layout/xul/test/test_bug386386.html
new file mode 100644
index 000000000..b39ccede2
--- /dev/null
+++ b/layout/xul/test/test_bug386386.html
@@ -0,0 +1,34 @@
+<html>
+<head><title>Testcase for bug 386386</title>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386386
+-->
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+
+<iframe id="test386386" src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cwindow%3E%3C/window%3E"></iframe>
+
+<script class="testbody" type="application/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var doc = document.getElementById("test386386").contentDocument;
+ var observes = doc.createElementNS(XUL_NS, 'observes');
+ doc.removeChild(doc.documentElement);
+ doc.appendChild(observes);
+ is(0, 0, "Test is successful if we get here without crashing");
+ SimpleTest.finish();
+}
+
+function do_test() {
+ setTimeout(boom, 200);
+}
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(do_test);
+</script>
+
+</body>
+</html>
diff --git a/layout/xul/test/test_bug393970.xul b/layout/xul/test/test_bug393970.xul
new file mode 100644
index 000000000..fcb0f9e43
--- /dev/null
+++ b/layout/xul/test/test_bug393970.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<?xml-stylesheet href="data:text/css,description {min-width: 1px; padding: 2px;}" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=393970
+-->
+<window title="Mozilla Bug 393970"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=393970"
+ target="_blank">Mozilla Bug 393970</a>
+ </body>
+
+ <hbox flex="1" pack="start" style="visibility: hidden;">
+ <grid min-width="1000" width="1000">
+ <columns>
+ <column flex="1"/>
+ <column flex="2"/>
+ <column flex="3"/>
+ </columns>
+ <rows id="rows1">
+ <row>
+ <description id="cell11">test1</description>
+ <description id="cell12">test2</description>
+ <description id="cell13">test3</description>
+ </row>
+ <rows id="rows2" flex="1">
+ <row>
+ <description id="cell21">test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1</description>
+ <description id="cell22">test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2</description>
+ <description id="cell23">test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3</description>
+ </row>
+ <rows id="rows3">
+ <row>
+ <description id="cell31">test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1</description>
+ <description id="cell32">test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2</description>
+ <description id="cell33">test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3</description>
+ </row>
+ </rows>
+ </rows>
+ </rows>
+ </grid>
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /** Test for Bug 393970 **/
+
+ if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 24);
+ }
+
+ var tests = [
+ 'overflow-x: hidden; overflow-y: hidden;',
+ 'overflow-x: scroll; overflow-y: hidden;',
+ 'overflow-x: hidden; overflow-y: scroll;',
+ 'overflow-x: scroll; overflow-y: scroll;',
+ ];
+ var currentTest = -1;
+
+ function runNextTest() {
+ currentTest++;
+ if (currentTest >= tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ $("rows2").setAttribute("style", tests[currentTest]);
+ setTimeout(checkPositions, 0, tests[currentTest]);
+ }
+
+ function checkPositions(variant) {
+ for (var col = 1; col <= 3; col++) {
+ is($('cell1' + col).boxObject.x, $('cell2' + col).boxObject.x, "Cells (1," + col + ") and (2," + col + ") line up horizontally (with " + variant + ")");
+ is($('cell2' + col).boxObject.x, $('cell3' + col).boxObject.x, "Cells (2," + col + ") and (3," + col + ") line up horizontally (with " + variant + ")");
+ }
+ for (var row = 1; row <= 3; row++) {
+ is($('cell' + row + '1').boxObject.y, $('cell' + row + '2').boxObject.y, "Cells (" + row + ",1) and (" + row + ",2) line up vertically (with " + variant + ")");
+ is($('cell' + row + '2').boxObject.y, $('cell' + row + '3').boxObject.y, "Cells (" + row + ",2) and (" + row + ",3) line up vertically (with " + variant + ")");
+ }
+ runNextTest();
+ }
+
+ addLoadEvent(runNextTest);
+ SimpleTest.waitForExplicitFinish()
+ ]]></script>
+</window>
diff --git a/layout/xul/test/test_bug394800.xhtml b/layout/xul/test/test_bug394800.xhtml
new file mode 100644
index 000000000..d2e2250e0
--- /dev/null
+++ b/layout/xul/test/test_bug394800.xhtml
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394800
+-->
+ <title>Test Mozilla bug 394800</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+function do_test()
+{
+ var x = document.getElementById("x");
+ x.parentNode.removeChild(x);
+ is(0, 0, "this is a crash/assertion test, so we're ok if we survived this far");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+
+<body>
+
+<xul:menulist><xul:tooltip/><div><span><xul:hbox id="x"/></span></div></xul:menulist>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394800">Mozilla Bug 394800</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<script>
+ addLoadEvent(do_test);
+</script>
+
+</body>
+</html>
diff --git a/layout/xul/test/test_bug398982-1.xul b/layout/xul/test/test_bug398982-1.xul
new file mode 100644
index 000000000..39716ddf4
--- /dev/null
+++ b/layout/xul/test/test_bug398982-1.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<menuitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="position: absolute; ">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=398982
+-->
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<tooltip type="zzz">
+<treecols/>
+</tooltip>
+
+<script xmlns="http://www.w3.org/1999/xhtml" class="testbody" type="application/javascript">
+<![CDATA[
+function doe() {
+ document.getElementsByTagName('menuitem')[0].removeAttribute('style');
+ is(0, 0, "Test is successful if we get here without crashing");
+ SimpleTest.finish();
+}
+function do_test() {
+ setTimeout(doe, 200);
+}
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+]]>
+</script>
+<html:body></html:body> <!-- XXX SimpleTest.showReport() requires a html:body -->
+</menuitem>
diff --git a/layout/xul/test/test_bug398982-2.xul b/layout/xul/test/test_bug398982-2.xul
new file mode 100644
index 000000000..253695c39
--- /dev/null
+++ b/layout/xul/test/test_bug398982-2.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 398982">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=398982
+-->
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<popupgroup style="position: absolute; ">
+<tooltip type="zzz">
+<treecols/>
+</tooltip>
+
+<script xmlns="http://www.w3.org/1999/xhtml" class="testbody" type="application/javascript">
+<![CDATA[
+function doe() {
+ document.getElementsByTagName('popupgroup')[0].removeAttribute('style');
+ is(0, 0, "Test is successful if we get here without crashing");
+ SimpleTest.finish();
+}
+function do_test() {
+ setTimeout(doe, 200);
+}
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+]]>
+</script>
+</popupgroup>
+<html:body></html:body> <!-- XXX SimpleTest.showReport() requires a html:body -->
+</window>
diff --git a/layout/xul/test/test_bug467442.xul b/layout/xul/test/test_bug467442.xul
new file mode 100644
index 000000000..809e90cb8
--- /dev/null
+++ b/layout/xul/test/test_bug467442.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=467442
+-->
+<window title="Mozilla Bug 467442"
+ onload="onload()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <popupset>
+ <panel id="panel">
+ Hello.
+ </panel>
+ </popupset>
+ <hbox>
+ <button id="anchor" label="Anchor hello on here" style="transform: translate(100px, 0)"/>
+ </hbox>
+ <script type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function onload() {
+ /** Test for Bug 467442 **/
+ let panel = document.getElementById("panel");
+ let anchor = document.getElementById("anchor");
+
+ panel.addEventListener("popupshown", function onpopupshown() {
+ panel.removeEventListener("popupshown", onpopupshown);
+ let panelRect = panel.getBoundingClientRect();
+ let anchorRect = anchor.getBoundingClientRect();
+ is(panelRect.left, anchorRect.left, "Panel should be anchored to the button");
+ panel.addEventListener("popuphidden", function onpopuphidden() {
+ panel.removeEventListener("popuphidden", onpopuphidden);
+ SimpleTest.finish();
+ });
+ panel.hidePopup();
+ });
+
+ panel.openPopup(anchor, "after_start", 0, 0, false, false);
+ }
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467442"
+ target="_blank">Mozilla Bug 467442</a>
+ </body>
+</window>
diff --git a/layout/xul/test/test_bug477754.xul b/layout/xul/test/test_bug477754.xul
new file mode 100644
index 000000000..f72b1fffa
--- /dev/null
+++ b/layout/xul/test/test_bug477754.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=477754
+-->
+<window title="Mozilla Bug 477754"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=477754"
+ target="_blank">Mozilla Bug 477754</a>
+ </body>
+
+ <hbox pack="center">
+ <label id="anchor" style="direction: rtl;" value="Anchor"/>
+ </hbox>
+ <panel id="testPopup" onpopupshown="doTest();">
+ <label value="I am a popup"/>
+ </panel>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /** Test for Bug 477754 **/
+ SimpleTest.waitForExplicitFinish();
+
+ let testPopup, testAnchor;
+
+ addEventListener("load", function () {
+ removeEventListener("load", arguments.callee, false);
+
+ testPopup = document.getElementById("testPopup");
+ testAnchor = document.getElementById("anchor");
+
+ testPopup.openPopup(testAnchor, "after_start", 10, 0, false, false);
+ }, false);
+
+ function doTest() {
+ is(Math.round(testAnchor.getBoundingClientRect().right -
+ testPopup.getBoundingClientRect().right), 10,
+ "RTL popup's right offset should be equal to the x offset passed to openPopup");
+ testPopup.hidePopup();
+ SimpleTest.finish();
+ }
+
+ ]]></script>
+</window>
diff --git a/layout/xul/test/test_bug511075.html b/layout/xul/test/test_bug511075.html
new file mode 100644
index 000000000..ecb228884
--- /dev/null
+++ b/layout/xul/test/test_bug511075.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=511075
+-->
+<head>
+ <title>Test for Bug 511075</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #scroller {
+ border: 1px solid black;
+ }
+ </style>
+</head>
+<body onload="runTests()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511075">Mozilla Bug 511075</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 511075 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var tests = [
+ function() {
+ ok(true, "Setting location.hash should scroll.");
+ nextTest();
+ // Click the top scroll arrow.
+ var x = scroller.getBoundingClientRect().width - 5;
+ var y = 5;
+ // On MacOSX the top scroll arrow can be below the slider just above
+ // the bottom scroll arrow.
+ if (navigator.platform.indexOf("Mac") >= 0)
+ y = scroller.getBoundingClientRect().height - 40;
+ synthesizeMouse(scroller, x, y, { type : "mousedown" }, window);
+ synthesizeMouse(scroller, x, y, { type: "mouseup" }, window);
+ },
+ function() {
+ ok(true, "Clicking the top scroll arrow should scroll.");
+ nextTest();
+ // Click the bottom scroll arrow.
+ var x = scroller.getBoundingClientRect().width - 5;
+ var y = scroller.getBoundingClientRect().height - 25;
+ synthesizeMouse(scroller, x, y, { type : "mousedown" }, window);
+ synthesizeMouse(scroller, x, y, { type: "mouseup" }, window);
+ },
+ function() {
+ ok(true, "Clicking the bottom scroll arrow should scroll.");
+ nextTest();
+ // Click the scrollbar.
+ var x = scroller.getBoundingClientRect().width - 5;
+ synthesizeMouse(scroller, x, 40, { type : "mousedown" }, window);
+ synthesizeMouse(scroller, x, 40, { type: "mouseup" }, window);
+ },
+ function() {
+ ok(true, "Clicking the scrollbar should scroll");
+ nextTest();
+ // Click the scrollbar.
+ var x = scroller.getBoundingClientRect().width - 5;
+ var y = scroller.getBoundingClientRect().height - 50;
+ synthesizeMouse(scroller, x, y, { type : "mousedown" }, window);
+ synthesizeMouse(scroller, x, y, { type: "mouseup" }, window);
+ },
+ function() {
+ scroller.onscroll = null;
+ ok(true, "Clicking the scrollbar should scroll");
+ finish();
+ }
+];
+
+document.onmousedown = function () { return false; };
+document.onmouseup = function () { return true; };
+
+
+var scroller;
+var timer = 0;
+
+function failure() {
+ ok(false, scroller.onscroll + " did not run!");
+ scroller.onscroll = null;
+ finish();
+}
+
+function nextTest() {
+ clearTimeout(timer);
+ scroller.onscroll = tests.shift();
+ timer = setTimeout(failure, 2000);
+}
+
+function runTests() {
+ scroller = document.getElementById("scroller");
+ nextTest();
+ window.location.hash = "initialPosition";
+}
+
+function finish() {
+ document.onmousedown = null;
+ document.onmouseup = null;
+ clearTimeout(timer);
+ window.location.hash = "topPosition";
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+<div id="scroller" style="overflow: scroll; width: 100px; height: 150px;">
+<a id="topPosition" name="topPosition">top</a>
+<div style="width: 20000px; height: 20000px;"></div>
+<a id="initialPosition" name="initialPosition">initialPosition</a>
+<div style="width: 20000px; height: 20000px;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/xul/test/test_bug563416.html b/layout/xul/test/test_bug563416.html
new file mode 100644
index 000000000..dd245de0b
--- /dev/null
+++ b/layout/xul/test/test_bug563416.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=563416
+-->
+<head>
+ <title>Test for Bug 563416</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=563416">Mozilla Bug 563416</a>
+<p id="display"><iframe id="test" src='data:text/html,<textarea style="box-sizing:content-box; -moz-appearance:none; height: 0px; padding: 0px;" cols="20" rows="10">hsldkjvmshlkkajskdlfksdjflskdjflskdjflskdjflskdjfddddddddd</textarea>'></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 563416 **/
+
+var result = -1;
+var expected = -2;
+var i = 0;
+
+function runTest() {
+ i = 0;
+ var frame = document.getElementById('test');
+ frame.onload = function() {
+ var t = frame.contentDocument.documentElement.getElementsByTagName("textarea")[0];
+ expected = t.clientWidth + 10;
+ t.style.width = expected + 'px';
+ result = t.clientWidth;
+ if (i == 0) {
+ i++;
+ setTimeout(function(){frame.contentWindow.location.reload();},0);
+ }
+ else {
+ is(result, expected, "setting style.width changes clientWidth");
+ SimpleTest.finish();
+ }
+ }
+ frame.contentWindow.location.reload();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/xul/test/test_bug703150.xul b/layout/xul/test/test_bug703150.xul
new file mode 100644
index 000000000..fab7d1677
--- /dev/null
+++ b/layout/xul/test/test_bug703150.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 703150">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=703150
+-->
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+<scrollbar id="scrollbar" curpos="0" maxpos="500"/>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var scrollbar = document.getElementById("scrollbar");
+var scrollbarThumb =
+ document.getAnonymousElementByAttribute(scrollbar, "sbattr",
+ "scrollbar-thumb");
+
+function doTest()
+{
+ function mousedownHandler(aEvent)
+ {
+ aEvent.stopPropagation();
+ }
+ window.addEventListener("mousedown", mousedownHandler, true);
+
+ // Wait for finishing reflow...
+ SimpleTest.executeSoon(function () {
+ synthesizeMouseAtCenter(scrollbarThumb, { type: "mousedown" });
+
+ is(scrollbar.getAttribute("curpos"), "0",
+ "scrollbar thumb has been moved already");
+
+ synthesizeMouseAtCenter(scrollbar, { type: "mousemove" });
+
+ ok(scrollbar.getAttribute("curpos") > 0,
+ "scrollbar thumb hasn't been dragged");
+
+ synthesizeMouseAtCenter(scrollbarThumb, { type: "mouseup" });
+
+ window.removeEventListener("mousedown", mousedownHandler, true);
+
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+<body id="html_body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=703150">Mozilla Bug 703150</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+<script>
+addLoadEvent(doTest);
+</script>
+</body>
+
+
+</window>
diff --git a/layout/xul/test/test_bug987230.xul b/layout/xul/test/test_bug987230.xul
new file mode 100644
index 000000000..b2b47f470
--- /dev/null
+++ b/layout/xul/test/test_bug987230.xul
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=987230
+-->
+<window title="Mozilla Bug 987230"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="SimpleTest.waitForFocus(nextTest, window)">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=987230"
+ target="_blank">Mozilla Bug 987230</a>
+ </body>
+
+ <vbox>
+ <toolbar>
+ <toolbarbutton id="toolbarbutton-anchor"
+ label="Anchor"
+ consumeanchor="toolbarbutton-anchor"
+ onclick="onAnchorClick(event)"
+ style="padding: 50px !important; list-style-image: url(chrome://branding/content/icon32.png)"/>
+ </toolbar>
+ <spacer flex="1"/>
+ <hbox id="hbox-anchor"
+ style="padding: 20px"
+ onclick="onAnchorClick(event)">
+ <hbox id="inner-anchor"
+ consumeanchor="hbox-anchor"
+ >
+ Another anchor
+ </hbox>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+
+ <panel id="mypopup"
+ type="arrow"
+ onpopupshown="onMyPopupShown(event)"
+ onpopuphidden="onMyPopupHidden(event)">This is a test popup</panel>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 987230 **/
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.requestCompleteLog();
+
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+
+ let platform = navigator.platform.toLowerCase();
+ let isWindows = platform.startsWith("win");
+ let mouseDown = isWindows ? 2 : 1;
+ let mouseUp = isWindows ? 4 : 2;
+ let mouseMove = isWindows ? 1 : 5;
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let scale = utils.screenPixelsPerCSSPixel;
+
+
+ function synthesizeNativeMouseClick(aElement, aOffsetX, aOffsetY) {
+ let rect = aElement.getBoundingClientRect();
+ let win = aElement.ownerDocument.defaultView;
+ let x = aOffsetX + win.mozInnerScreenX + rect.left;
+ let y = aOffsetY + win.mozInnerScreenY + rect.top;
+
+ info("Sending mousedown+up for offsets: " + aOffsetX + ", " + aOffsetY +
+ "; innerscreen: " + win.mozInnerScreenX + ", " + win.mozInnerScreenY +
+ "; rect: " + rect.left + ", " + rect.top + ".");
+ info("Resulting x, y, scale: " + x + ", " + y + ", " + scale);
+ info("Final params: " + (x * scale) + ", " + (y * scale));
+ utils.sendNativeMouseEvent(x * scale, y * scale, mouseDown, 0, null);
+ utils.sendNativeMouseEvent(x * scale, y * scale, mouseUp, 0, null);
+ }
+
+ function onMyPopupHidden(e) {
+ ok(true, "Popup hidden");
+ if (outerAnchor.id == "toolbarbutton-anchor") {
+ popupHasShown = false;
+ outerAnchor = document.getElementById("hbox-anchor");
+ anchor = document.getElementById("inner-anchor");
+ nextTest();
+ } else {
+ //XXXgijs set mouse position back outside the iframe:
+ let frameRect = window.frameElement.getBoundingClientRect();
+ let outsideOfFrameX = (window.mozInnerScreenX + frameRect.width + 100) * scale;
+ let outsideOfFrameY = Math.max(0, window.mozInnerScreenY - 100) * scale;
+
+ info("Mousemove: " + outsideOfFrameX + ", " + outsideOfFrameY +
+ " (from innerscreen " + window.mozInnerScreenX + ", " + window.mozInnerScreenY +
+ " and rect width " + frameRect.width + " and scale " + scale + ")");
+ utils.sendNativeMouseEvent(outsideOfFrameX, outsideOfFrameY, mouseMove, 0, null);
+ SimpleTest.finish();
+ }
+ }
+
+ let popupHasShown = false;
+ function onMyPopupShown(e) {
+ popupHasShown = true;
+ synthesizeNativeMouseClick(outerAnchor, 5, 5);
+ }
+
+ function onAnchorClick(e) {
+ info("click: " + e.target.id);
+ ok(!popupHasShown, "Popup should only be shown once");
+ popup.openPopup(anchor, "bottomcenter topright");
+ }
+
+ let popup = document.getElementById("mypopup");
+ let outerAnchor = document.getElementById("toolbarbutton-anchor");
+ let anchor = document.getAnonymousElementByAttribute(outerAnchor, "class", "toolbarbutton-icon");
+
+ function nextTest(e) {
+ synthesizeMouse(outerAnchor, 5, 5, {});
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/layout/xul/test/test_popupReflowPos.xul b/layout/xul/test/test_popupReflowPos.xul
new file mode 100644
index 000000000..deeeff62e
--- /dev/null
+++ b/layout/xul/test/test_popupReflowPos.xul
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="XUL Panel reflow placement test"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function openPopup()
+ {
+ synthesizeMouseAtCenter(document.getElementById("thebutton"), {}, window);
+ }
+
+ function popupShown(event)
+ {
+ document.getElementById("parent").className = "";
+
+ var buttonbcr = document.getElementById("thebutton").getBoundingClientRect();
+ var popupbcr = document.getElementById("thepopup").getOuterScreenRect();
+
+ ok(Math.abs(popupbcr.x - window.mozInnerScreenX - buttonbcr.x) < 3, "x pos is correct");
+ ok(Math.abs(popupbcr.y - window.mozInnerScreenY - buttonbcr.bottom) < 3, "y pos is correct");
+
+ event.target.hidePopup();
+ }
+
+ SimpleTest.waitForFocus(openPopup);
+ ]]></script>
+
+ <html:style>
+ .mbox {
+ display: inline-block;
+ width: 33%;
+ height: 50px;
+ background: green;
+ vertical-align: middle;
+ }
+ .orange {
+ background: orange;
+ }
+ .change > .mbox {
+ width: 60px;
+ }
+ </html:style>
+
+ <html:div style="width: 300px; height: 200px;">
+ <html:div id="parent" class="change" style="background: red; border: 1px solid black; width: 300px; height: 200px;">
+ <html:div class="mbox"></html:div>
+ <html:div class="mbox"></html:div>
+ <html:div class="mbox"></html:div>
+ <html:div class="mbox orange">
+
+ <button label="Show" type="menu" id="thebutton">
+ <menupopup id="thepopup" onpopupshown="popupShown(event)" onpopuphidden="SimpleTest.finish()">
+ <menuitem label="New"/>
+ <menuitem label="Open"/>
+ <menuitem label="Save"/>
+ <menuseparator/>
+ <menuitem label="Exit"/>
+ </menupopup>
+ </button>
+
+ </html:div>
+ </html:div>
+ </html:div>
+
+</window>
diff --git a/layout/xul/test/test_popupSizeTo.xul b/layout/xul/test/test_popupSizeTo.xul
new file mode 100644
index 000000000..a135e1980
--- /dev/null
+++ b/layout/xul/test/test_popupSizeTo.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+XUL Panel sizeTo tests
+-->
+<window title="XUL Panel sizeTo tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function openPopup()
+ {
+ document.getElementById("panel").
+ openPopupAtScreen(Math.round(window.mozInnerScreenX) + window.innerWidth - 130,
+ Math.round(window.mozInnerScreenY) + window.innerHeight - 130);
+ }
+
+ function sizeAndCheck(width, height) {
+ var panel = document.getElementById("panel");
+ panel.sizeTo(width, height);
+ is(panel.getBoundingClientRect().width, width, "width is correct");
+ is(panel.getBoundingClientRect().height, height, "height is correct");
+
+ }
+ function popupShown(event)
+ {
+ var panel = document.getElementById("panel");
+ var bcr = panel.getBoundingClientRect();
+ // resize to 10px bigger in both dimensions.
+ sizeAndCheck(bcr.width+10, bcr.height+10);
+ // Same width, different height (based on *new* size from last sizeAndCheck)
+ sizeAndCheck(bcr.width+10, bcr.height);
+ // Same height, different width (also based on *new* size from last sizeAndCheck)
+ sizeAndCheck(bcr.width, bcr.height);
+ event.target.hidePopup();
+ }
+
+ SimpleTest.waitForFocus(openPopup);
+ ]]></script>
+
+<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="SimpleTest.finish()">
+ <resizer id="resizer" dir="bottomend" width="16" height="16"/>
+ <hbox width="50" height="50" flex="1"/>
+</panel>
+
+</window>
diff --git a/layout/xul/test/test_popupZoom.xul b/layout/xul/test/test_popupZoom.xul
new file mode 100644
index 000000000..b8e15ba1d
--- /dev/null
+++ b/layout/xul/test/test_popupZoom.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="XUL Panel zoom test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var docviewer;
+ var savedzoom;
+
+ function openPopup()
+ {
+ docviewer = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell)
+ .contentViewer;
+ savedzoom = docviewer.fullZoom;
+ docviewer.fullZoom = 2;
+
+ document.getElementById("panel").
+ openPopup(document.getElementById("anchor"), "after_start", 0, 0, false, false, null);
+ }
+
+ function popupShown(event)
+ {
+ var panel = document.getElementById("panel");
+ var panelbcr = panel.getBoundingClientRect();
+ var anchorbcr = document.getElementById("anchor").getBoundingClientRect();
+
+ ok(Math.abs(panelbcr.x - anchorbcr.x) < 3, "x pos is correct");
+ ok(Math.abs(panelbcr.y - anchorbcr.bottom) < 3, "y pos is correct");
+
+ docviewer.fullZoom = savedzoom;
+
+ event.target.hidePopup();
+ }
+
+ SimpleTest.waitForFocus(openPopup);
+ ]]></script>
+
+<description id="anchor" value="Sometext to this some texts"/>
+<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="SimpleTest.finish()">
+ <resizer id="resizer" dir="bottomend" width="16" height="16"/>
+ <hbox width="50" height="50" flex="1"/>
+</panel>
+
+
+</window>
diff --git a/layout/xul/test/test_resizer.xul b/layout/xul/test/test_resizer.xul
new file mode 100644
index 000000000..2ba971d05
--- /dev/null
+++ b/layout/xul/test/test_resizer.xul
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+XUL <resizer> tests
+-->
+<window title="XUL resizer tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.ignoreAllUncaughtExceptions();
+
+ function openPopup()
+ {
+ document.getElementById("panel").
+ openPopupAtScreen(Math.round(window.mozInnerScreenX) + window.innerWidth - 130,
+ Math.round(window.mozInnerScreenY) + window.innerHeight - 130);
+ }
+
+ var step = 0;
+ function popupShown(event)
+ {
+ if (step == 0) {
+ // check to make sure that the popup cannot be resized past the edges of
+ // the content area
+ var resizerrect = document.getElementById("resizer").getBoundingClientRect();
+ synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" });
+ synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" });
+
+ // allow a one pixel variance as rounding is always done to the inside
+ // of a rectangle.
+ var popuprect = document.getElementById("panel").getBoundingClientRect();
+ ok(Math.round(popuprect.right) == window.innerWidth ||
+ Math.round(popuprect.right) == window.innerWidth - 1,
+ "resized to content edge width");
+ ok(Math.round(popuprect.bottom) == window.innerHeight ||
+ Math.round(popuprect.bottom) == window.innerHeight - 1,
+ "resized to content edge height");
+
+ resizerrect = document.getElementById("resizer").getBoundingClientRect();
+ synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" });
+ }
+ else {
+ // the popup is opened twice. Make sure that for the second time, the
+ // resized popup opens in the same direction as there should still be
+ // room for it
+ var popuprect = document.getElementById("panel").getBoundingClientRect();
+ is(Math.round(popuprect.left), window.innerWidth - 130, "reopen popup left");
+ is(Math.round(popuprect.top), window.innerHeight - 130, "reopen popup top");
+ }
+
+ event.target.hidePopup();
+ }
+
+ function doResizerWindowTests() {
+ step++;
+ if (step == 1) {
+ openPopup();
+ return;
+ }
+
+ if (/Mac/.test(navigator.platform)) {
+ window.open("window_resizer.xul", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome");
+ }
+ else {
+ // Skip window_resizer.xul tests.
+ todo(false, "We can't test GTK and Windows native drag resizing implementations.");
+ // Run window_resizer_element.xul test only.
+ lastResizerTest();
+ }
+ }
+
+ function lastResizerTest()
+ {
+ window.open("window_resizer_element.xul", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome");
+ }
+
+ SimpleTest.waitForFocus(openPopup);
+ ]]></script>
+
+<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="doResizerWindowTests()">
+ <resizer id="resizer" dir="bottomend" width="16" height="16"/>
+ <hbox width="50" height="50" flex="1"/>
+</panel>
+
+</window>
diff --git a/layout/xul/test/test_resizer_incontent.xul b/layout/xul/test/test_resizer_incontent.xul
new file mode 100644
index 000000000..068bd5bb1
--- /dev/null
+++ b/layout/xul/test/test_resizer_incontent.xul
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+This test ensures that a resizer in content doesn't resize the window.
+-->
+<window title="XUL resizer in content test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function testResizer()
+ {
+ var oldScreenX = window.screenX;
+ var oldScreenY = window.screenY;
+ var oldWidth = window.outerWidth;
+ var oldHeight = window.outerHeight;
+ var resizer = document.getElementById("resizer");
+ synthesizeMouseAtCenter(resizer, { type:"mousedown" });
+ synthesizeMouse(resizer, 32, 32, { type:"mousemove" });
+ synthesizeMouse(resizer, 32, 32, { type:"mouseup" });
+ is(window.screenX, oldScreenX, "window not moved for non-chrome window screenX");
+ is(window.screenY, oldScreenY, "window not moved for non-chrome window screenY");
+ is(window.outerWidth, oldWidth, "window not moved for non-chrome window outerWidth");
+ is(window.outerHeight, oldHeight, "window not moved for non-chrome window outerHeight");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForFocus(testResizer);
+ ]]></script>
+
+ <resizer id="resizer" dir="bottomend" width="16" height="16"/>
+
+</window>
diff --git a/layout/xul/test/test_splitter.xul b/layout/xul/test/test_splitter.xul
new file mode 100644
index 000000000..697ad4be8
--- /dev/null
+++ b/layout/xul/test/test_splitter.xul
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+<?xml-stylesheet href="data:text/css, hbox { border: 1px solid red; } vbox { border: 1px solid green }" type="text/css"?>
+<!--
+XUL <splitter> collapsing tests
+-->
+<window title="XUL splitter collapsing tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ orient="horizontal">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function dragSplitter(offsetX, callback) {
+ var splitterWidth = splitter.boxObject.width;
+ synthesizeMouse(splitter, splitterWidth / 2, 2, {type: "mousedown"});
+ synthesizeMouse(splitter, splitterWidth / 2, 1, {type: "mousemove"});
+ SimpleTest.executeSoon(function() {
+ SimpleTest.is(splitter.getAttribute("state"), "dragging", "The splitter should be dragged");
+ synthesizeMouse(splitter, offsetX, 1, {type: "mousemove"});
+ synthesizeMouse(splitter, offsetX, 1, {type: "mouseup"});
+ SimpleTest.executeSoon(callback);
+ });
+ }
+
+ function shouldBeCollapsed(where) {
+ SimpleTest.is(splitter.getAttribute("state"), "collapsed", "The splitter should be collapsed");
+ SimpleTest.is(splitter.getAttribute("substate"), where, "The splitter should be collapsed " + where);
+ }
+
+ function shouldNotBeCollapsed() {
+ SimpleTest.is(splitter.getAttribute("state"), "", "The splitter should not be collapsed");
+ }
+
+ function runPass(rightCollapsed, leftCollapsed, callback) {
+ var containerWidth = container.boxObject.width;
+ var isRTL = getComputedStyle(splitter, null).direction == "rtl";
+ dragSplitter(containerWidth, function() {
+ if (rightCollapsed) {
+ shouldBeCollapsed(isRTL ? "before" : "after");
+ } else {
+ shouldNotBeCollapsed();
+ }
+ dragSplitter(-containerWidth * 2, function() {
+ if (leftCollapsed) {
+ shouldBeCollapsed(isRTL ? "after" : "before");
+ } else {
+ shouldNotBeCollapsed();
+ }
+ dragSplitter(containerWidth / 2, function() {
+ // the splitter should never be collapsed in the middle
+ shouldNotBeCollapsed();
+ callback();
+ });
+ });
+ });
+ }
+
+ var splitter, container;
+ function runLTRTests(callback) {
+ splitter = document.getElementById("ltr-splitter");
+ container = splitter.parentNode;
+ splitter.setAttribute("collapse", "before");
+ runPass(false, true, function() {
+ splitter.setAttribute("collapse", "after");
+ runPass(true, false, function() {
+ splitter.setAttribute("collapse", "both");
+ runPass(true, true, callback);
+ });
+ });
+ }
+
+ function runRTLTests(callback) {
+ splitter = document.getElementById("rtl-splitter");
+ container = splitter.parentNode;
+ splitter.setAttribute("collapse", "before");
+ runPass(true, false, function() {
+ splitter.setAttribute("collapse", "after");
+ runPass(false, true, function() {
+ splitter.setAttribute("collapse", "both");
+ runPass(true, true, callback);
+ });
+ });
+ }
+
+ function runTests() {
+ runLTRTests(function() {
+ runRTLTests(function() {
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ addLoadEvent(function() {SimpleTest.executeSoon(runTests);});
+ ]]></script>
+
+ <hbox style="max-width: 200px; height: 300px; direction: ltr;">
+ <vbox style="width: 100px; height: 300px;" flex="1"/>
+ <splitter id="ltr-splitter"/>
+ <vbox style="width: 100px; height: 300px;" flex="1"/>
+ </hbox>
+
+ <hbox style="max-width: 200px; height: 300px; direction: rtl;">
+ <vbox style="width: 100px; height: 300px;" flex="1"/>
+ <splitter id="rtl-splitter"/>
+ <vbox style="width: 100px; height: 300px;" flex="1"/>
+ </hbox>
+
+</window>
diff --git a/layout/xul/test/test_stack.xul b/layout/xul/test/test_stack.xul
new file mode 100644
index 000000000..781a6f298
--- /dev/null
+++ b/layout/xul/test/test_stack.xul
@@ -0,0 +1,327 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window align="start" title="XUL stack tests" onload="runTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- a * before an expected value means an offset from the right or bottom edge -->
+ <stack id="stack">
+ <hbox id="left-top" left="10" top="12" width="20" height="24"
+ expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36"
+ stackwidth="30" stackheight="36"/>
+ <hbox id="start-top" start="10" top="12" width="20" height="24"
+ expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36"
+ stackwidth="30" stackheight="36"/>
+ <hbox id="right-bottom" right="10" bottom="12" width="20" height="24"
+ expectedleft="*30" expectedtop="*36" expectedright="*10" expectedbottom="*12"
+ stackwidth="30" stackheight="36"/>
+ <hbox id="end-bottom" end="10" bottom="12" width="20" height="24"
+ expectedleft="*30" expectedtop="*36" expectedright="*10" expectedbottom="*12"
+ stackwidth="30" stackheight="36"/>
+ <hbox id="left-bottom" left="18" bottom="15" width="16" height="19"
+ expectedleft="18" expectedtop="*34" expectedright="34" expectedbottom="*15"
+ stackwidth="34" stackheight="34"/>
+ <hbox id="start-bottom" start="18" bottom="15" width="16" height="19"
+ expectedleft="18" expectedtop="*34" expectedright="34" expectedbottom="*15"
+ stackwidth="34" stackheight="34"/>
+ <hbox id="right-top" right="5" top="8" width="10" height="11"
+ expectedleft="*15" expectedtop="8" expectedright="*5" expectedbottom="19"
+ stackwidth="15" stackheight="19"/>
+ <hbox id="end-top" end="5" top="8" width="10" height="11"
+ expectedleft="*15" expectedtop="8" expectedright="*5" expectedbottom="19"
+ stackwidth="15" stackheight="19"/>
+ <hbox id="left-right" left="12" right="9" width="15" height="6"
+ expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0"
+ stackwidth="36" stackheight="6"/>
+ <hbox id="start-right" start="12" right="9" width="15" height="6"
+ expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0"
+ stackwidth="36" stackheight="6"/>
+ <hbox id="left-end" start="12" end="9" width="15" height="6"
+ expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0"
+ stackwidth="36" stackheight="6"/>
+ <hbox id="start-end" start="12" end="9" width="15" height="6"
+ expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0"
+ stackwidth="36" stackheight="6"/>
+ <hbox id="top-bottom" top="20" bottom="39" width="15" height="6"
+ expectedleft="0" expectedtop="20" expectedright="*0" expectedbottom="*39"
+ stackwidth="15" stackheight="65"/>
+ <hbox id="left-right-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;"
+ left="16" top="20" right="20" bottom="35" width="7" height="8"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="43" stackheight="63"/>
+ <hbox id="start-right-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;"
+ start="16" top="20" right="20" bottom="35" width="7" height="8"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="43" stackheight="63"/>
+ <hbox id="left-end-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;"
+ left="16" top="20" end="20" bottom="35" width="7" height="8"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="43" stackheight="63"/>
+ <hbox id="start-end-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;"
+ start="16" top="20" end="20" bottom="35" width="7" height="8"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="43" stackheight="63"/>
+ <hbox id="left-right-top-bottom-nosize" left="16" top="20" right="20" bottom="35"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="36" stackheight="55"/>
+ <hbox id="start-right-top-bottom-nosize" start="16" top="20" right="20" bottom="35"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="36" stackheight="55"/>
+ <hbox id="left-end-top-bottom-nosize" left="16" top="20" end="20" bottom="35"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="36" stackheight="55"/>
+ <hbox id="start-end-top-bottom-nosize" start="16" top="20" end="20" bottom="35"
+ expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35"
+ stackwidth="36" stackheight="55"/>
+ <hbox id="none" width="10" height="12" expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0"
+ stackwidth="10" stackheight="12"/>
+ <hbox id="none-nosize" expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0"
+ stackwidth="0" stackheight="0"/>
+ <hbox id="style-left-right-top-bottom"
+ style="left: 17px; top: 20px;" right="20" bottom="35" width="7" height="8"
+ expectedleft="*27" expectedtop="*43" expectedright="*20" expectedbottom="*35"
+ stackwidth="27" stackheight="43"/>
+ <hbox id="style-left-right-top-bottom-nosize"
+ style="left: 16px; top: 20px; right: 20px; bottom: 35px;"
+ expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0"
+ stackwidth="0" stackheight="0"/>
+ <hbox id="left-large-right" left="20" right="1000" height="6"
+ expectedleft="20" expectedtop="0" expectedright="20" expectedbottom="*0"
+ stackwidth="1020" stackheight="6"/>
+ <hbox id="left-top-with-margin" left="8" top="17" width="20" height="24"
+ style="margin: 1px 2px 3px 4px;"
+ expectedleft="12" expectedtop="18" expectedright="32" expectedbottom="42"
+ stackwidth="34" stackheight="45"/>
+ <hbox id="right-bottom-with-margin" right="6" bottom="15" width="10" height="14"
+ style="margin: 1px 2px 3px 4px;"
+ expectedleft="*18" expectedtop="*32" expectedright="*8" expectedbottom="*18"
+ stackwidth="22" stackheight="33"/>
+ <hbox id="left-top-right-bottom-with-margin" left="14" right="6" top="8" bottom="15" width="10" height="14"
+ style="margin: 1px 2px 3px 4px;"
+ expectedleft="18" expectedtop="9" expectedright="*8" expectedbottom="*18"
+ stackwidth="36" stackheight="41"/>
+ <hbox id="none-with-margin"
+ style="margin: 1px 2px 3px 4px;"
+ expectedleft="4" expectedtop="1" expectedright="*2" expectedbottom="*3"
+ stackwidth="6" stackheight="4"/>
+ </stack>
+
+ <stack id="stack-with-size" width="12" height="14">
+ <hbox id="left-top-with-stack-size" left="10" top="12" width="20" height="24"
+ expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36"/>
+ </stack>
+
+ <stack id="stack-with-start-end" width="30">
+ <hbox id="start-with-start-end" start="10" top="12" width="20" height="24"
+ expectedstart="10" expectedend="30"/>
+ <hbox id="end-width-start-end" end="5" top="12" width="20" height="24"
+ expectedstart="5" expectedend="25"/>
+ <hbox id="start-end-width-start-end" start="12" end="9" width="20" top="12" height="24"
+ expectedstart="12" expectedend="21"/>
+ </stack>
+
+ <stack id="stack-with-border"
+ style="border-left: 4px solid black; border-top: 2px solid black; border-right: 1px solid black; border-bottom: 3px solid black;">
+ <hbox id="left-top-with-border" left="10" top="14" width="20" height="24"
+ expectedleft="14" expectedtop="16" expectedright="34" expectedbottom="40"/>
+ <hbox id="start-top-with-border" start="10" top="14" width="20" height="24"
+ expectedleft="14" expectedtop="16" expectedright="34" expectedbottom="40"/>
+ <hbox id="right-bottom-with-border" right="5" bottom="8" width="6" height="10"
+ expectedleft="*12" expectedtop="*21" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="end-bottom-with-border" end="5" bottom="8" width="6" height="10"
+ expectedleft="*12" expectedtop="*21" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="left-top-right-bottom-with-border" left="12" right="5" top="18" bottom="8"
+ expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="start-top-right-bottom-with-border" start="12" right="5" top="18" bottom="8"
+ expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="left-top-end-bottom-with-border" left="12" end="5" top="18" bottom="8"
+ expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="start-top-end-bottom-with-border" start="12" end="5" top="18" bottom="8"
+ expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/>
+ <hbox id="none-with-with-border"
+ expectedleft="4" expectedtop="2" expectedright="*1" expectedbottom="*3"/>
+ </stack>
+
+ <stack id="stack-dyn"/>
+ <stack id="stack-dyn-sized" width="12" height="14"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml"/>
+
+ <script><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var stackRect;
+ var dynStack;
+
+ function compareSide(child, actual, side, dyn, direction)
+ {
+ var clientRect = child.getBoundingClientRect();
+ var vertical = (side == "top" || side == "bottom");
+ var expectedval = child.getAttribute("expected" + side);
+ if (expectedval.indexOf("*") == 0)
+ expectedval = (vertical ? stackRect.bottom : stackRect.right) - Number(expectedval.substring(1));
+ else if (direction == "rtl")
+ expectedval = (vertical ? stackRect.top : -stackRect.width + clientRect.right + clientRect.left) + Number(expectedval);
+ else
+ expectedval = (vertical ? stackRect.top : stackRect.left) + Number(expectedval);
+
+ is(+actual, expectedval, child.id + " " + side + (dyn ? " dynamic" : ""));
+ }
+
+ function runTest()
+ {
+ runTestForStack("stack", false);
+ runTestForStack("stack-with-size", false);
+ runTestForStartEndAttributes("stack-with-start-end", "ltr");
+ runTestForStartEndAttributes("stack-with-start-end", "rtl");
+
+ var stackWithSize = $("stack-with-size");
+
+ var sizedStackRect = stackWithSize.getBoundingClientRect();
+ is(sizedStackRect.width, 30, "stack size stretched width");
+ is(sizedStackRect.height, 36, "stack size stretched height");
+
+ // set -moz-stack-sizing: ignore and ensure that the stack does not grow
+ // to include the child
+ var item = $("left-top-with-stack-size");
+ item.style.MozStackSizing = "ignore";
+ var parent = item.parentNode;
+ parent.removeChild(item);
+ parent.appendChild(item);
+
+ sizedStackRect = stackWithSize.getBoundingClientRect();
+ is(sizedStackRect.width, 12, "stack size not stretched width");
+ is(sizedStackRect.height, 14, "stack size not stretched height");
+
+ testPositionChanges(stackWithSize, true);
+ item.style.MozStackSizing = "";
+ testPositionChanges(stackWithSize, false);
+
+ // now test adding stack children dynamically to ensure that
+ // the size of the stack adjusts accordingly
+ dynStack = $("stack-dyn");
+ runTestForStack("stack", true);
+
+ SimpleTest.finish();
+ }
+
+ function runTestForStartEndAttributes(stackid, aDirection)
+ {
+ // Change the direction of the layout to RTL to ensure start/end are
+ // working as expected
+ var stack = $(stackid);
+ stack.style.direction = aDirection;
+
+ var stackRect = stack.getBoundingClientRect();
+ var children = stack.childNodes;
+ for (var c = children.length - 1; c >= 0; c--) {
+ var child = children[c];
+
+ // do tests only for elements that have a rtl-enabled mode
+ if (!child.hasAttribute("start") && !child.hasAttribute("end"))
+ continue;
+
+ var childrect = child.getBoundingClientRect();
+ compareSide(child, childrect.right, "end", false, aDirection);
+ compareSide(child, childrect.left, "start", false, aDirection);
+ }
+
+ // Reset the direction
+ stack.style.direction = "ltr";
+ }
+
+ function runTestForStack(stackid, dyn)
+ {
+ var stack = $(stackid);
+ if (!dyn)
+ stackRect = stack.getBoundingClientRect();
+ var children = stack.childNodes;
+ for (var c = children.length - 1; c >= 0; c--) {
+ var child = children[c];
+ if (dyn) {
+ // for dynamic tests, get the size after appending the child as the
+ // stack size will be effected by it
+ dynStack.appendChild(child);
+ stackRect = dynStack.getBoundingClientRect();
+ is(String(stackRect.width), child.getAttribute("stackwidth"), child.id + " stack width" + (dyn ? " dynamic" : ""));
+ is(String(stackRect.height), child.getAttribute("stackheight"), child.id + " stack height" + (dyn ? " dynamic" : ""));
+ }
+
+ var childrect = child.getBoundingClientRect();
+ compareSide(child, childrect.left, "left", dyn);
+ compareSide(child, childrect.top, "top", dyn);
+ compareSide(child, childrect.right, "right", dyn);
+ compareSide(child, childrect.bottom, "bottom", dyn);
+ if (dyn)
+ dynStack.removeChild(child);
+ }
+ }
+
+ function testPositionChanges(stack, ignoreStackSizing)
+ {
+ var add = ignoreStackSizing ? " ignore stack sizing" : "";
+
+ // ensure that changing left/top/right/bottom/start/end works
+ var stackchild = document.getElementById("left-top-with-stack-size");
+ stackchild.left = 18;
+ is(stackchild.getBoundingClientRect().left, stack.getBoundingClientRect().left + 18, "left changed" + add);
+ is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 38, "left changed stack width" + add);
+
+ stackchild.left = "";
+ stackchild.setAttribute("start", "19");
+ is(stackchild.getBoundingClientRect().left, stack.getBoundingClientRect().left + 19, "left changed" + add);
+ is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 39, "left changed stack width" + add);
+ stackchild.removeAttribute("start");
+ stackchild.left = 18;
+
+ stackchild.top = 22;
+ is(stackchild.getBoundingClientRect().top, stack.getBoundingClientRect().top + 22, "top changed" + add);
+ is(stack.getBoundingClientRect().height, ignoreStackSizing ? 14 : 46, "left changed stack height" + add);
+
+ stackchild.setAttribute("right", "6");
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().left + 18, "right changed" + add);
+ // the width is only 12 pixels in ignoreStackSizing mode, so don't check the offset
+ // from the right edge in this case
+ if (!ignoreStackSizing)
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 6,
+ "right changed from right edge" + add);
+ is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 24, "right changed stack width" + add);
+
+ stackchild.removeAttribute("right");
+ stackchild.setAttribute("end", "7");
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().left + 18, "right changed" + add);
+ // the width is only 12 pixels in ignoreStackSizing mode, so don't check the offset
+ // from the right edge in this case
+ if (!ignoreStackSizing)
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 7,
+ "right changed from right edge" + add);
+ is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 25, "right changed stack width" + add);
+ stackchild.removeAttribute("end");
+ stackchild.setAttribute("right", "6");
+
+ stackchild.setAttribute("bottom", "9");
+ is(stackchild.getBoundingClientRect().bottom, stack.getBoundingClientRect().top + 22, "bottom changed" + add);
+ is(stack.getBoundingClientRect().height, ignoreStackSizing ? 14 : 31, "bottom changed stack height" + add);
+ if (!ignoreStackSizing)
+ is(stackchild.getBoundingClientRect().bottom, stack.getBoundingClientRect().bottom - 9,
+ "right changed from bottom edge" + add);
+
+ stackchild.left = "";
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 6, "right changed" + add);
+ is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 26, "right changed no left stack width" + add);
+
+ stackchild.removeAttribute("right");
+ is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right, "right cleared" + add);
+ is(stack.getBoundingClientRect().width, 12, "right cleared stack height" + add);
+
+ // reset the values
+ stackchild.removeAttribute("bottom");
+ stackchild.left = 10;
+ stackchild.top = 12;
+ }
+
+
+ ]]></script>
+</window>
diff --git a/layout/xul/test/test_submenuClose.xul b/layout/xul/test/test_submenuClose.xul
new file mode 100644
index 000000000..907736d99
--- /dev/null
+++ b/layout/xul/test/test_submenuClose.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1181560
+-->
+<window title="Mozilla Bug 1181560"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="SimpleTest.waitForFocus(nextTest, window)">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181560"
+ target="_blank">Mozilla Bug 1181560</a>
+ </body>
+
+ <vbox>
+ <menubar>
+ <menu id="menu" label="MyMenu">
+ <menupopup>
+ <menuitem label="A"/>
+ <menu id="b" label="B">
+ <menupopup>
+ <menuitem label="B1"/>
+ </menupopup>
+ </menu>
+ <menu id="c" label="C">
+ <menupopup>
+ <menuitem label="C1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 1181560 **/
+ SimpleTest.waitForExplicitFinish();
+
+ let menuB, menuC, mainMenu, menuBOpen, menuCOpen;
+ let menuBOpenCount = 0;
+
+ function handleBOpens() {
+ menuBOpenCount++;
+ menuBOpen = true;
+ ok(!menuCOpen, "Menu C should not be open when menu B has opened");
+ if (menuBOpenCount >= 2) {
+ SimpleTest.finish();
+ return;
+ }
+ sendKey("LEFT", window);
+ sendKey("DOWN", window);
+ sendKey("RIGHT", window);
+ }
+
+ function handleBCloses() {
+ menuBOpen = false;
+ }
+
+ function handleCOpens() {
+ menuCOpen = true;
+ ok(!menuBOpen, "Menu B should not be open when menu C has opened");
+ synthesizeMouseAtCenter(menuB, {}, window);
+ }
+
+ function handleCCloses() {
+ menuCOpen = false;
+ }
+
+ function nextTest(e) {
+ mainMenu = document.getElementById("menu");
+ menuB = document.getElementById("b");
+ menuC = document.getElementById("c");
+ menuB.firstChild.addEventListener("popupshown", handleBOpens, false);
+ menuB.firstChild.addEventListener("popuphidden", handleBCloses, false);
+ menuC.firstChild.addEventListener("popupshown", handleCOpens, false);
+ menuC.firstChild.addEventListener("popuphidden", handleCCloses, false);
+ mainMenu.addEventListener("popupshown", ev => {
+ synthesizeMouseAtCenter(menuB, {}, window);
+ });
+ mainMenu.open = true;
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/xul/test/test_windowminmaxsize.xul b/layout/xul/test/test_windowminmaxsize.xul
new file mode 100644
index 000000000..5909039cf
--- /dev/null
+++ b/layout/xul/test/test_windowminmaxsize.xul
@@ -0,0 +1,245 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Window Minimum and Maximum Size Tests" onload="nextTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<panel id="panel" onpopupshown="doPanelTest(this)" onpopuphidden="nextPopupTest(this)"
+ align="start" pack="start" style="-moz-appearance: none; margin: 0; border: 0; padding: 0;">
+ <resizer id="popupresizer" dir="bottomright" flex="1" width="60" height="60"
+ style="-moz-appearance: none; margin: 0; border: 0; padding: 0;"/>
+</panel>
+
+<script>
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var gTestId = -1;
+
+var prefix = "data:application/vnd.mozilla.xul+xml,<?xml-stylesheet href='chrome://global/skin' type='text/css'?><window " +
+ "xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' " +
+ "align='start' pack='start' style='-moz-appearance: none; margin: 0; padding: 0; border: 0; ";
+var suffix = "><resizer dir='bottomright' flex='1' width='150' height='150' style='-moz-appearance: none; margin: 0; border: 0; padding: 0;'/></window>";
+var titledpanel = "<panel noautohide='true' titlebar='normal' minwidth='120' minheight='140'/><label value='Test'/>";
+
+// width and height in the tests below specify the expected size of the window.
+// note, win8 has a minimum inner window size of around 122 pixels. Don't go below this on min-width tests.
+var tests = [
+ { testname: "unconstrained",
+ style: "", attrs: "",
+ width: 150, height: 150 },
+ { testname: "constraint min style",
+ style: "min-width: 180px; min-height: 210px;", attrs: "",
+ width: 180, height: 210 },
+ { testname: "constraint max style",
+ style: "max-width: 125px; max-height: 140px;", attrs: "",
+ width: 125, height: 140 },
+ { testname: "constraint min attributes",
+ style: "", attrs: "minwidth='240' minheight='220'",
+ width: 240, height: 220 },
+ { testname: "constraint min attributes with width and height set",
+ style: "", attrs: "width='190' height='220' minwidth='215' minheight='235'",
+ width: 215, height: 235 },
+ { testname: "constraint max attributes",
+ style: "", attrs: "maxwidth='125' maxheight='95'",
+ width: 125, height: 95 },
+ // this gets the inner width as <window minwidth='210'> makes the box 210 pixels wide
+ { testname: "constraint min width attribute only",
+ style: "", attrs: "minwidth='210'",
+ width: 210, height: 150 },
+ { testname: "constraint max width attribute only",
+ style: "", attrs: "maxwidth='128'",
+ width: 128, height: 150 },
+ { testname: "constraint max width attribute with minheight",
+ style: "", attrs: "maxwidth='195' width='230' height='120' minheight='180'",
+ width: 195, height: 180 },
+ { testname: "constraint minwidth, minheight, maxwidth and maxheight set",
+ style: "", attrs: "minwidth='120' maxwidth='480' minheight='110' maxheight='470'",
+ width: 150, height: 150, last: true }
+];
+
+var popupTests = [
+ { testname: "popup unconstrained",
+ width: 60, height: 60
+ },
+ { testname: "popup with minimum size",
+ minwidth: 150, minheight: 180,
+ width: 150, height: 180
+ },
+ { testname: "popup with maximum size",
+ maxwidth: 50, maxheight: 45,
+ width: 50, height: 45,
+ },
+ { testname: "popup with minimum and size",
+ minwidth: 80, minheight: 70, maxwidth: 250, maxheight: 220,
+ width: 80, height: 70, last: true
+ }
+];
+
+function nextTest()
+{
+ // Run through each of the tests above by opening a simple window with
+ // the attributes or style defined for that test. The comparisons will be
+ // done by windowOpened. gTestId holds the index into the tests array.
+ if (++gTestId >= tests.length) {
+ // Now do the popup tests
+ gTestId = -1;
+ SimpleTest.waitForFocus(function () { nextPopupTest(document.getElementById("panel")) } );
+ }
+ else {
+ tests[gTestId].window = window.open(prefix + tests[gTestId].style + "' " + tests[gTestId].attrs + suffix, "_blank", "chrome,resizable=yes");
+ SimpleTest.waitForFocus(windowOpened, tests[gTestId].window);
+ }
+}
+
+function windowOpened(otherWindow)
+{
+ // Check the width and the width plus one due to bug 696746.
+ ok(otherWindow.innerWidth == tests[gTestId].width ||
+ otherWindow.innerWidth == tests[gTestId].width + 1,
+ tests[gTestId].testname + " width of " + otherWindow.innerWidth + " matches " + tests[gTestId].width);
+ is(otherWindow.innerHeight, tests[gTestId].height, tests[gTestId].testname + " height");
+
+ // On the last test, try moving the resizer to a size larger than the maximum
+ // and smaller than the minimum. This test is only done on Mac as the other
+ // platforms use native resizing.
+ if ('last' in tests[gTestId] && (navigator.platform.indexOf("Mac") == 0)) {
+ var resizer = otherWindow.document.documentElement.firstChild;
+ synthesizeMouse(resizer, 4, 4, { type:"mousedown" }, otherWindow);
+ synthesizeMouse(resizer, 800, 800, { type:"mousemove" }, otherWindow);
+ is(otherWindow.innerWidth, 480, "Width after maximum resize");
+ is(otherWindow.innerHeight, 470, "Height after maximum resize");
+
+ synthesizeMouse(resizer, -100, -100, { type:"mousemove" }, otherWindow);
+ is(otherWindow.innerWidth, 120, "Width after minimum resize");
+ is(otherWindow.innerHeight, 110, "Height after minimum resize");
+
+ synthesizeMouse(resizer, 4, 4, { type:"mouseup" }, otherWindow);
+
+ // Change the minimum and maximum size and try resizing the window again.
+ otherWindow.document.documentElement.minWidth = 140;
+ otherWindow.document.documentElement.minHeight = 130;
+ otherWindow.document.documentElement.maxWidth = 380;
+ otherWindow.document.documentElement.maxHeight = 360;
+
+ synthesizeMouse(resizer, 4, 4, { type:"mousedown" }, otherWindow);
+ synthesizeMouse(resizer, 800, 800, { type:"mousemove" }, otherWindow);
+ is(otherWindow.innerWidth, 380, "Width after changed maximum resize");
+ is(otherWindow.innerHeight, 360, "Height after changed maximum resize");
+
+ synthesizeMouse(resizer, -100, -100, { type:"mousemove" }, otherWindow);
+ is(otherWindow.innerWidth, 140, "Width after changed minimum resize");
+ is(otherWindow.innerHeight, 130, "Height after changed minimum resize");
+
+ synthesizeMouse(resizer, 4, 4, { type:"mouseup" }, otherWindow);
+ }
+
+ otherWindow.close();
+ nextTest();
+}
+
+function doPanelTest(panel)
+{
+ var rect = panel.getBoundingClientRect();
+ is(rect.width, popupTests[gTestId].width, popupTests[gTestId].testname + " width");
+ is(rect.height, popupTests[gTestId].height, popupTests[gTestId].testname + " height");
+
+ if ('last' in popupTests[gTestId]) {
+ var resizer = document.getElementById("popupresizer");
+ synthesizeMouse(resizer, 4, 4, { type:"mousedown" });
+ synthesizeMouse(resizer, 800, 800, { type:"mousemove" });
+
+ rect = panel.getBoundingClientRect();
+ is(rect.width, 250, "Popup width after maximum resize");
+ is(rect.height, 220, "Popup height after maximum resize");
+
+ synthesizeMouse(resizer, -100, -100, { type:"mousemove" });
+
+ rect = panel.getBoundingClientRect();
+ is(rect.width, 80, "Popup width after minimum resize");
+ is(rect.height, 70, "Popup height after minimum resize");
+
+ synthesizeMouse(resizer, 4, 4, { type:"mouseup" });
+ }
+
+ panel.hidePopup();
+}
+
+function nextPopupTest(panel)
+{
+ if (++gTestId >= popupTests.length) {
+ // Next, check a panel that has a titlebar to ensure that it is accounted for
+ // properly in the size.
+ var titledPanelWindow = window.open(prefix + "'>" + titledpanel + "</window>", "_blank", "chrome,resizable=yes");
+ SimpleTest.waitForFocus(titledPanelWindowOpened, titledPanelWindow);
+ }
+ else {
+ function setattr(attr) {
+ if (attr in popupTests[gTestId])
+ panel.setAttribute(attr, popupTests[gTestId][attr]);
+ else
+ panel.removeAttribute(attr);
+ }
+ setattr("minwidth");
+ setattr("minheight");
+ setattr("maxwidth");
+ setattr("maxheight");
+
+ // Remove the flexibility as it causes the resizer to not shrink down
+ // when resizing.
+ if ("last" in popupTests[gTestId])
+ document.getElementById("popupresizer").removeAttribute("flex");
+
+ // Prevent event loop starvation as a result of popup events being
+ // synchronous. See bug 1131576.
+ SimpleTest.executeSoon(() => {
+ // Non-chrome shells require focus to open a popup.
+ SimpleTest.waitForFocus(() => { panel.openPopup() });
+ });
+ }
+}
+
+function titledPanelWindowOpened(panelwindow)
+{
+ var panel = panelwindow.document.documentElement.firstChild;
+ panel.openPopup();
+ panel.addEventListener("popupshown", () => doTitledPanelTest(panel), false);
+ panel.addEventListener("popuphidden", () => done(panelwindow), false);
+}
+
+function doTitledPanelTest(panel)
+{
+ var rect = panel.getBoundingClientRect();
+ is(rect.width, 120, "panel with titlebar width");
+ is(rect.height, 140, "panel with titlebar height");
+ panel.hidePopup();
+}
+
+function done(panelwindow)
+{
+ panelwindow.close();
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/layout/xul/test/window_resizer.xul b/layout/xul/test/window_resizer.xul
new file mode 100644
index 000000000..4e349d125
--- /dev/null
+++ b/layout/xul/test/window_resizer.xul
@@ -0,0 +1,113 @@
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ screenX="200" screenY="200" width="300" height="300"
+ onload="setTimeout(doTest, 0)">
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script><![CDATA[
+var is = window.opener.SimpleTest.is;
+
+function doTest() {
+ // from test_resizer.xul
+ var expectX = 200;
+ var expectY = 200;
+ var expectXMost = 500;
+ var expectYMost = 500;
+ var screenScale = expectX/window.screenX;
+ var root = document.documentElement;
+
+ var oldScreenX = window.screenX;
+ var oldScreenY = window.screenY;
+ var oldWidth = window.outerWidth;
+ var oldHeight = window.outerHeight;
+
+ function testResizer(dx, dy) {
+ var offset = 20;
+ var scale = 5;
+ // target the centre of the resizer
+ var offsetX = window.innerWidth/2 + (window.innerWidth/3)*dx;
+ var offsetY = window.innerHeight/2 + (window.innerHeight/3)*dy;
+
+ for (var mouseX = -1; mouseX <= 1; ++mouseX) {
+ for (var mouseY = -1; mouseY <= 1; ++mouseY) {
+ var newExpectX = expectX;
+ var newExpectXMost = expectXMost;
+ var newExpectY = expectY;
+ var newExpectYMost = expectYMost;
+ if (dx < 0) {
+ newExpectX += mouseX*scale;
+ } else if (dx > 0) {
+ newExpectXMost += mouseX*scale;
+ }
+ if (dy < 0) {
+ newExpectY += mouseY*scale;
+ } else if (dy > 0) {
+ newExpectYMost += mouseY*scale;
+ }
+
+ synthesizeMouse(root, offsetX, offsetY, { type:"mousedown" });
+ synthesizeMouse(root, offsetX + mouseX*scale, offsetY + mouseY*scale, { type:"mousemove" });
+ is(window.screenX*screenScale, newExpectX,
+ "Bad x for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
+ is(window.screenY*screenScale, newExpectY,
+ "Bad y for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
+ is(window.outerWidth, newExpectXMost - newExpectX,
+ "Bad width for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
+ is(window.outerHeight, newExpectYMost - newExpectY,
+ "Bad height for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
+
+ // move it back before we release! Adjust for any window movement
+ synthesizeMouse(root, offsetX - (newExpectX - expectX),
+ offsetY - (newExpectY - expectY), { type:"mousemove" });
+ synthesizeMouse(root, offsetX, offsetY, { type:"mouseup" });
+ }
+ }
+ }
+
+ testResizer(-1, -1);
+ testResizer(-1, 0);
+ testResizer(-1, 1);
+ testResizer(0, -1);
+ testResizer(0, 1);
+ testResizer(1, -1);
+ testResizer(1, 0);
+ testResizer(1, 1);
+
+ var resizers = document.getElementsByTagName("resizer");
+ Array.forEach(resizers, function (element) {
+ is(getComputedStyle(element, "").cursor,
+ element.getAttribute("expectedcursor"),
+ "cursor for " + element.dir);
+ });
+
+ // now check the cursors in rtl. The bottomend resizer
+ // should be reversed
+ document.getElementById("bottomend").setAttribute("rtl", "true");
+ Array.forEach(resizers, function (element) {
+ is(getComputedStyle(element, "").cursor,
+ element.dir == "bottomend" ? "sw-resize" :
+ element.getAttribute("expectedcursor"),
+ "cursor for " + element.dir);
+ });
+
+ window.close();
+ window.opener.lastResizerTest();
+}
+]]></script>
+ <hbox id="container" flex="1">
+ <vbox flex="1">
+ <resizer dir="topleft" expectedcursor="nw-resize" flex="1"/>
+ <resizer dir="left" expectedcursor="ew-resize" flex="1"/>
+ <resizer dir="bottomleft" expectedcursor="sw-resize" flex="1"/>
+ </vbox>
+ <vbox flex="1">
+ <resizer dir="top" expectedcursor="ns-resize" flex="1"/>
+ <resizer id="bottomend" dir="bottomend" expectedcursor="se-resize" flex="1"/>
+ <resizer dir="bottom" expectedcursor="ns-resize" flex="1"/>
+ </vbox>
+ <vbox flex="1">
+ <resizer dir="topright" expectedcursor="ne-resize" flex="1"/>
+ <resizer dir="right" expectedcursor="ew-resize" flex="1"/>
+ <resizer dir="bottomright" expectedcursor="se-resize" flex="1"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/layout/xul/test/window_resizer_element.xul b/layout/xul/test/window_resizer_element.xul
new file mode 100644
index 000000000..b0c58d1a1
--- /dev/null
+++ b/layout/xul/test/window_resizer_element.xul
@@ -0,0 +1,188 @@
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ align="start">
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script><![CDATA[
+var is = window.opener.SimpleTest.is;
+window.onerror = window.opener.onerror;
+
+const anchorPositions =
+ [ "before_start", "before_end", "after_start", "after_end",
+ "start_before", "start_after", "end_before", "end_after", "overlap", "screen"];
+var currentPosition;
+
+function testResizer(resizerid, noShrink, hResize, vResize, testid)
+{
+ var rect = document.getElementById(resizerid + "-container").getBoundingClientRect();
+ var resizer = document.getElementById(resizerid);
+ var resizerrect = resizer.getBoundingClientRect();
+
+ var originalX = resizerrect.left;
+ var originalY = resizerrect.top;
+
+ const scale = 20;
+ for (var mouseX = -1; mouseX <= 1; ++mouseX) {
+ for (var mouseY = -1; mouseY <= 1; ++mouseY) {
+ var expectedWidth = rect.width + hResize * mouseX * scale;
+ var expectedHeight = rect.height + vResize * mouseY * scale;
+
+ if (noShrink) {
+ if (mouseX == -1)
+ expectedWidth = rect.width;
+ if (mouseY == -1)
+ expectedHeight = rect.height;
+ }
+
+ synthesizeMouse(document.documentElement, originalX + 5, originalY + 5, { type:"mousedown" });
+ synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
+ originalY + 5 + mouseY * scale, { type:"mousemove" });
+
+ var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect();
+ is(Math.round(newrect.width), Math.round(expectedWidth), "resize element " + resizerid +
+ " " + testid + " width moving " + mouseX + "," + mouseY + ",,," + hResize);
+ is(Math.round(newrect.height), Math.round(expectedHeight), "resize element " + resizerid +
+ " " + testid + " height moving " + mouseX + "," + mouseY);
+ // release
+ synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
+ originalY + 5 + mouseY * scale, { type:"mouseup" });
+ // return to the original size
+ synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
+ originalY + 5 + mouseY * scale, { type:"dblclick" });
+ var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect();
+ is(Math.round(newrect.width), Math.round(rect.width), "resize element " + resizerid +
+ " " + testid + " doubleclicking to restore original size");
+ is(Math.round(newrect.height), Math.round(rect.height), "resize element " + resizerid +
+ " " + testid + " doubleclicking to restore original size");
+ }
+ }
+}
+
+function doTest() {
+ // first, check if a resizer with a element attribute set to an element that
+ // does not exist does not cause a problem
+ var resizer = document.getElementById("notfound");
+ synthesizeMouse(resizer, 5, 5, { type:"mousedown" });
+ synthesizeMouse(resizer, 10, 10, { type:"mousemove" });
+ synthesizeMouse(resizer, 5, 5, { type:"mouseup" });
+
+ testResizer("outside", true, 1, 1, "");
+ testResizer("html", true, 1, 1, "");
+ testResizer("inside", true, 1, 1, "");
+ testResizer("inside-large", false, 1, 1, "");
+ testResizer("inside-with-border", true, 1, 1, "");
+
+ document.getElementById("inside-popup-container").
+ openPopupAtScreen(Math.ceil(window.mozInnerScreenX) + 100, Math.ceil(window.mozInnerScreenY) + 100);
+}
+
+function popupShown(event)
+{
+ testResizer("inside-popup", false, 1, 1, "");
+ document.getElementById("inside-popup-container").id = "outside-popup-container";
+ testResizer("outside-popup", false, 1, 1, "");
+
+ var resizerrect = document.getElementById("inside-popup").getBoundingClientRect();
+ synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" });
+ synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" });
+
+ var isMac = (navigator.platform.indexOf("Mac") >= 0);
+ var popuprect = document.getElementById("outside-popup-container").getBoundingClientRect();
+ // subtract 3 due to space left for panel dropshadow
+ is(Math.ceil(window.mozInnerScreenX) + popuprect.right,
+ (isMac ? screen.availLeft + screen.availWidth : screen.left + screen.width) - 3, "resized to edge width");
+ is(Math.ceil(window.mozInnerScreenY) + popuprect.bottom,
+ (isMac ? screen.availTop + screen.availHeight : screen.top + screen.height) - 3, "resized to edge height");
+
+ resizerrect = document.getElementById("inside-popup").getBoundingClientRect();
+ synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" });
+
+ event.target.hidePopup();
+}
+
+function popupHidden()
+{
+ if (anchorPositions.length == 0) {
+ window.close();
+ window.opener.SimpleTest.finish();
+ return;
+ }
+
+ currentPosition = anchorPositions.shift();
+ var anchor = document.getElementById("anchor");
+ var popup = document.getElementById("anchored-panel-container");
+
+ if (currentPosition == "screen")
+ popup.openPopupAtScreen(window.screenX + 100, window.screenY + 100);
+ else
+ popup.openPopup(anchor, currentPosition);
+}
+
+function anchoredPopupShown(event)
+{
+ var leftAllowed = (currentPosition.indexOf("end_") == -1 && currentPosition.indexOf("_start") == -1);
+ var rightAllowed = (currentPosition.indexOf("start_") == -1 && currentPosition.indexOf("_end") == -1);
+ var topAllowed = (currentPosition.indexOf("after_") == -1 && currentPosition.indexOf("_before") == -1);
+ var bottomAllowed = (currentPosition.indexOf("before_") == -1 && currentPosition.indexOf("_after") == -1);
+
+ if (currentPosition == "overlap") {
+ leftAllowed = topAllowed = false;
+ rightAllowed = bottomAllowed = true;
+ }
+
+ var resizerTypes = [ "topleft", "top", "topright", "left", "right",
+ "bottomleft", "bottom", "bottomright", "bottomend" ];
+ for (var r = 0; r < resizerTypes.length; r++) {
+ var resizerType = resizerTypes[r];
+ var horiz = 0, vert = 0;
+ if (leftAllowed && resizerType.indexOf("left") >= 0) horiz = -1;
+ else if (rightAllowed && (resizerType.indexOf("right") >= 0 || resizerType == "bottomend")) horiz = 1;
+
+ if (topAllowed && resizerType.indexOf("top") >= 0) vert = -1;
+ else if (bottomAllowed && resizerType.indexOf("bottom") >= 0) vert = 1;
+
+ document.getElementById("anchored-panel").dir = resizerType;
+ testResizer("anchored-panel", false, horiz, vert, currentPosition + " " + resizerType);
+ }
+
+ event.target.hidePopup();
+}
+
+window.opener.SimpleTest.waitForFocus(doTest, window);
+]]></script>
+
+<resizer id="outside" dir="bottomend" element="outside-container"/>
+<resizer id="notfound" dir="bottomend" element="nothing"/>
+<hbox id="outside-container">
+ <hbox minwidth="46" minheight="39"/>
+</hbox>
+<html:div id="html-container" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:button>One</html:button><html:br/>
+ <resizer id="html" dir="bottomend" element="_parent"/>
+</html:div>
+<hbox id="anchor" align="start" style="margin-left: 100px;">
+ <hbox id="inside-container" align="start">
+ <hbox minwidth="45" minheight="41"/>
+ <resizer id="inside" dir="bottomend" element="_parent"/>
+ </hbox>
+ <hbox id="inside-large-container" width="70" height="70" align="start">
+ <resizer id="inside-large" dir="bottomend" element="_parent"/>
+ </hbox>
+ <hbox id="inside-with-border-container" style="border: 5px solid red; padding: 2px; margin: 2px;" align="start">
+ <hbox minwidth="35" minheight="30"/>
+ <resizer id="inside-with-border" dir="bottomend" element="_parent"/>
+ </hbox>
+</hbox>
+
+<panel id="inside-popup-container" align="start" onpopupshown="popupShown(event)" onpopuphidden="popupHidden()">
+ <resizer id="inside-popup" dir="bottomend"/>
+ <hbox width="50" height="50" flex="1"/>
+</panel>
+<resizer id="outside-popup" dir="bottomend" element="outside-popup-container"/>
+
+<panel id="anchored-panel-container" align="start" onpopupshown="anchoredPopupShown(event)"
+ onpopuphidden="popupHidden()">
+ <hbox width="50" height="50" flex="1"/>
+ <resizer id="anchored-panel" width="20" height="20"/>
+</panel>
+
+</window>
diff --git a/layout/xul/tree/TreeBoxObject.cpp b/layout/xul/tree/TreeBoxObject.cpp
new file mode 100644
index 000000000..2265d9ee5
--- /dev/null
+++ b/layout/xul/tree/TreeBoxObject.cpp
@@ -0,0 +1,695 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TreeBoxObject.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMXULElement.h"
+#include "nsIScriptableRegion.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsTreeContentView.h"
+#include "nsITreeSelection.h"
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/TreeBoxObjectBinding.h"
+#include "nsITreeColumns.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TreeBoxObject, BoxObject,
+ mView)
+
+NS_IMPL_ADDREF_INHERITED(TreeBoxObject, BoxObject)
+NS_IMPL_RELEASE_INHERITED(TreeBoxObject, BoxObject)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TreeBoxObject)
+ NS_INTERFACE_MAP_ENTRY(nsITreeBoxObject)
+NS_INTERFACE_MAP_END_INHERITING(BoxObject)
+
+void
+TreeBoxObject::Clear()
+{
+ ClearCachedValues();
+
+ // Drop the view's ref to us.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr); // Break the circular ref between the view and us.
+ }
+ mView = nullptr;
+
+ BoxObject::Clear();
+}
+
+
+TreeBoxObject::TreeBoxObject()
+ : mTreeBody(nullptr)
+{
+}
+
+TreeBoxObject::~TreeBoxObject()
+{
+}
+
+static nsIContent* FindBodyElement(nsIContent* aParent)
+{
+ mozilla::dom::FlattenedChildIterator iter(aParent);
+ for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) {
+ mozilla::dom::NodeInfo *ni = content->NodeInfo();
+ if (ni->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) {
+ return content;
+ } else if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ // There are nesting tree elements. Only the innermost should
+ // find the treechilren.
+ return nullptr;
+ } else if (content->IsElement() &&
+ !ni->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) {
+ nsIContent* result = FindBodyElement(content);
+ if (result)
+ return result;
+ }
+ }
+
+ return nullptr;
+}
+
+nsTreeBodyFrame*
+TreeBoxObject::GetTreeBodyFrame(bool aFlushLayout)
+{
+ // Make sure our frames are up to date, and layout as needed. We
+ // have to do this before checking for our cached mTreeBody, since
+ // it might go away on style flush, and in any case if aFlushLayout
+ // is true we need to make sure to flush no matter what.
+ // XXXbz except that flushing style when we were not asked to flush
+ // layout here breaks things. See bug 585123.
+ nsIFrame* frame = nullptr;
+ if (aFlushLayout) {
+ frame = GetFrame(aFlushLayout);
+ if (!frame)
+ return nullptr;
+ }
+
+ if (mTreeBody) {
+ // Have one cached already.
+ return mTreeBody;
+ }
+
+ if (!aFlushLayout) {
+ frame = GetFrame(aFlushLayout);
+ if (!frame)
+ return nullptr;
+ }
+
+ // Iterate over our content model children looking for the body.
+ nsCOMPtr<nsIContent> content = FindBodyElement(frame->GetContent());
+ if (!content)
+ return nullptr;
+
+ frame = content->GetPrimaryFrame();
+ if (!frame)
+ return nullptr;
+
+ // Make sure that the treebodyframe has a pointer to |this|.
+ nsTreeBodyFrame *treeBody = do_QueryFrame(frame);
+ NS_ENSURE_TRUE(treeBody && treeBody->GetTreeBoxObject() == this, nullptr);
+
+ mTreeBody = treeBody;
+ return mTreeBody;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetView(nsITreeView * *aView)
+{
+ if (!mTreeBody) {
+ if (!GetTreeBodyFrame()) {
+ // Don't return an uninitialised view
+ *aView = nullptr;
+ return NS_OK;
+ }
+
+ if (mView)
+ // Our new frame needs to initialise itself
+ return mTreeBody->GetView(aView);
+ }
+ if (!mView) {
+ nsCOMPtr<nsIDOMXULElement> xulele = do_QueryInterface(mContent);
+ if (xulele) {
+ // See if there is a XUL tree builder associated with the element
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xulele->GetBuilder(getter_AddRefs(builder));
+ mView = do_QueryInterface(builder);
+
+ if (!mView) {
+ // No tree builder, create a tree content view.
+ nsresult rv = NS_NewTreeContentView(getter_AddRefs(mView));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Initialise the frame and view
+ mTreeBody->SetView(mView);
+ }
+ }
+ NS_IF_ADDREF(*aView = mView);
+ return NS_OK;
+}
+
+already_AddRefed<nsITreeView>
+TreeBoxObject::GetView() {
+ nsCOMPtr<nsITreeView> view;
+ GetView(getter_AddRefs(view));
+ return view.forget();
+}
+
+static bool
+CanTrustView(nsISupports* aValue)
+{
+ // Untrusted content is only allowed to specify known-good views
+ if (nsContentUtils::IsCallerChrome())
+ return true;
+ nsCOMPtr<nsINativeTreeView> nativeTreeView = do_QueryInterface(aValue);
+ if (!nativeTreeView || NS_FAILED(nativeTreeView->EnsureNative())) {
+ // XXX ERRMSG need a good error here for developers
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP TreeBoxObject::SetView(nsITreeView * aView)
+{
+ if (!CanTrustView(aView))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ mView = aView;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ body->SetView(aView);
+
+ return NS_OK;
+}
+
+void TreeBoxObject::SetView(nsITreeView* aView, ErrorResult& aRv)
+{
+ aRv = SetView(aView);
+}
+
+bool TreeBoxObject::Focused()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetFocused();
+ return false;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetFocused(bool* aFocused)
+{
+ *aFocused = Focused();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::SetFocused(bool aFocused)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->SetFocused(aFocused);
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetTreeBody(nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetTreeBody(aElement);
+ return NS_OK;
+}
+
+already_AddRefed<Element>
+TreeBoxObject::GetTreeBody()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetTreeBody(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<nsTreeColumns>
+TreeBoxObject::GetColumns()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->Columns();
+ return nullptr;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetColumns(nsITreeColumns** aColumns)
+{
+ *aColumns = GetColumns().take();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::RowHeight()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowHeight();
+ return 0;
+}
+
+int32_t TreeBoxObject::RowWidth()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowWidth();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetRowHeight(int32_t* aRowHeight)
+{
+ *aRowHeight = RowHeight();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetRowWidth(int32_t *aRowWidth)
+{
+ *aRowWidth = RowWidth();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetFirstVisibleRow()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->FirstVisibleRow();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetFirstVisibleRow(int32_t *aFirstVisibleRow)
+{
+ *aFirstVisibleRow = GetFirstVisibleRow();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetLastVisibleRow()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->LastVisibleRow();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetLastVisibleRow(int32_t *aLastVisibleRow)
+{
+ *aLastVisibleRow = GetLastVisibleRow();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::HorizontalPosition()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetHorizontalPosition();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetHorizontalPosition(int32_t *aHorizontalPosition)
+{
+ *aHorizontalPosition = HorizontalPosition();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetPageLength()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->PageLength();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetPageLength(int32_t *aPageLength)
+{
+ *aPageLength = GetPageLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetSelectionRegion(nsIScriptableRegion **aRegion)
+{
+ *aRegion = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetSelectionRegion(aRegion);
+ return NS_OK;
+}
+
+already_AddRefed<nsIScriptableRegion>
+TreeBoxObject::SelectionRegion()
+{
+ nsCOMPtr<nsIScriptableRegion> region;
+ GetSelectionRegion(getter_AddRefs(region));
+ return region.forget();
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EnsureRowIsVisible(int32_t aRow)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EnsureRowIsVisible(aRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EnsureCellIsVisible(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToRow(int32_t aRow)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame(true);
+ if (body)
+ return body->ScrollToRow(aRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollByLines(int32_t aNumLines)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollByLines(aNumLines);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollByPages(int32_t aNumPages)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollByPages(aNumPages);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToCell(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToColumn(nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToColumn(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToHorizontalPosition(int32_t aHorizontalPosition)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToHorizontalPosition(aHorizontalPosition);
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::Invalidate()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->Invalidate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateColumn(nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateColumn(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateRow(int32_t aIndex)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateRow(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateCell(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateRange(int32_t aStart, int32_t aEnd)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateRange(aStart, aEnd);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateColumnRange(aStart, aEnd, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetRowAt(int32_t x, int32_t y, int32_t *aRow)
+{
+ *aRow = 0;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetRowAt(x, y, aRow);
+ return NS_OK;
+}
+
+int32_t
+TreeBoxObject::GetRowAt(int32_t x, int32_t y)
+{
+ int32_t row;
+ GetRowAt(x, y, &row);
+ return row;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetCellAt(int32_t aX, int32_t aY, int32_t *aRow,
+ nsITreeColumn** aCol, nsAString& aChildElt)
+{
+ *aRow = 0;
+ *aCol = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ nsAutoCString element;
+ nsresult retval = body->GetCellAt(aX, aY, aRow, aCol, element);
+ CopyUTF8toUTF16(element, aChildElt);
+ return retval;
+ }
+ return NS_OK;
+}
+
+void
+TreeBoxObject::GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsITreeColumn> col;
+ GetCellAt(x, y, &aRetVal.mRow, getter_AddRefs(col), aRetVal.mChildElt);
+ aRetVal.mCol = col.forget().downcast<nsTreeColumn>();
+}
+
+void
+TreeBoxObject::GetCellAt(JSContext* cx,
+ int32_t x, int32_t y,
+ JS::Handle<JSObject*> rowOut,
+ JS::Handle<JSObject*> colOut,
+ JS::Handle<JSObject*> childEltOut,
+ ErrorResult& aRv)
+{
+ int32_t row;
+ nsITreeColumn* col;
+ nsAutoString childElt;
+ GetCellAt(x, y, &row, &col, childElt);
+
+ JS::Rooted<JS::Value> v(cx);
+
+ if (!ToJSValue(cx, row, &v) ||
+ !JS_SetProperty(cx, rowOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!dom::WrapObject(cx, col, &v) ||
+ !JS_SetProperty(cx, colOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!ToJSValue(cx, childElt, &v) ||
+ !JS_SetProperty(cx, childEltOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsAString& aElement,
+ int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight)
+{
+ *aX = *aY = *aWidth = *aHeight = 0;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ NS_ConvertUTF16toUTF8 element(aElement);
+ if (body)
+ return body->GetCoordsForCellItem(aRow, aCol, element, aX, aY, aWidth, aHeight);
+ return NS_OK;
+}
+
+already_AddRefed<DOMRect>
+TreeBoxObject::GetCoordsForCellItem(int32_t row, nsTreeColumn& col, const nsAString& element, ErrorResult& aRv)
+{
+ int32_t x, y, w, h;
+ GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h);
+ RefPtr<DOMRect> rect = new DOMRect(mContent, x, y, w, h);
+ return rect.forget();
+}
+
+void
+TreeBoxObject::GetCoordsForCellItem(JSContext* cx,
+ int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ JS::Handle<JSObject*> xOut,
+ JS::Handle<JSObject*> yOut,
+ JS::Handle<JSObject*> widthOut,
+ JS::Handle<JSObject*> heightOut,
+ ErrorResult& aRv)
+{
+ int32_t x, y, w, h;
+ GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h);
+ JS::Rooted<JS::Value> v(cx, JS::Int32Value(x));
+ if (!JS_SetProperty(cx, xOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(y);
+ if (!JS_SetProperty(cx, yOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(w);
+ if (!JS_SetProperty(cx, widthOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(h);
+ if (!JS_SetProperty(cx, heightOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+TreeBoxObject::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *aIsCropped)
+{
+ *aIsCropped = false;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->IsCellCropped(aRow, aCol, aIsCropped);
+ return NS_OK;
+}
+
+bool
+TreeBoxObject::IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv)
+{
+ bool ret;
+ aRv = IsCellCropped(row, col, &ret);
+ return ret;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::RowCountChanged(int32_t aIndex, int32_t aDelta)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowCountChanged(aIndex, aDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::BeginUpdateBatch()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->BeginUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EndUpdateBatch()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EndUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ClearStyleAndImageCaches()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ClearStyleAndImageCaches();
+ return NS_OK;
+}
+
+void
+TreeBoxObject::ClearCachedValues()
+{
+ mTreeBody = nullptr;
+}
+
+JSObject*
+TreeBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return TreeBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewTreeBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new TreeBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/tree/TreeBoxObject.h b/layout/xul/tree/TreeBoxObject.h
new file mode 100644
index 000000000..d997b5a63
--- /dev/null
+++ b/layout/xul/tree/TreeBoxObject.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TreeBoxObject_h
+#define mozilla_dom_TreeBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+#include "nsITreeView.h"
+#include "nsITreeBoxObject.h"
+
+class nsTreeBodyFrame;
+class nsTreeColumn;
+class nsTreeColumns;
+
+namespace mozilla {
+namespace dom {
+
+struct TreeCellInfo;
+class DOMRect;
+
+class TreeBoxObject final : public BoxObject,
+ public nsITreeBoxObject
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TreeBoxObject, BoxObject)
+ NS_DECL_NSITREEBOXOBJECT
+
+ TreeBoxObject();
+
+ nsTreeBodyFrame* GetTreeBodyFrame(bool aFlushLayout = false);
+ nsTreeBodyFrame* GetCachedTreeBodyFrame() { return mTreeBody; }
+
+ //NS_PIBOXOBJECT interfaces
+ virtual void Clear() override;
+ virtual void ClearCachedValues() override;
+
+ // WebIDL
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<nsTreeColumns> GetColumns();
+
+ already_AddRefed<nsITreeView> GetView();
+
+ void SetView(nsITreeView* arg, ErrorResult& aRv);
+
+ bool Focused();
+
+ already_AddRefed<Element> GetTreeBody();
+
+ int32_t RowHeight();
+
+ int32_t RowWidth();
+
+ int32_t HorizontalPosition();
+
+ already_AddRefed<nsIScriptableRegion> SelectionRegion();
+
+ int32_t GetFirstVisibleRow();
+
+ int32_t GetLastVisibleRow();
+
+ int32_t GetPageLength();
+
+ int32_t GetRowAt(int32_t x, int32_t y);
+
+ void GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv);
+
+ already_AddRefed<DOMRect> GetCoordsForCellItem(int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ ErrorResult& aRv);
+
+ bool IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv);
+
+ // Deprecated APIs from old IDL
+ void GetCellAt(JSContext* cx,
+ int32_t x, int32_t y,
+ JS::Handle<JSObject*> rowOut,
+ JS::Handle<JSObject*> colOut,
+ JS::Handle<JSObject*> childEltOut,
+ ErrorResult& aRv);
+
+ void GetCoordsForCellItem(JSContext* cx,
+ int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ JS::Handle<JSObject*> xOut,
+ JS::Handle<JSObject*> yOut,
+ JS::Handle<JSObject*> widthOut,
+ JS::Handle<JSObject*> heightOut,
+ ErrorResult& aRv);
+
+ // Same signature (except for nsresult return type) as the XPIDL impls
+ // void Invalidate();
+ // void BeginUpdateBatch();
+ // void EndUpdateBatch();
+ // void ClearStyleAndImageCaches();
+ // void SetFocused(bool arg);
+ // void EnsureRowIsVisible(int32_t index);
+ // void EnsureCellIsVisible(int32_t row, nsITreeColumn* col);
+ // void ScrollToRow(int32_t index);
+ // void ScrollByLines(int32_t numLines);
+ // void ScrollByPages(int32_t numPages);
+ // void ScrollToCell(int32_t row, nsITreeColumn* col);
+ // void ScrollToColumn(nsITreeColumn* col);
+ // void ScrollToHorizontalPosition(int32_t horizontalPosition);
+ // void InvalidateColumn(nsITreeColumn* col);
+ // void InvalidateRow(int32_t index);
+ // void InvalidateCell(int32_t row, nsITreeColumn* col);
+ // void InvalidateRange(int32_t startIndex, int32_t endIndex);
+ // void InvalidateColumnRange(int32_t startIndex, int32_t endIndex, nsITreeColumn* col);
+ // void RowCountChanged(int32_t index, int32_t count);
+
+protected:
+ nsTreeBodyFrame* mTreeBody;
+ nsCOMPtr<nsITreeView> mView;
+
+private:
+ ~TreeBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/layout/xul/tree/crashtests/307298-1.xul b/layout/xul/tree/crashtests/307298-1.xul
new file mode 100644
index 000000000..57396755a
--- /dev/null
+++ b/layout/xul/tree/crashtests/307298-1.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var tree = document.getElementById('tree'), treeitem = document.getElementById('treeitem'); tree.parentNode.insertBefore(treeitem, tree);">
+
+<tree flex="1" id="tree">
+ <treecols>
+ <treecol id="name" label="Name" primary="true" flex="1"/>
+ </treecols>
+
+ <treechildren>
+ <treeitem id="treeitem">
+ <treerow>
+ <treecell label="Click the button below to crash"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/309732-1.xul b/layout/xul/tree/crashtests/309732-1.xul
new file mode 100644
index 000000000..df955e14e
--- /dev/null
+++ b/layout/xul/tree/crashtests/309732-1.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+
+ <script>
+ function boom()
+ {
+ document.documentElement.appendChild(document.getElementById("TC"));
+ document.documentElement.appendChild(document.getElementById("TI"));
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1">
+ <treecols>
+ <treecol label="Name"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI">
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/309732-2.xul b/layout/xul/tree/crashtests/309732-2.xul
new file mode 100644
index 000000000..9c65e7379
--- /dev/null
+++ b/layout/xul/tree/crashtests/309732-2.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+ <script>
+ function boom()
+ {
+ document.documentElement.appendChild(document.getElementById('TC'));
+ document.getElementById('TI').hidden = false;
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+
+ <tree flex="1">
+ <treecols>
+ <treecol label="Name" flex="1"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem>
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+ <treeitem id="TI" hidden="true"/>
+</window>
diff --git a/layout/xul/tree/crashtests/366583-1.xul b/layout/xul/tree/crashtests/366583-1.xul
new file mode 100644
index 000000000..db37b444e
--- /dev/null
+++ b/layout/xul/tree/crashtests/366583-1.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom1();"
+ class="reftest-wait">
+
+<script>
+
+var tree;
+
+function boom1()
+{
+ tree = document.getElementById("tree");
+ tree.style.position = "fixed";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ tree.style.overflow = "visible";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+<tree rows="6" id="tree" style="display: list-item; overflow: auto; visibility: collapse;">
+ <treecols>
+ <treecol id="firstname" label="First Name" primary="true" flex="3"/>
+ <treecol id="lastname" label="Last Name" flex="7"/>
+ </treecols>
+
+ <treechildren>
+ <treeitem container="true" open="true">
+ <treerow>
+ <treecell label="Foo"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+
+</window>
diff --git a/layout/xul/tree/crashtests/380217-1.xul b/layout/xul/tree/crashtests/380217-1.xul
new file mode 100644
index 000000000..b834f7e1f
--- /dev/null
+++ b/layout/xul/tree/crashtests/380217-1.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.style.content = '\'a\'';">
+
+<html:style>
+* { position: fixed; }
+</html:style>
+
+<tree rows="6">
+ <treecols>
+ <treecol id="firstname" label="First Name" primary="true"/>
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/382444-1-inner.html b/layout/xul/tree/crashtests/382444-1-inner.html
new file mode 100644
index 000000000..d59a2e787
--- /dev/null
+++ b/layout/xul/tree/crashtests/382444-1-inner.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsINodeInfo::Equals] with underflow event, tree stuff and removing window</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%3Ctree%20style%3D%22overflow%3A%20auto%3B%20display%3A%20-moz-inline-box%3B%22%3E%0A%3Ctreeitem%20style%3D%22overflow%3A%20scroll%3B%20display%3A%20table-cell%3B%22%3E%0A%3Ctreechildren%20style%3D%22%20display%3A%20table-row%3B%22%3E%0A%3Ctreeitem%20id%3D%22a%22%20style%3D%22display%3A%20table-cell%3B%22%3E%0A%3C/treeitem%3E%0A%3C/treechildren%3E%0A%3C/treeitem%3E%0A%0A%3C/tree%3E%0A%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Afunction%20doe%28%29%20%7B%0Adocument.getElementById%28%27a%27%29.parentNode.removeChild%28document.getElementById%28%27a%27%29%29%3B%0A%7D%0AsetTimeout%28doe%2C%20100%29%3B%0Adocument.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0Awindow.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3C/window%3E" id="content"></iframe>
+
+<script>
+function doe() {
+window.location.reload();
+}
+setTimeout(doe, 500);
+</script>
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/382444-1.html b/layout/xul/tree/crashtests/382444-1.html
new file mode 100644
index 000000000..8926cf16d
--- /dev/null
+++ b/layout/xul/tree/crashtests/382444-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="382444-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/391178-1.xhtml b/layout/xul/tree/crashtests/391178-1.xhtml
new file mode 100644
index 000000000..0f4b16cd9
--- /dev/null
+++ b/layout/xul/tree/crashtests/391178-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+var ccc;
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var hbox = document.createElementNS(XUL_NS, 'hbox');
+ var tree = document.createElementNS(XUL_NS, 'tree');
+ var treecol = document.createElementNS(XUL_NS, 'treecol');
+
+ ccc = document.getElementById("ccc");
+
+ ccc.style.position = "fixed";
+
+ hbox.appendChild(treecol);
+ tree.appendChild(hbox);
+ ccc.appendChild(tree);
+
+ setTimeout(boom2, 200);
+}
+
+function boom2()
+{
+ ccc.style.position = "";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="ccc">
+</div>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/391178-2.xul b/layout/xul/tree/crashtests/391178-2.xul
new file mode 100644
index 000000000..491fbe77b
--- /dev/null
+++ b/layout/xul/tree/crashtests/391178-2.xul
@@ -0,0 +1,20 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<tree id="a" style="position: fixed;">
+ <box style=" display: -moz-box; position: fixed;">
+ <treecol style=" display: -moz-box;"/>
+ </box>
+ <box style="position: fixed;">
+ <treechildren style="display: -moz-box; position: absolute;"/>
+ </box>
+</tree>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function removestyles(){
+ document.getElementById('a').removeAttribute('style');
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(removestyles, 100);
+</script>
+</window>
diff --git a/layout/xul/tree/crashtests/393665-1.xul b/layout/xul/tree/crashtests/393665-1.xul
new file mode 100644
index 000000000..6fb5ec0c9
--- /dev/null
+++ b/layout/xul/tree/crashtests/393665-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <treechildren style="display: block" />
+</window>
diff --git a/layout/xul/tree/crashtests/399227-1.xul b/layout/xul/tree/crashtests/399227-1.xul
new file mode 100644
index 000000000..bfc381892
--- /dev/null
+++ b/layout/xul/tree/crashtests/399227-1.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+
+ <script>
+ function boom()
+ {
+ var tree = document.getElementById("thetree");
+ var selection = tree.view.selection;
+
+ selection.select(0);
+ tree.parentNode.removeChild(tree);
+
+ // This is expected to throw an error (it used to crash).
+ try {
+ selection.rangedSelect(1, 1, false);
+ }
+ catch (ex) {}
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1" id="thetree">
+ <treecols>
+ <treecol label="Name"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI1">
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="TI2">
+ <treerow>
+ <treecell label="Second treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/399227-2.xul b/layout/xul/tree/crashtests/399227-2.xul
new file mode 100644
index 000000000..55665ec47
--- /dev/null
+++ b/layout/xul/tree/crashtests/399227-2.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30);">
+
+
+ <script>
+ function boom()
+ {
+ var tree = document.getElementById("thetree");
+ var selection = tree.view.selection;
+ var treecolumn0 = tree.columns[0];
+ var treecolumn1 = tree.columns[1];
+
+ selection.select(0);
+ selection.currentColumn = treecolumn0;
+ tree.parentNode.removeChild(tree);
+
+ // This is expected to throw an error (it used to crash).
+ try {
+ selection.currentColumn = treecolumn1;
+ }
+ catch (ex) {}
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1" id="thetree" seltype="cell">
+ <treecols>
+ <treecol label="Name"/>
+ <treecol label="Test"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI1">
+ <treerow>
+ <treecell label="First treecell"/>
+ <treecell label="Second treecell"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="TI2">
+ <treerow>
+ <treecell label="Third treecell"/>
+ <treecell label="Fourth treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/399692-1.xhtml b/layout/xul/tree/crashtests/399692-1.xhtml
new file mode 100644
index 000000000..97eec2674
--- /dev/null
+++ b/layout/xul/tree/crashtests/399692-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+</head>
+<body>
+
+<xul:treechildren style="display: inline;" />
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/399715-1.xhtml b/layout/xul/tree/crashtests/399715-1.xhtml
new file mode 100644
index 000000000..ea0a20cfa
--- /dev/null
+++ b/layout/xul/tree/crashtests/399715-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<body style="float: right;" onload="document.body.style.cssFloat = '';">
+
+<xul:tree><xul:hbox><xul:treecol /></xul:hbox></xul:tree>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/409807-1.xul b/layout/xul/tree/crashtests/409807-1.xul
new file mode 100644
index 000000000..a3af3da41
--- /dev/null
+++ b/layout/xul/tree/crashtests/409807-1.xul
@@ -0,0 +1,25 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var tree = document.getElementById("tree");
+ var tc = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "treechildren");
+
+ document.addEventListener("DOMAttrModified", m, false);
+
+ tree.appendChild(tc);
+
+ function m()
+ {
+ document.removeEventListener("DOMAttrModified", m, false);
+ tree.removeChild(tc);
+ }
+}
+
+</script>
+
+<tree id="tree" />
+
+</window>
diff --git a/layout/xul/tree/crashtests/414170-1.xul b/layout/xul/tree/crashtests/414170-1.xul
new file mode 100644
index 000000000..f3bc1d134
--- /dev/null
+++ b/layout/xul/tree/crashtests/414170-1.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var option = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+ document.getElementById("tc").appendChild(option);
+}
+
+</script>
+
+<tree><treechildren id="tc"><hbox/></treechildren></tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/430394-1.xul b/layout/xul/tree/crashtests/430394-1.xul
new file mode 100644
index 000000000..63f4c3780
--- /dev/null
+++ b/layout/xul/tree/crashtests/430394-1.xul
@@ -0,0 +1,8 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<tree id="a" style="content: 't';" rows="-2">
+ <menuitem id="b" onoverflow="event.currentTarget.parentNode.removeAttribute('style')">
+ <treechildren style="display: block;" onoverflow="event.target.parentNode.removeChild(event.target)" />
+ <treechildren style="display: block;" ordinal="0.5"/>
+ </menuitem>
+</tree>
+</window> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/454186-1.xul b/layout/xul/tree/crashtests/454186-1.xul
new file mode 100644
index 000000000..edf266e43
--- /dev/null
+++ b/layout/xul/tree/crashtests/454186-1.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<tree flex="1">
+ <treecols>
+ <treecol label="test" flex="1" type="progressmeter" />
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell value="50" mode="normal" />
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell mode="undetermined" />
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/479931-1.xhtml b/layout/xul/tree/crashtests/479931-1.xhtml
new file mode 100644
index 000000000..458a19250
--- /dev/null
+++ b/layout/xul/tree/crashtests/479931-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var o = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+ var q = document.getElementById("q");
+ q.appendChild(o);
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<xul:tree><xul:treechildren id="q"><div/></xul:treechildren></xul:tree>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/509602-1-overlay.xul b/layout/xul/tree/crashtests/509602-1-overlay.xul
new file mode 100644
index 000000000..f5cecd40e
--- /dev/null
+++ b/layout/xul/tree/crashtests/509602-1-overlay.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="b">
+<box onDOMAttrModified="event.target.parentNode.removeChild(event.target)" id="d"/>
+<tree/>
+</box>
+
+<tree>
+<box id="b" observes="d"/>
+<treechildren observes="b"/>
+</tree>
+</overlay> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/509602-1.xul b/layout/xul/tree/crashtests/509602-1.xul
new file mode 100644
index 000000000..a1cdcf1cc
--- /dev/null
+++ b/layout/xul/tree/crashtests/509602-1.xul
@@ -0,0 +1,3 @@
+<?xul-overlay href="509602-1-overlay.xul"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/585815-iframe.xul b/layout/xul/tree/crashtests/585815-iframe.xul
new file mode 100644
index 000000000..ce9d29448
--- /dev/null
+++ b/layout/xul/tree/crashtests/585815-iframe.xul
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setInterval(run, 25)">
+
+<tree flex="1" rows="2">
+ <treecols>
+ <treecol id="sender" label="Sender" flex="1"/>
+ <treecol id="subject" label="Subject" flex="2"/>
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell label="joe@somewhere.com"/>
+ <treecell label="Top secret plans"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+<script type="text/javascript"><![CDATA[
+function run() {
+ var tree = document.getElementsByTagName("tree")[0];
+ var sel = tree.view.selection;
+ sel.rangedSelect(0, 0, true);
+ sel.rangedSelect(1000, 1001, true);
+ sel.adjustSelection(1, 0x7fffffff);
+}
+]]></script>
+
+</window>
diff --git a/layout/xul/tree/crashtests/585815.html b/layout/xul/tree/crashtests/585815.html
new file mode 100644
index 000000000..0b8b01827
--- /dev/null
+++ b/layout/xul/tree/crashtests/585815.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 585815</title>
+<script>
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(done,1000)">
+
+<iframe src="585815-iframe.xul"></iframe>
+
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/601427.html b/layout/xul/tree/crashtests/601427.html
new file mode 100644
index 000000000..cd9574eef
--- /dev/null
+++ b/layout/xul/tree/crashtests/601427.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+
+var onPaintFunctions =
+[
+ function() { document.documentElement.style.MozAppearance = "treeheadersortarrow"; },
+ function() { document.documentElement.style.position = "fixed"; },
+ function() { document.documentElement.removeAttribute("class"); }
+];
+
+var i = 0;
+
+function advance()
+{
+ var f = onPaintFunctions[i++];
+ if (f)
+ f();
+}
+
+function start()
+{
+ window.addEventListener("MozAfterPaint", advance, true);
+ advance();
+}
+
+window.addEventListener("load", start, false);
+
+</script>
+</html>
diff --git a/layout/xul/tree/crashtests/730441-1.xul b/layout/xul/tree/crashtests/730441-1.xul
new file mode 100644
index 000000000..a31c3b63f
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-1.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!--
+Program received signal SIGSEGV, Segmentation fault.
+0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285
+285 return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
+(gdb) p this
+$6 = (nsIContent * const) 0x0
+(gdb) bt 3
+#0 0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285
+#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605
+#2 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+(More stack frames follow...)
+(gdb) frame 1
+#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+(gdb) list
+600 PRUint32 numChildren = colsContent->GetChildCount();
+601 for (PRUint32 i = 0; i < numChildren; ++i) {
+602 nsIContent *child = colsContent->GetChildAt(i);
+603 nsAutoString ordinal;
+604 ordinal.AppendInt(i);
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+606 }
+(gdb) p child
+$7 = (nsIContent *) 0x0
+
+First loop iteration: |child->SetAttr()| dispatches "DOMAttrModified" event.
+Event listener removes next column. Second loop iteration: |colsContent->GetChildAt(i)|
+returns null. Then we have |null->SetAttr()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run();">
+<tree id="tree">
+ <treecols>
+ <treecol id="col1"/>
+ <treecol id="col2"/>
+ </treecols>
+ <treechildren/>
+</tree>
+<script type="text/javascript"><![CDATA[
+function listener() {
+ var col2 = document.getElementById("col2");
+ col2.parentNode.removeChild(col2);
+}
+
+function run() {
+ var col1 = document.getElementById("col1");
+ col1.addEventListener("DOMAttrModified", listener, true);
+ var tree = document.getElementById("tree");
+ tree.columns.restoreNaturalOrder();
+}
+]]></script>
+</window>
+
diff --git a/layout/xul/tree/crashtests/730441-2.xul b/layout/xul/tree/crashtests/730441-2.xul
new file mode 100644
index 000000000..02b3a307e
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-2.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+Program received signal SIGSEGV, Segmentation fault.
+0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610
+610 mTree->Invalidate();
+(gdb) bt 3
+#0 0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610
+#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD)
+ at js/src/xpconnect/src/xpcwrappednative.cpp:2722
+(More stack frames follow...)
+(gdb) list
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+606 }
+607
+608 nsTreeColumns::InvalidateColumns();
+609
+610 mTree->Invalidate();
+611
+612 return NS_OK;
+613 }
+614
+(gdb) p mTree
+$9 = (nsITreeBoxObject *) 0x0
+
+|child->SetAttr()| dispatches "DOMAttrModified" event. Event listener removes
+whole tree, |mTree| is being set to null. Then we have |null->Invalidate()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run();">
+<tree id="tree">
+ <treecols>
+ <treecol id="col"/>
+ </treecols>
+ <treechildren/>
+</tree>
+<script type="text/javascript"><![CDATA[
+var tree = null;
+
+function listener() {
+ tree.parentNode.removeChild(tree);
+}
+
+function run() {
+ col = document.getElementById("col");
+ col.addEventListener("DOMAttrModified", listener, true);
+ tree = document.getElementById("tree");
+ tree.columns.restoreNaturalOrder();
+}
+]]></script>
+</window>
+
diff --git a/layout/xul/tree/crashtests/730441-3.xul b/layout/xul/tree/crashtests/730441-3.xul
new file mode 100644
index 000000000..2cf74d1b9
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-3.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!--
+###!!! ASSERTION: You can't dereference a NULL nsCOMPtr with operator->().: 'mRawPtr != 0', file ../../../../dist/include/nsCOMPtr.h, line 796
+
+Program received signal SIGSEGV, Segmentation fault.
+0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571
+571 boxObject->GetElement(getter_AddRefs(element));
+(gdb) bt 3
+#0 0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571
+#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD)
+ at js/src/xpconnect/src/xpcwrappednative.cpp:2722
+(More stack frames follow...)
+(gdb) list 566
+561 nsTreeContentView::SetTree(nsITreeBoxObject* aTree)
+562 {
+563 ClearRows();
+564
+565 mBoxObject = aTree;
+566
+567 if (aTree && !mRoot) {
+568 // Get our root element
+569 nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject);
+570 nsCOMPtr<nsIDOMElement> element;
+571 boxObject->GetElement(getter_AddRefs(element));
+(gdb) p boxObject
+$16 = {mRawPtr = 0x0}
+
+|aTree| does not implement |nsIBoxObject|, so |do_QueryInterface(mBoxObject)|
+returns null. Then we have |null->GetElement()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.getElementById('tree').view.setTree({});">
+<tree id="tree">
+ <treechildren/>
+</tree>
+</window>
+
diff --git a/layout/xul/tree/crashtests/crashtests.list b/layout/xul/tree/crashtests/crashtests.list
new file mode 100644
index 000000000..d462e9550
--- /dev/null
+++ b/layout/xul/tree/crashtests/crashtests.list
@@ -0,0 +1,24 @@
+load 307298-1.xul
+load 309732-1.xul
+load 309732-2.xul
+load 366583-1.xul
+load 380217-1.xul
+load 382444-1.html
+load 391178-1.xhtml
+load 391178-2.xul
+load 393665-1.xul
+load 399227-1.xul
+load 399227-2.xul
+load 399692-1.xhtml
+load 399715-1.xhtml
+load 409807-1.xul
+load 414170-1.xul
+load 430394-1.xul
+load 454186-1.xul
+load 479931-1.xhtml
+load 509602-1.xul
+load 585815.html
+load 601427.html
+load 730441-1.xul
+load 730441-2.xul
+load 730441-3.xul
diff --git a/layout/xul/tree/moz.build b/layout/xul/tree/moz.build
new file mode 100644
index 000000000..ccac5bde9
--- /dev/null
+++ b/layout/xul/tree/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL')
+
+XPIDL_SOURCES += [
+ 'nsITreeBoxObject.idl',
+ 'nsITreeColumns.idl',
+ 'nsITreeContentView.idl',
+ 'nsITreeSelection.idl',
+ 'nsITreeView.idl',
+]
+
+XPIDL_MODULE = 'layout_xul_tree'
+
+EXPORTS += [
+ 'nsTreeColFrame.h',
+ 'nsTreeColumns.h',
+ 'nsTreeUtils.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'TreeBoxObject.h'
+]
+
+UNIFIED_SOURCES += [
+ 'nsTreeBodyFrame.cpp',
+ 'nsTreeColFrame.cpp',
+ 'nsTreeColumns.cpp',
+ 'nsTreeContentView.cpp',
+ 'nsTreeImageListener.cpp',
+ 'nsTreeSelection.cpp',
+ 'nsTreeStyleCache.cpp',
+ 'nsTreeUtils.cpp',
+ 'TreeBoxObject.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '..',
+ '../../base',
+ '../../forms',
+ '../../generic',
+ '../../style',
+ '/dom/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/xul/tree/nsITreeBoxObject.idl b/layout/xul/tree/nsITreeBoxObject.idl
new file mode 100644
index 000000000..638c8a41c
--- /dev/null
+++ b/layout/xul/tree/nsITreeBoxObject.idl
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsITreeView;
+interface nsITreeSelection;
+interface nsITreeColumn;
+interface nsITreeColumns;
+interface nsIScriptableRegion;
+
+[scriptable, uuid(f3da0c5e-51f5-45f0-b2cd-6be3ab6847ae)]
+interface nsITreeBoxObject : nsISupports
+{
+ /**
+ * Obtain the columns.
+ */
+ readonly attribute nsITreeColumns columns;
+
+ /**
+ * The view that backs the tree and that supplies it with its data.
+ * It is dynamically settable, either using a view attribute on the
+ * tree tag or by setting this attribute to a new value.
+ */
+ attribute nsITreeView view;
+
+ /**
+ * Whether or not we are currently focused.
+ */
+ attribute boolean focused;
+
+ /**
+ * Obtain the treebody content node
+ */
+ readonly attribute nsIDOMElement treeBody;
+
+ /**
+ * Obtain the height of a row.
+ */
+ readonly attribute long rowHeight;
+
+ /**
+ * Obtain the width of a row.
+ */
+ readonly attribute long rowWidth;
+
+ /**
+ * Get the pixel position of the horizontal scrollbar.
+ */
+ readonly attribute long horizontalPosition;
+
+ /**
+ * Return the region for the visible parts of the selection, in device pixels.
+ */
+ readonly attribute nsIScriptableRegion selectionRegion;
+
+ /**
+ * Get the index of the first visible row.
+ */
+ long getFirstVisibleRow();
+
+ /**
+ * Get the index of the last visible row.
+ */
+ long getLastVisibleRow();
+
+ /**
+ * Gets the number of possible visible rows.
+ */
+ long getPageLength();
+
+ /**
+ * Ensures that a row at a given index is visible.
+ */
+ void ensureRowIsVisible(in long index);
+
+ /**
+ * Ensures that a given cell in the tree is visible.
+ */
+ void ensureCellIsVisible(in long row, in nsITreeColumn col);
+
+ /**
+ * Scrolls such that the row at index is at the top of the visible view.
+ */
+ void scrollToRow(in long index);
+
+ /**
+ * Scroll the tree up or down by numLines lines. Positive
+ * values move down in the tree. Prevents scrolling off the
+ * end of the tree.
+ */
+ void scrollByLines(in long numLines);
+
+ /**
+ * Scroll the tree up or down by numPages pages. A page
+ * is considered to be the amount displayed by the tree.
+ * Positive values move down in the tree. Prevents scrolling
+ * off the end of the tree.
+ */
+ void scrollByPages(in long numPages);
+
+ /**
+ * Scrolls such that a given cell is visible (if possible)
+ * at the top left corner of the visible view.
+ */
+ void scrollToCell(in long row, in nsITreeColumn col);
+
+ /**
+ * Scrolls horizontally so that the specified column is
+ * at the left of the view (if possible).
+ */
+ void scrollToColumn(in nsITreeColumn col);
+
+ /**
+ * Scroll to a specific horizontal pixel position.
+ */
+ void scrollToHorizontalPosition(in long horizontalPosition);
+
+ /**
+ * Invalidation methods for fine-grained painting control.
+ */
+ void invalidate();
+ void invalidateColumn(in nsITreeColumn col);
+ void invalidateRow(in long index);
+ void invalidateCell(in long row, in nsITreeColumn col);
+ void invalidateRange(in long startIndex, in long endIndex);
+ void invalidateColumnRange(in long startIndex, in long endIndex,
+ in nsITreeColumn col);
+
+ /**
+ * A hit test that can tell you what row the mouse is over.
+ * returns -1 for invalid mouse coordinates.
+ *
+ * The coordinate system is the client coordinate system for the
+ * document this boxObject lives in, and the units are CSS pixels.
+ */
+ long getRowAt(in long x, in long y);
+
+ /**
+ * A hit test that can tell you what cell the mouse is over. Row is the row index
+ * hit, returns -1 for invalid mouse coordinates. ColID is the column hit.
+ * ChildElt is the pseudoelement hit: this can have values of
+ * "cell", "twisty", "image", and "text".
+ *
+ * The coordinate system is the client coordinate system for the
+ * document this boxObject lives in, and the units are CSS pixels.
+ */
+ void getCellAt(in long x, in long y, out long row, out nsITreeColumn col, out AString childElt);
+
+ /**
+ * Find the coordinates of an element within a specific cell.
+ */
+ void getCoordsForCellItem(in long row, in nsITreeColumn col, in AString element,
+ out long x, out long y, out long width, out long height);
+
+ /**
+ * Determine if the text of a cell is being cropped or not.
+ */
+ boolean isCellCropped(in long row, in nsITreeColumn col);
+
+ /**
+ * The view is responsible for calling these notification methods when
+ * rows are added or removed. Index is the position at which the new
+ * rows were added or at which rows were removed. For
+ * non-contiguous additions/removals, this method should be called multiple times.
+ */
+ void rowCountChanged(in long index, in long count);
+
+ /**
+ * Notify the tree that the view is about to perform a batch
+ * update, that is, add, remove or invalidate several rows at once.
+ * This must be followed by calling endUpdateBatch(), otherwise the tree
+ * will get out of sync.
+ */
+ void beginUpdateBatch();
+
+ /**
+ * Notify the tree that the view has completed a batch update.
+ */
+ void endUpdateBatch();
+
+ /**
+ * Called on a theme switch to flush out the tree's style and image caches.
+ */
+ void clearStyleAndImageCaches();
+};
diff --git a/layout/xul/tree/nsITreeColumns.idl b/layout/xul/tree/nsITreeColumns.idl
new file mode 100644
index 000000000..a601f3776
--- /dev/null
+++ b/layout/xul/tree/nsITreeColumns.idl
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITreeColumns;
+interface nsIDOMElement;
+interface nsIAtom;
+
+[scriptable, uuid(ae835ecf-6b32-4660-9b43-8a270df56e02)]
+interface nsITreeColumn : nsISupports
+{
+ readonly attribute nsIDOMElement element;
+
+ readonly attribute nsITreeColumns columns;
+
+ readonly attribute long x;
+ readonly attribute long width;
+
+ readonly attribute AString id;
+ [noscript] void getIdConst([shared] out wstring idConst);
+ [noscript] readonly attribute nsIAtom atom;
+
+ readonly attribute long index;
+
+ readonly attribute boolean primary;
+ readonly attribute boolean cycler;
+ readonly attribute boolean editable;
+ readonly attribute boolean selectable;
+
+ const short TYPE_TEXT = 1;
+ const short TYPE_CHECKBOX = 2;
+ const short TYPE_PROGRESSMETER = 3;
+ const short TYPE_PASSWORD = 4;
+ readonly attribute short type;
+
+ nsITreeColumn getNext();
+ nsITreeColumn getPrevious();
+
+ void invalidate();
+};
+
+interface nsITreeBoxObject;
+
+[scriptable, uuid(f8a8d6b4-6788-438d-9009-7142798767ab)]
+interface nsITreeColumns : nsISupports
+{
+ /**
+ * The tree widget for these columns.
+ */
+ readonly attribute nsITreeBoxObject tree;
+
+ /**
+ * The number of columns.
+ */
+ readonly attribute long count;
+
+ /**
+ * An alias for count (for the benefit of scripts which treat this as an
+ * array).
+ */
+ readonly attribute long length;
+
+ /**
+ * Get the first/last column.
+ */
+ nsITreeColumn getFirstColumn();
+ nsITreeColumn getLastColumn();
+
+ /**
+ * Attribute based column getters.
+ */
+ nsITreeColumn getPrimaryColumn();
+ nsITreeColumn getSortedColumn();
+ nsITreeColumn getKeyColumn();
+
+ /**
+ * Get the column for the given element.
+ */
+ nsITreeColumn getColumnFor(in nsIDOMElement element);
+
+ /**
+ * Parametric column getters.
+ */
+ nsITreeColumn getNamedColumn(in AString id);
+ nsITreeColumn getColumnAt(in long index);
+
+ /**
+ * This method is called whenever a treecol is added or removed and
+ * the column cache needs to be rebuilt.
+ */
+ void invalidateColumns();
+
+ void restoreNaturalOrder();
+};
diff --git a/layout/xul/tree/nsITreeContentView.idl b/layout/xul/tree/nsITreeContentView.idl
new file mode 100644
index 000000000..0b5d178aa
--- /dev/null
+++ b/layout/xul/tree/nsITreeContentView.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+
+[scriptable, uuid(5ef62896-0c0a-41f1-bb3c-44a60f5dfdab)]
+interface nsITreeContentView : nsISupports
+{
+ /**
+ * Retrieve the content item associated with the specified index.
+ */
+ nsIDOMElement getItemAtIndex(in long index);
+
+ /**
+ * Retrieve the index associated with the specified content item.
+ */
+ long getIndexOfItem(in nsIDOMElement item);
+};
diff --git a/layout/xul/tree/nsITreeSelection.idl b/layout/xul/tree/nsITreeSelection.idl
new file mode 100644
index 000000000..6cc137f1e
--- /dev/null
+++ b/layout/xul/tree/nsITreeSelection.idl
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsITreeBoxObject;
+interface nsITreeColumn;
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(ab6fe746-300b-4ab4-abb9-1c0e3977874c)]
+interface nsITreeSelection : nsISupports
+{
+ /**
+ * The tree widget for this selection.
+ */
+ attribute nsITreeBoxObject tree;
+
+ /**
+ * This attribute is a boolean indicating single selection.
+ */
+ readonly attribute boolean single;
+
+ /**
+ * The number of rows currently selected in this tree.
+ */
+ readonly attribute long count;
+
+ /**
+ * Indicates whether or not the row at the specified index is
+ * part of the selection.
+ */
+ boolean isSelected(in long index);
+
+ /**
+ * Deselect all rows and select the row at the specified index.
+ */
+ void select(in long index);
+
+ /**
+ * Perform a timed select.
+ */
+ void timedSelect(in long index, in long delay);
+
+ /**
+ * Toggle the selection state of the row at the specified index.
+ */
+ void toggleSelect(in long index);
+
+ /**
+ * Select the range specified by the indices. If augment is true,
+ * then we add the range to the selection without clearing out anything
+ * else. If augment is false, everything is cleared except for the specified range.
+ */
+ void rangedSelect(in long startIndex, in long endIndex, in boolean augment);
+
+ /**
+ * Clears the range.
+ */
+ void clearRange(in long startIndex, in long endIndex);
+
+ /**
+ * Clears the selection.
+ */
+ void clearSelection();
+
+ /**
+ * Inverts the selection.
+ */
+ void invertSelection();
+
+ /**
+ * Selects all rows.
+ */
+ void selectAll();
+
+ /**
+ * Iterate the selection using these methods.
+ */
+ long getRangeCount();
+ void getRangeAt(in long i, out long min, out long max);
+
+ /**
+ * Can be used to invalidate the selection.
+ */
+ void invalidateSelection();
+
+ /**
+ * Called when the row count changes to adjust selection indices.
+ */
+ void adjustSelection(in long index, in long count);
+
+ /**
+ * This attribute is a boolean indicating whether or not the
+ * "select" event should fire when the selection is changed using
+ * one of our methods. A view can use this to temporarily suppress
+ * the selection while manipulating all of the indices, e.g., on
+ * a sort.
+ * Note: setting this attribute to false will fire a select event.
+ */
+ attribute boolean selectEventsSuppressed;
+
+ /**
+ * The current item (the one that gets a focus rect in addition to being
+ * selected).
+ */
+ attribute long currentIndex;
+
+ /**
+ * The current column.
+ */
+ attribute nsITreeColumn currentColumn;
+
+ /**
+ * The selection "pivot". This is the first item the user selected as
+ * part of a ranged select.
+ */
+ readonly attribute long shiftSelectPivot;
+};
+
+/**
+ * The following interface is not scriptable and MUST NEVER BE MADE scriptable.
+ * Native treeselections implement it, and we use this to check whether a
+ * treeselection is native (and therefore suitable for use by untrusted content).
+ */
+[uuid(1bd59678-5cb3-4316-b246-31a91b19aabe)]
+interface nsINativeTreeSelection : nsITreeSelection
+{
+ [noscript] void ensureNative();
+};
diff --git a/layout/xul/tree/nsITreeView.idl b/layout/xul/tree/nsITreeView.idl
new file mode 100644
index 000000000..66920f81b
--- /dev/null
+++ b/layout/xul/tree/nsITreeView.idl
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITreeBoxObject;
+interface nsITreeSelection;
+interface nsITreeColumn;
+interface nsIDOMDataTransfer;
+
+[scriptable, uuid(091116f0-0bdc-4b32-b9c8-c8d5a37cb088)]
+interface nsITreeView : nsISupports
+{
+ /**
+ * The total number of rows in the tree (including the offscreen rows).
+ */
+ readonly attribute long rowCount;
+
+ /**
+ * The selection for this view.
+ */
+ attribute nsITreeSelection selection;
+
+ /**
+ * A whitespace delimited list of properties. For each property X the view
+ * gives back will cause the pseudoclasses ::-moz-tree-cell(x),
+ * ::-moz-tree-row(x), ::-moz-tree-twisty(x), ::-moz-tree-image(x),
+ * ::-moz-tree-cell-text(x). to be matched on the pseudoelement
+ * ::moz-tree-row.
+ */
+ AString getRowProperties(in long index);
+
+ /**
+ * A whitespace delimited list of properties for a given cell. Each
+ * property, x, that the view gives back will cause the pseudoclasses
+ * ::-moz-tree-cell(x), ::-moz-tree-row(x), ::-moz-tree-twisty(x),
+ * ::-moz-tree-image(x), ::-moz-tree-cell-text(x). to be matched on the
+ * cell.
+ */
+ AString getCellProperties(in long row, in nsITreeColumn col);
+
+ /**
+ * Called to get properties to paint a column background. For shading the sort
+ * column, etc.
+ */
+ AString getColumnProperties(in nsITreeColumn col);
+
+ /**
+ * Methods that can be used to test whether or not a twisty should be drawn,
+ * and if so, whether an open or closed twisty should be used.
+ */
+ boolean isContainer(in long index);
+ boolean isContainerOpen(in long index);
+ boolean isContainerEmpty(in long index);
+
+ /**
+ * isSeparator is used to determine if the row at index is a separator.
+ * A value of true will result in the tree drawing a horizontal separator.
+ * The tree uses the ::moz-tree-separator pseudoclass to draw the separator.
+ */
+ boolean isSeparator(in long index);
+
+ /**
+ * Specifies if there is currently a sort on any column. Used mostly by dragdrop
+ * to affect drop feedback.
+ */
+ boolean isSorted();
+
+ const short DROP_BEFORE = -1;
+ const short DROP_ON = 0;
+ const short DROP_AFTER = 1;
+ /**
+ * Methods used by the drag feedback code to determine if a drag is allowable at
+ * the current location. To get the behavior where drops are only allowed on
+ * items, such as the mailNews folder pane, always return false when
+ * the orientation is not DROP_ON.
+ */
+ boolean canDrop(in long index, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Called when the user drops something on this view. The |orientation| param
+ * specifies before/on/after the given |row|.
+ */
+ void drop(in long row, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Methods used by the tree to draw thread lines in the tree.
+ * getParentIndex is used to obtain the index of a parent row.
+ * If there is no parent row, getParentIndex returns -1.
+ */
+ long getParentIndex(in long rowIndex);
+
+ /**
+ * hasNextSibling is used to determine if the row at rowIndex has a nextSibling
+ * that occurs *after* the index specified by afterIndex. Code that is forced
+ * to march down the view looking at levels can optimize the march by starting
+ * at afterIndex+1.
+ */
+ boolean hasNextSibling(in long rowIndex, in long afterIndex);
+
+ /**
+ * The level is an integer value that represents
+ * the level of indentation. It is multiplied by the width specified in the
+ * :moz-tree-indentation pseudoelement to compute the exact indendation.
+ */
+ long getLevel(in long index);
+
+ /**
+ * The image path for a given cell. For defining an icon for a cell.
+ * If the empty string is returned, the :moz-tree-image pseudoelement
+ * will be used.
+ */
+ AString getImageSrc(in long row, in nsITreeColumn col);
+
+ /**
+ * The progress mode for a given cell. This method is only called for
+ * columns of type |progressmeter|.
+ */
+ const short PROGRESS_NORMAL = 1;
+ const short PROGRESS_UNDETERMINED = 2;
+ const short PROGRESS_NONE = 3;
+ long getProgressMode(in long row, in nsITreeColumn col);
+
+ /**
+ * The value for a given cell. This method is only called for columns
+ * of type other than |text|.
+ */
+ AString getCellValue(in long row, in nsITreeColumn col);
+
+ /**
+ * The text for a given cell. If a column consists only of an image, then
+ * the empty string is returned.
+ */
+ AString getCellText(in long row, in nsITreeColumn col);
+
+ /**
+ * Called during initialization to link the view to the front end box object.
+ */
+ void setTree(in nsITreeBoxObject tree);
+
+ /**
+ * Called on the view when an item is opened or closed.
+ */
+ void toggleOpenState(in long index);
+
+ /**
+ * Called on the view when a header is clicked.
+ */
+ void cycleHeader(in nsITreeColumn col);
+
+ /**
+ * Should be called from a XUL onselect handler whenever the selection changes.
+ */
+ void selectionChanged();
+
+ /**
+ * Called on the view when a cell in a non-selectable cycling column (e.g., unread/flag/etc.) is clicked.
+ */
+ void cycleCell(in long row, in nsITreeColumn col);
+
+ /**
+ * isEditable is called to ask the view if the cell contents are editable.
+ * A value of true will result in the tree popping up a text field when
+ * the user tries to inline edit the cell.
+ */
+ boolean isEditable(in long row, in nsITreeColumn col);
+
+ /**
+ * isSelectable is called to ask the view if the cell is selectable.
+ * This method is only called if the selection style is |cell| or |text|.
+ * XXXvarga shouldn't this be called isCellSelectable?
+ */
+ boolean isSelectable(in long row, in nsITreeColumn col);
+
+ /**
+ * setCellValue is called when the value of the cell has been set by the user.
+ * This method is only called for columns of type other than |text|.
+ */
+ void setCellValue(in long row, in nsITreeColumn col, in AString value);
+
+ /**
+ * setCellText is called when the contents of the cell have been edited by the user.
+ */
+ void setCellText(in long row, in nsITreeColumn col, in AString value);
+
+ /**
+ * A command API that can be used to invoke commands on the selection. The tree
+ * will automatically invoke this method when certain keys are pressed. For example,
+ * when the DEL key is pressed, performAction will be called with the "delete" string.
+ */
+ void performAction(in wstring action);
+
+ /**
+ * A command API that can be used to invoke commands on a specific row.
+ */
+ void performActionOnRow(in wstring action, in long row);
+
+ /**
+ * A command API that can be used to invoke commands on a specific cell.
+ */
+ void performActionOnCell(in wstring action, in long row, in nsITreeColumn col);
+};
+
+/**
+ * The following interface is not scriptable and MUST NEVER BE MADE scriptable.
+ * Native treeviews implement it, and we use this to check whether a treeview
+ * is native (and therefore suitable for use by untrusted content).
+ */
+[uuid(46c90265-6553-41ae-8d39-7022e7d09145)]
+interface nsINativeTreeView : nsITreeView
+{
+ [noscript] void ensureNative();
+};
diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp
new file mode 100644
index 000000000..deba04a36
--- /dev/null
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -0,0 +1,4945 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEditRules.h"
+
+#include "gfxUtils.h"
+#include "nsAlgorithm.h"
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsTreeBodyFrame.h"
+#include "nsTreeSelection.h"
+#include "nsTreeImageListener.h"
+
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+#include "nsIBoxObject.h"
+#include "nsIDOMCustomEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocument.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsCSSRendering.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsXPIDLString.h"
+#include "nsContainerFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsVariant.h"
+#include "nsWidgetsCID.h"
+#include "nsBoxFrame.h"
+#include "nsIURL.h"
+#include "nsBoxLayoutState.h"
+#include "nsTreeContentView.h"
+#include "nsTreeUtils.h"
+#include "nsThemeConstants.h"
+#include "nsITheme.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "imgILoader.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsDisplayList.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "nsRenderingContext.h"
+#include "nsIScriptableRegion.h"
+#include <algorithm>
+#include "ScrollbarActivity.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#include "nsIWritablePropertyBag2.h"
+#endif
+#include "nsBidiUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+
+// Function that cancels all the image requests in our cache.
+void
+nsTreeBodyFrame::CancelImageRequests()
+{
+ for (auto iter = mImageCache.Iter(); !iter.Done(); iter.Next()) {
+ // If our imgIRequest object was registered with the refresh driver
+ // then we need to deregister it.
+ nsTreeImageCacheEntry entry = iter.UserData();
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
+ nullptr);
+ entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+}
+
+//
+// NS_NewTreeFrame
+//
+// Creates a new tree frame
+//
+nsIFrame*
+NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTreeBodyFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
+
+NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
+
+// Constructor
+nsTreeBodyFrame::nsTreeBodyFrame(nsStyleContext* aContext)
+:nsLeafBoxFrame(aContext),
+ mSlots(nullptr),
+ mImageCache(),
+ mTopRowIndex(0),
+ mPageLength(0),
+ mHorzPosition(0),
+ mOriginalHorzWidth(-1),
+ mHorzWidth(0),
+ mAdjustWidth(0),
+ mRowHeight(0),
+ mIndentation(0),
+ mStringWidth(-1),
+ mUpdateBatchNest(0),
+ mRowCount(0),
+ mMouseOverRow(-1),
+ mFocused(false),
+ mHasFixedRowCount(false),
+ mVerticalOverflow(false),
+ mHorizontalOverflow(false),
+ mReflowCallbackPosted(false),
+ mCheckingOverflow(false)
+{
+ mColumns = new nsTreeColumns(this);
+}
+
+// Destructor
+nsTreeBodyFrame::~nsTreeBodyFrame()
+{
+ CancelImageRequests();
+ DetachImageListeners();
+ delete mSlots;
+}
+
+static void
+GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin)
+{
+ aMargin.SizeTo(0, 0, 0, 0);
+ aContext->StylePadding()->GetPadding(aMargin);
+ aMargin += aContext->StyleBorder()->GetComputedBorder();
+}
+
+static void
+AdjustForBorderPadding(nsStyleContext* aContext, nsRect& aRect)
+{
+ nsMargin borderPadding(0, 0, 0, 0);
+ GetBorderPadding(aContext, borderPadding);
+ aRect.Deflate(borderPadding);
+}
+
+void
+nsTreeBodyFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mIndentation = GetIndentation();
+ mRowHeight = GetRowHeight();
+
+ EnsureBoxObject();
+
+ if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+ mScrollbarActivity = new ScrollbarActivity(
+ static_cast<nsIScrollbarMediator*>(this));
+ }
+}
+
+nsSize
+nsTreeBodyFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ EnsureView();
+
+ nsIContent* baseElement = GetBaseElement();
+
+ nsSize min(0,0);
+ int32_t desiredRows;
+ if (MOZ_UNLIKELY(!baseElement)) {
+ desiredRows = 0;
+ }
+ else if (baseElement->IsHTMLElement(nsGkAtoms::select)) {
+ min.width = CalcMaxRowWidth();
+ nsAutoString size;
+ baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::size, size);
+ if (!size.IsEmpty()) {
+ nsresult err;
+ desiredRows = size.ToInteger(&err);
+ mHasFixedRowCount = true;
+ mPageLength = desiredRows;
+ }
+ else {
+ desiredRows = 1;
+ }
+ }
+ else {
+ // tree
+ nsAutoString rows;
+ baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
+ if (!rows.IsEmpty()) {
+ nsresult err;
+ desiredRows = rows.ToInteger(&err);
+ mPageLength = desiredRows;
+ }
+ else {
+ desiredRows = 0;
+ }
+ }
+
+ min.height = mRowHeight * desiredRows;
+
+ AddBorderAndPadding(min);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(aBoxLayoutState, this, min, widthSet, heightSet);
+
+ return min;
+}
+
+nscoord
+nsTreeBodyFrame::CalcMaxRowWidth()
+{
+ if (mStringWidth != -1)
+ return mStringWidth;
+
+ if (!mView)
+ return 0;
+
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ nsMargin rowMargin(0,0,0,0);
+ GetBorderPadding(rowContext, rowMargin);
+
+ nscoord rowWidth;
+ nsTreeColumn* col;
+
+ nsRenderingContext rc(
+ PresContext()->PresShell()->CreateReferenceRenderingContext());
+
+ for (int32_t row = 0; row < mRowCount; ++row) {
+ rowWidth = 0;
+
+ for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) {
+ nscoord desiredWidth, currentWidth;
+ nsresult rv = GetCellWidth(row, col, &rc, desiredWidth, currentWidth);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("invalid column");
+ continue;
+ }
+ rowWidth += desiredWidth;
+ }
+
+ if (rowWidth > mStringWidth)
+ mStringWidth = rowWidth;
+ }
+
+ mStringWidth += rowMargin.left + rowMargin.right;
+ return mStringWidth;
+}
+
+void
+nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->Destroy();
+ mScrollbarActivity = nullptr;
+ }
+
+ mScrollEvent.Revoke();
+ // Make sure we cancel any posted callbacks.
+ if (mReflowCallbackPosted) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ if (mColumns)
+ mColumns->SetTree(nullptr);
+
+ // Save off our info into the box object.
+ nsCOMPtr<nsPIBoxObject> box(do_QueryInterface(mTreeBoxObject));
+ if (box) {
+ if (mTopRowIndex > 0) {
+ nsAutoString topRowStr; topRowStr.AssignLiteral("topRow");
+ nsAutoString topRow;
+ topRow.AppendInt(mTopRowIndex);
+ box->SetProperty(topRowStr.get(), topRow.get());
+ }
+
+ // Always null out the cached tree body frame.
+ box->ClearCachedValues();
+
+ mTreeBoxObject = nullptr; // Drop our ref here.
+ }
+
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr);
+ mView = nullptr;
+ }
+
+ nsLeafBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsTreeBodyFrame::EnsureBoxObject()
+{
+ if (!mTreeBoxObject) {
+ nsIContent* parent = GetBaseElement();
+ if (parent) {
+ nsIDocument* nsDoc = parent->GetComposedDoc();
+ if (!nsDoc) // there may be no document, if we're called from Destroy()
+ return;
+ ErrorResult ignored;
+ nsCOMPtr<nsIBoxObject> box =
+ nsDoc->GetBoxObjectFor(parent->AsElement(), ignored);
+ // Ensure that we got a native box object.
+ nsCOMPtr<nsPIBoxObject> pBox = do_QueryInterface(box);
+ if (pBox) {
+ nsCOMPtr<nsITreeBoxObject> realTreeBoxObject = do_QueryInterface(pBox);
+ if (realTreeBoxObject) {
+ nsTreeBodyFrame* innerTreeBoxObject =
+ static_cast<dom::TreeBoxObject*>(realTreeBoxObject.get())
+ ->GetCachedTreeBodyFrame();
+ ENSURE_TRUE(!innerTreeBoxObject || innerTreeBoxObject == this);
+ mTreeBoxObject = realTreeBoxObject;
+ }
+ }
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::EnsureView()
+{
+ if (!mView) {
+ if (PresContext()->PresShell()->IsReflowLocked()) {
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+ return;
+ }
+ nsCOMPtr<nsIBoxObject> box = do_QueryInterface(mTreeBoxObject);
+ if (box) {
+ nsWeakFrame weakFrame(this);
+ nsCOMPtr<nsITreeView> treeView;
+ mTreeBoxObject->GetView(getter_AddRefs(treeView));
+ if (treeView && weakFrame.IsAlive()) {
+ nsXPIDLString rowStr;
+ box->GetProperty(u"topRow", getter_Copies(rowStr));
+ nsAutoString rowStr2(rowStr);
+ nsresult error;
+ int32_t rowIndex = rowStr2.ToInteger(&error);
+
+ // Set our view.
+ SetView(treeView);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Scroll to the given row.
+ // XXX is this optimal if we haven't laid out yet?
+ ScrollToRow(rowIndex);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Clear out the property info for the top row, but we always keep the
+ // view current.
+ box->RemoveProperty(u"topRow");
+ }
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth)
+{
+ if (!mReflowCallbackPosted &&
+ (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) {
+ PresContext()->PresShell()->PostReflowCallback(this);
+ mReflowCallbackPosted = true;
+ mOriginalHorzWidth = mHorzWidth;
+ }
+ else if (mReflowCallbackPosted &&
+ mHorzWidth != aHorzWidth && mOriginalHorzWidth == aHorzWidth) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ mOriginalHorzWidth = -1;
+ }
+}
+
+void
+nsTreeBodyFrame::SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea)
+{
+ nscoord horzWidth = CalcHorzWidth(GetScrollParts());
+ ManageReflowCallback(aRect, horzWidth);
+ mHorzWidth = horzWidth;
+
+ nsLeafBoxFrame::SetXULBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
+}
+
+
+bool
+nsTreeBodyFrame::ReflowFinished()
+{
+ if (!mView) {
+ nsWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ }
+ if (mView) {
+ CalcInnerBox();
+ ScrollParts parts = GetScrollParts();
+ mHorzWidth = CalcHorzWidth(parts);
+ if (!mHasFixedRowCount) {
+ mPageLength = mInnerBox.height / mRowHeight;
+ }
+
+ int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
+ if (mTopRowIndex > lastPageTopRow)
+ ScrollToRowInternal(parts, lastPageTopRow);
+
+ nsIContent *treeContent = GetBaseElement();
+ if (treeContent &&
+ treeContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::keepcurrentinview,
+ nsGkAtoms::_true, eCaseMatters)) {
+ // make sure that the current selected item is still
+ // visible after the tree changes size.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ int32_t currentIndex;
+ sel->GetCurrentIndex(&currentIndex);
+ if (currentIndex != -1)
+ EnsureRowIsVisibleInternal(parts, currentIndex);
+ }
+ }
+
+ if (!FullScrollbarsUpdate(false)) {
+ return false;
+ }
+ }
+
+ mReflowCallbackPosted = false;
+ return false;
+}
+
+void
+nsTreeBodyFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackPosted = false;
+}
+
+nsresult
+nsTreeBodyFrame::GetView(nsITreeView * *aView)
+{
+ *aView = nullptr;
+ nsWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ NS_IF_ADDREF(*aView = mView);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::SetView(nsITreeView * aView)
+{
+ // First clear out the old view.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr);
+
+ // Only reset the top row index and delete the columns if we had an old non-null view.
+ mTopRowIndex = 0;
+ }
+
+ // Tree, meet the view.
+ mView = aView;
+
+ // Changing the view causes us to refetch our data. This will
+ // necessarily entail a full invalidation of the tree.
+ Invalidate();
+
+ nsIContent *treeContent = GetBaseElement();
+ if (treeContent) {
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService)
+ accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent, mView);
+#endif
+ FireDOMEvent(NS_LITERAL_STRING("TreeViewChanged"), treeContent);
+ }
+
+ if (mView) {
+ // Give the view a new empty selection object to play with, but only if it
+ // doesn't have one already.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(mTreeBoxObject);
+ }
+ else {
+ NS_NewTreeSelection(mTreeBoxObject, getter_AddRefs(sel));
+ mView->SetSelection(sel);
+ }
+
+ // View, meet the tree.
+ nsWeakFrame weakFrame(this);
+ mView->SetTree(mTreeBoxObject);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ mView->GetRowCount(&mRowCount);
+
+ if (!PresContext()->PresShell()->IsReflowLocked()) {
+ // The scrollbar will need to be updated.
+ FullScrollbarsUpdate(false);
+ } else if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::SetFocused(bool aFocused)
+{
+ if (mFocused != aFocused) {
+ mFocused = aFocused;
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->InvalidateSelection();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::GetTreeBody(nsIDOMElement** aElement)
+{
+ //NS_ASSERTION(mContent, "no content, see bug #104878");
+ if (!mContent)
+ return NS_ERROR_NULL_POINTER;
+
+ return mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)aElement);
+}
+
+int32_t
+nsTreeBodyFrame::RowHeight() const
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+}
+
+int32_t
+nsTreeBodyFrame::RowWidth()
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts()));
+}
+
+int32_t
+nsTreeBodyFrame::GetHorizontalPosition() const
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition);
+}
+
+nsresult
+nsTreeBodyFrame::GetSelectionRegion(nsIScriptableRegion **aRegion)
+{
+ *aRegion = nullptr;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+ NS_ENSURE_TRUE(selection, NS_OK);
+
+ nsCOMPtr<nsIScriptableRegion> region = do_CreateInstance("@mozilla.org/gfx/region;1");
+ NS_ENSURE_TRUE(region, NS_ERROR_FAILURE);
+ region->Init();
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsIntRect rect = mRect.ToOutsidePixels(presContext->AppUnitsPerCSSPixel());
+
+ nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
+ nsPoint origin = GetOffsetTo(rootFrame);
+
+ // iterate through the visible rows and add the selected ones to the
+ // drag region
+ int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
+ int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
+ int32_t top = y;
+ int32_t end = LastVisibleRow();
+ int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+ for (int32_t i = mTopRowIndex; i <= end; i++) {
+ bool isSelected;
+ selection->IsSelected(i, &isSelected);
+ if (isSelected)
+ region->UnionRect(x, y, rect.width, rowHeight);
+ y += rowHeight;
+ }
+
+ // clip to the tree boundary in case one row extends past it
+ region->IntersectRect(x, top, rect.width, rect.height);
+
+ region.forget(aRegion);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::Invalidate()
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ InvalidateFrame();
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateColumn(nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(-1, -1, aCol, aCol);
+#endif
+
+ nsRect columnRect;
+ nsresult rv = col->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // When false then column is out of view
+ if (OffsetForHorzScroll(columnRect, true))
+ InvalidateFrameWithRect(columnRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateRow(int32_t aIndex)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
+#endif
+
+ aIndex -= mTopRowIndex;
+ if (aIndex < 0 || aIndex > mPageLength)
+ return NS_OK;
+
+ nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*aIndex, mInnerBox.width, mRowHeight);
+ InvalidateFrameWithRect(rowRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
+#endif
+
+ aIndex -= mTopRowIndex;
+ if (aIndex < 0 || aIndex > mPageLength)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nsRect cellRect;
+ nsresult rv = col->GetRect(this, mInnerBox.y+mRowHeight*aIndex, mRowHeight,
+ &cellRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (OffsetForHorzScroll(cellRect, true))
+ InvalidateFrameWithRect(cellRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ if (aStart == aEnd)
+ return InvalidateRow(aStart);
+
+ int32_t last = LastVisibleRow();
+ if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
+ return NS_OK;
+
+ if (aStart < mTopRowIndex)
+ aStart = mTopRowIndex;
+
+ if (aEnd > last)
+ aEnd = last;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ int32_t end =
+ mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
+ FireInvalidateEvent(aStart, end, nullptr, nullptr);
+ }
+#endif
+
+ nsRect rangeRect(mInnerBox.x, mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), mInnerBox.width, mRowHeight*(aEnd-aStart+1));
+ InvalidateFrameWithRect(rangeRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ if (aStart == aEnd)
+ return InvalidateCell(aStart, col);
+
+ int32_t last = LastVisibleRow();
+ if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
+ return NS_OK;
+
+ if (aStart < mTopRowIndex)
+ aStart = mTopRowIndex;
+
+ if (aEnd > last)
+ aEnd = last;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ int32_t end =
+ mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
+ FireInvalidateEvent(aStart, end, aCol, aCol);
+ }
+#endif
+
+ nsRect rangeRect;
+ nsresult rv = col->GetRect(this,
+ mInnerBox.y+mRowHeight*(aStart-mTopRowIndex),
+ mRowHeight*(aEnd-aStart+1),
+ &rangeRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ InvalidateFrameWithRect(rangeRect);
+
+ return NS_OK;
+}
+
+static void
+FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult)
+{
+ if (!aResult->mColumnsScrollFrame) {
+ nsIScrollableFrame* f = do_QueryFrame(aCurrFrame);
+ if (f) {
+ aResult->mColumnsFrame = aCurrFrame;
+ aResult->mColumnsScrollFrame = f;
+ }
+ }
+
+ nsScrollbarFrame *sf = do_QueryFrame(aCurrFrame);
+ if (sf) {
+ if (!aCurrFrame->IsXULHorizontal()) {
+ if (!aResult->mVScrollbar) {
+ aResult->mVScrollbar = sf;
+ }
+ } else {
+ if (!aResult->mHScrollbar) {
+ aResult->mHScrollbar = sf;
+ }
+ }
+ // don't bother searching inside a scrollbar
+ return;
+ }
+
+ nsIFrame* child = aCurrFrame->PrincipalChildList().FirstChild();
+ while (child &&
+ !child->GetContent()->IsRootOfNativeAnonymousSubtree() &&
+ (!aResult->mVScrollbar || !aResult->mHScrollbar ||
+ !aResult->mColumnsScrollFrame)) {
+ FindScrollParts(child, aResult);
+ child = child->GetNextSibling();
+ }
+}
+
+nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts()
+{
+ ScrollParts result = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
+ nsIContent* baseElement = GetBaseElement();
+ nsIFrame* treeFrame =
+ baseElement ? baseElement->GetPrimaryFrame() : nullptr;
+ if (treeFrame) {
+ // The way we do this, searching through the entire frame subtree, is pretty
+ // dumb! We should know where these frames are.
+ FindScrollParts(treeFrame, &result);
+ if (result.mHScrollbar) {
+ result.mHScrollbar->SetScrollbarMediatorContent(GetContent());
+ nsIFrame* f = do_QueryFrame(result.mHScrollbar);
+ result.mHScrollbarContent = f->GetContent();
+ }
+ if (result.mVScrollbar) {
+ result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
+ nsIFrame* f = do_QueryFrame(result.mVScrollbar);
+ result.mVScrollbarContent = f->GetContent();
+ }
+ }
+ return result;
+}
+
+void
+nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts)
+{
+ nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+
+ nsWeakFrame weakFrame(this);
+
+ if (aParts.mVScrollbar) {
+ nsAutoString curPos;
+ curPos.AppendInt(mTopRowIndex*rowHeightAsPixels);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
+ // 'this' might be deleted here
+ }
+
+ if (weakFrame.IsAlive() && aParts.mHScrollbar) {
+ nsAutoString curPos;
+ curPos.AppendInt(mHorzPosition);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
+ // 'this' might be deleted here
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+}
+
+void
+nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts)
+{
+ bool verticalOverflowChanged = false;
+ bool horizontalOverflowChanged = false;
+
+ if (!mVerticalOverflow && mRowCount > mPageLength) {
+ mVerticalOverflow = true;
+ verticalOverflowChanged = true;
+ }
+ else if (mVerticalOverflow && mRowCount <= mPageLength) {
+ mVerticalOverflow = false;
+ verticalOverflowChanged = true;
+ }
+
+ if (aParts.mColumnsFrame) {
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ if (bounds.width != 0) {
+ /* Ignore overflows that are less than half a pixel. Yes these happen
+ all over the place when flex boxes are compressed real small.
+ Probably a result of a rounding errors somewhere in the layout code. */
+ bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f);
+ if (!mHorizontalOverflow && bounds.width < mHorzWidth) {
+ mHorizontalOverflow = true;
+ horizontalOverflowChanged = true;
+ } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) {
+ mHorizontalOverflow = false;
+ horizontalOverflowChanged = true;
+ }
+ }
+ }
+
+ nsWeakFrame weakFrame(this);
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsCOMPtr<nsIPresShell> presShell = presContext->GetPresShell();
+ nsCOMPtr<nsIContent> content = mContent;
+
+ if (verticalOverflowChanged) {
+ InternalScrollPortEvent event(true,
+ mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eVertical;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ if (horizontalOverflowChanged) {
+ InternalScrollPortEvent event(true,
+ mHorizontalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eHorizontal;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ // The synchronous event dispatch above can trigger reflow notifications.
+ // Flush those explicitly now, so that we can guard against potential infinite
+ // recursion. See bug 905909.
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set");
+ // Don't use AutoRestore since we want to not touch mCheckingOverflow if we fail
+ // the weakFrame.IsAlive() check below
+ mCheckingOverflow = true;
+ presShell->FlushPendingNotifications(Flush_Layout);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ mCheckingOverflow = false;
+}
+
+void
+nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame)
+{
+ if (mUpdateBatchNest || !mView)
+ return;
+ nsWeakFrame weakFrame(this);
+
+ if (aParts.mVScrollbar) {
+ // Do Vertical Scrollbar
+ nsAutoString maxposStr;
+
+ nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+
+ int32_t size = rowHeightAsPixels * (mRowCount > mPageLength ? mRowCount - mPageLength : 0);
+ maxposStr.AppendInt(size);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Also set our page increment and decrement.
+ nscoord pageincrement = mPageLength*rowHeightAsPixels;
+ nsAutoString pageStr;
+ pageStr.AppendInt(pageincrement);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+ }
+
+ if (aParts.mHScrollbar && aParts.mColumnsFrame && aWeakColumnsFrame.IsAlive()) {
+ // And now Horizontal scrollbar
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ nsAutoString maxposStr;
+
+ maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ nsAutoString pageStr;
+ pageStr.AppendInt(bounds.width);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ pageStr.Truncate();
+ pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16));
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true);
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+}
+
+// Takes client x/y in pixels, converts them to appunits, and converts into
+// values relative to this nsTreeBodyFrame frame.
+nsPoint
+nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY)
+{
+ nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
+ nsPresContext::CSSPixelsToAppUnits(aY));
+
+ nsPresContext* presContext = PresContext();
+ point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());
+
+ // Adjust by the inner box coords, so that we're in the inner box's
+ // coordinate space.
+ point -= mInnerBox.TopLeft();
+ return point;
+} // AdjustClientCoordsToBoxCoordSpace
+
+nsresult
+nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY, int32_t* _retval)
+{
+ if (!mView)
+ return NS_OK;
+
+ nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
+
+ // Check if the coordinates are above our visible space.
+ if (point.y < 0) {
+ *_retval = -1;
+ return NS_OK;
+ }
+
+ *_retval = GetRowAt(point.x, point.y);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow, nsITreeColumn** aCol,
+ nsACString& aChildElt)
+{
+ if (!mView)
+ return NS_OK;
+
+ nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
+
+ // Check if the coordinates are above our visible space.
+ if (point.y < 0) {
+ *aRow = -1;
+ return NS_OK;
+ }
+
+ nsTreeColumn* col;
+ nsIAtom* child;
+ GetCellAt(point.x, point.y, aRow, &col, &child);
+
+ if (col) {
+ NS_ADDREF(*aCol = col);
+ if (child == nsCSSAnonBoxes::moztreecell)
+ aChildElt.AssignLiteral("cell");
+ else if (child == nsCSSAnonBoxes::moztreetwisty)
+ aChildElt.AssignLiteral("twisty");
+ else if (child == nsCSSAnonBoxes::moztreeimage)
+ aChildElt.AssignLiteral("image");
+ else if (child == nsCSSAnonBoxes::moztreecelltext)
+ aChildElt.AssignLiteral("text");
+ }
+
+ return NS_OK;
+}
+
+
+//
+// GetCoordsForCellItem
+//
+// Find the x/y location and width/height (all in PIXELS) of the given object
+// in the given column.
+//
+// XXX IMPORTANT XXX:
+// Hyatt says in the bug for this, that the following needs to be done:
+// (1) You need to deal with overflow when computing cell rects. See other column
+// iteration examples... if you don't deal with this, you'll mistakenly extend the
+// cell into the scrollbar's rect.
+//
+// (2) You are adjusting the cell rect by the *row" border padding. That's
+// wrong. You need to first adjust a row rect by its border/padding, and then the
+// cell rect fits inside the adjusted row rect. It also can have border/padding
+// as well as margins. The vertical direction isn't that important, but you need
+// to get the horizontal direction right.
+//
+// (3) GetImageSize() does not include margins (but it does include border/padding).
+// You need to make sure to add in the image's margins as well.
+//
+nsresult
+nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement,
+ int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight)
+{
+ *aX = 0;
+ *aY = 0;
+ *aWidth = 0;
+ *aHeight = 0;
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord currX = mInnerBox.x - mHorzPosition;
+
+ // The Rect for the requested item.
+ nsRect theRect;
+
+ nsPresContext* presContext = PresContext();
+
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; currCol = currCol->GetNext()) {
+
+ // The Rect for the current cell.
+ nscoord colWidth;
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ currCol->GetWidthInTwips(this, &colWidth);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");
+
+ nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex),
+ colWidth, mRowHeight);
+
+ // Check the ID of the current column to see if it matches. If it doesn't
+ // increment the current X value and continue to the next column.
+ if (currCol != aCol) {
+ currX += cellRect.width;
+ continue;
+ }
+ // Now obtain the properties for our cell.
+ PrefillPropertyArray(aRow, currCol);
+
+ nsAutoString properties;
+ mView->GetCellProperties(aRow, currCol, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+
+ // We don't want to consider any of the decorations that may be present
+ // on the current row, so we have to deflate the rect by the border and
+ // padding and offset its left and top coordinates appropriately.
+ AdjustForBorderPadding(rowContext, cellRect);
+
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ NS_NAMED_LITERAL_CSTRING(cell, "cell");
+ if (currCol->IsCycler() || cell.Equals(aElement)) {
+ // If the current Column is a Cycler, then the Rect is just the cell - the margins.
+ // Similarly, if we're just being asked for the cell rect, provide it.
+
+ theRect = cellRect;
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ theRect.Deflate(cellMargin);
+ break;
+ }
+
+ // Since we're not looking for the cell, and since the cell isn't a cycler,
+ // we're looking for some subcomponent, and now we need to subtract the
+ // borders and padding of the cell from cellRect so this does not
+ // interfere with our computations.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ nsRenderingContext rc(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ // Now we'll start making our way across the cell, starting at the edge of
+ // the cell and proceeding until we hit the right edge. |cellX| is the
+ // working X value that we will increment as we crawl from left to right.
+ nscoord cellX = cellRect.x;
+ nscoord remainWidth = cellRect.width;
+
+ if (currCol->IsPrimary()) {
+ // If the current Column is a Primary, then we need to take into account the indentation
+ // and possibly a twisty.
+
+ // The amount of indentation is the indentation width (|mIndentation|) by the level.
+ int32_t level;
+ mView->GetLevel(aRow, &level);
+ if (!isRTL)
+ cellX += mIndentation * level;
+ remainWidth -= mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ nsRect imageRect;
+ nsRect twistyRect(cellRect);
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+ GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
+ twistyContext);
+
+ if (NS_LITERAL_CSTRING("twisty").Equals(aElement)) {
+ // If we're looking for the twisty Rect, just return the size
+ theRect = twistyRect;
+ break;
+ }
+
+ // Now we need to add in the margins of the twisty element, so that we
+ // can find the offset of the next element in the cell.
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ // Adjust our working X value with the twisty width (image size, margins,
+ // borders, padding.
+ if (!isRTL)
+ cellX += twistyRect.width;
+ }
+
+ // Cell Image
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
+ if (NS_LITERAL_CSTRING("image").Equals(aElement)) {
+ theRect = imageSize;
+ theRect.x = cellX;
+ theRect.y = cellRect.y;
+ break;
+ }
+
+ // Add in the margins of the cell image.
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageSize.Inflate(imageMargin);
+
+ // Increment cellX by the image width
+ if (!isRTL)
+ cellX += imageSize.width;
+
+ // Cell Text
+ nsAutoString cellText;
+ mView->GetCellText(aRow, currCol, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ // Create a scratch rect to represent the text rectangle, with the current
+ // X and Y coords, and a guess at the width and height. The width is the
+ // remaining width we have left to traverse in the cell, which will be the
+ // widest possible value for the text rect, and the row height.
+ nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);
+
+ // Measure the width of the text. If the width of the text is greater than
+ // the remaining width available, then we just assume that the text has
+ // been cropped and use the remaining rect as the text Rect. Otherwise,
+ // we add in borders and padding to the text dimension and give that back.
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ nscoord height = fm->MaxHeight();
+
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ // Center the text. XXX Obey vertical-align style prop?
+ if (height < textRect.height) {
+ textRect.y += (textRect.height - height) / 2;
+ textRect.height = height;
+ }
+
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(textContext, bp);
+ textRect.height += bp.top + bp.bottom;
+
+ AdjustForCellText(cellText, aRow, currCol, rc, *fm, textRect);
+
+ theRect = textRect;
+ }
+
+ if (isRTL)
+ theRect.x = mInnerBox.width - theRect.x - theRect.width;
+
+ *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x);
+ *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y);
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width);
+ *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height);
+
+ return NS_OK;
+}
+
+int32_t
+nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY)
+{
+ // Now just mod by our total inner box height and add to our top row index.
+ int32_t row = (aY/mRowHeight)+mTopRowIndex;
+
+ // Check if the coordinates are below our visible space (or within our visible
+ // space but below any row).
+ if (row > mTopRowIndex + mPageLength || row >= mRowCount)
+ return -1;
+
+ return row;
+}
+
+void
+nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText)
+{
+ // We could check to see whether the prescontext already has bidi enabled,
+ // but usually it won't, so it's probably faster to avoid the call to
+ // GetPresContext() when it's not needed.
+ if (HasRTLChars(aText)) {
+ PresContext()->SetBidiEnabled();
+ }
+}
+
+void
+nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText,
+ int32_t aRowIndex, nsTreeColumn* aColumn,
+ nsRenderingContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsRect& aTextRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ nscoord maxWidth = aTextRect.width;
+ bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText,
+ aFontMetrics,
+ drawTarget,
+ maxWidth);
+
+ if (aColumn->Overflow()) {
+ DebugOnly<nsresult> rv;
+ nsTreeColumn* nextColumn = aColumn->GetNext();
+ while (nextColumn && widthIsGreater) {
+ while (nextColumn) {
+ nscoord width;
+ rv = nextColumn->GetWidthInTwips(this, &width);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
+
+ if (width != 0)
+ break;
+
+ nextColumn = nextColumn->GetNext();
+ }
+
+ if (nextColumn) {
+ nsAutoString nextText;
+ mView->GetCellText(aRowIndex, nextColumn, nextText);
+ // We don't measure or draw this text so no need to check it for
+ // bidi-ness
+
+ if (nextText.Length() == 0) {
+ nscoord width;
+ rv = nextColumn->GetWidthInTwips(this, &width);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
+
+ maxWidth += width;
+ widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText,
+ aFontMetrics,
+ drawTarget,
+ maxWidth);
+
+ nextColumn = nextColumn->GetNext();
+ }
+ else {
+ nextColumn = nullptr;
+ }
+ }
+ }
+ }
+
+ nscoord width;
+ if (widthIsGreater) {
+ // See if the width is even smaller than the ellipsis
+ // If so, clear the text completely.
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ aFontMetrics.SetTextRunRTL(false);
+ nscoord ellipsisWidth =
+ nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);
+
+ width = maxWidth;
+ if (ellipsisWidth > width)
+ aText.SetLength(0);
+ else if (ellipsisWidth == width)
+ aText.Assign(kEllipsis);
+ else {
+ // We will be drawing an ellipsis, thank you very much.
+ // Subtract out the required width of the ellipsis.
+ // This is the total remaining width we have to play with.
+ width -= ellipsisWidth;
+
+ // Now we crop.
+ switch (aColumn->GetCropStyle()) {
+ default:
+ case 0: {
+ // Crop right.
+ nscoord cwidth;
+ nscoord twidth = 0;
+ uint32_t length = aText.Length();
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ char16_t ch = aText[i];
+ // XXX this is horrible and doesn't handle clusters
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ if (twidth + cwidth > width)
+ break;
+ twidth += cwidth;
+ }
+ aText.Truncate(i);
+ aText.Append(kEllipsis);
+ }
+ break;
+
+ case 2: {
+ // Crop left.
+ nscoord cwidth;
+ nscoord twidth = 0;
+ int32_t length = aText.Length();
+ int32_t i;
+ for (i=length-1; i >= 0; --i) {
+ char16_t ch = aText[i];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ if (twidth + cwidth > width)
+ break;
+ twidth += cwidth;
+ }
+
+ nsAutoString copy;
+ aText.Right(copy, length-1-i);
+ aText.Assign(kEllipsis);
+ aText += copy;
+ }
+ break;
+
+ case 1:
+ {
+ // Crop center.
+ nsAutoString leftStr, rightStr;
+ nscoord cwidth, twidth = 0;
+ int32_t length = aText.Length();
+ int32_t rightPos = length - 1;
+ for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) {
+ char16_t ch = aText[leftPos];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ twidth += cwidth;
+ if (twidth > width)
+ break;
+ leftStr.Append(ch);
+
+ ch = aText[rightPos];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ twidth += cwidth;
+ if (twidth > width)
+ break;
+ rightStr.Insert(ch, 0);
+ --rightPos;
+ }
+ aText = leftStr;
+ aText.Append(kEllipsis);
+ aText += rightStr;
+ }
+ break;
+ }
+ }
+ }
+
+ width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, aFontMetrics,
+ aRenderingContext);
+
+ switch (aColumn->GetTextAlignment()) {
+ case NS_STYLE_TEXT_ALIGN_RIGHT: {
+ aTextRect.x += aTextRect.width - width;
+ }
+ break;
+ case NS_STYLE_TEXT_ALIGN_CENTER: {
+ aTextRect.x += (aTextRect.width - width) / 2;
+ }
+ break;
+ }
+
+ aTextRect.width = width;
+}
+
+nsIAtom*
+nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
+ int32_t aRowIndex,
+ nsTreeColumn* aColumn)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Obtain the properties for our cell.
+ PrefillPropertyArray(aRowIndex, aColumn);
+ nsAutoString properties;
+ mView->GetCellProperties(aRowIndex, aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the cell.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect cellRect(aCellRect);
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ cellRect.Deflate(cellMargin);
+
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) {
+ // The user clicked within the cell's margins/borders/padding. This constitutes a click on the cell.
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ nscoord currX = cellRect.x;
+ nscoord remainingWidth = cellRect.width;
+
+ // Handle right alignment hit testing.
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ nsPresContext* presContext = PresContext();
+ nsRenderingContext rc(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ if (aColumn->IsPrimary()) {
+ // If we're the primary column, we have indentation and a twisty.
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+
+ if (!isRTL)
+ currX += mIndentation*level;
+ remainingWidth -= mIndentation*level;
+
+ if ((isRTL && aX > currX + remainingWidth) ||
+ (!isRTL && aX < currX)) {
+ // The user clicked within the indentation.
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ // Always leave space for the twisty.
+ nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ bool hasTwisty = false;
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty)
+ hasTwisty = true;
+ }
+
+ // Resolve style for the twisty.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext,
+ twistyContext);
+
+ // We will treat a click as hitting the twisty if it happens on the margins, borders, padding,
+ // or content of the twisty object. By allowing a "slop" into the margin, we make it a little
+ // bit easier for a user to hit the twisty. (We don't want to be too picky here.)
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+ if (isRTL)
+ twistyRect.x = currX + remainingWidth - twistyRect.width;
+
+ // Now we test to see if aX is actually within the twistyRect. If it is, and if the item should
+ // have a twisty, then we return "twisty". If it is within the rect but we shouldn't have a twisty,
+ // then we return "cell".
+ if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) {
+ if (hasTwisty)
+ return nsCSSAnonBoxes::moztreetwisty;
+ else
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ if (!isRTL)
+ currX += twistyRect.width;
+ remainingWidth -= twistyRect.width;
+ }
+
+ // Now test to see if the user hit the icon for the cell.
+ nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
+
+ // Resolve style for the image.
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext);
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ iconSize.Inflate(imageMargin);
+ iconRect.width = iconSize.width;
+ if (isRTL)
+ iconRect.x = currX + remainingWidth - iconRect.width;
+
+ if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) {
+ // The user clicked on the image.
+ return nsCSSAnonBoxes::moztreeimage;
+ }
+
+ if (!isRTL)
+ currX += iconRect.width;
+ remainingWidth -= iconRect.width;
+
+ nsAutoString cellText;
+ mView->GetCellText(aRowIndex, aColumn, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height);
+
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ AdjustForBorderPadding(textContext, textRect);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ AdjustForCellText(cellText, aRowIndex, aColumn, rc, *fm, textRect);
+
+ if (aX >= textRect.x && aX < textRect.x + textRect.width)
+ return nsCSSAnonBoxes::moztreecelltext;
+ else
+ return nsCSSAnonBoxes::moztreecell;
+}
+
+void
+nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
+ nsTreeColumn** aCol, nsIAtom** aChildElt)
+{
+ *aCol = nullptr;
+ *aChildElt = nullptr;
+
+ *aRow = GetRowAt(aX, aY);
+ if (*aRow < 0)
+ return;
+
+ // Determine the column hit.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect cellRect;
+ nsresult rv = currCol->GetRect(this,
+ mInnerBox.y +
+ mRowHeight * (*aRow - mTopRowIndex),
+ mRowHeight,
+ &cellRect);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("column has no frame");
+ continue;
+ }
+
+ if (!OffsetForHorzScroll(cellRect, false))
+ continue;
+
+ if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) {
+ // We know the column hit now.
+ *aCol = currCol;
+
+ if (currCol->IsCycler())
+ // Cyclers contain only images. Fill this in immediately and return.
+ *aChildElt = nsCSSAnonBoxes::moztreeimage;
+ else
+ *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
+ break;
+ }
+ }
+}
+
+nsresult
+nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
+ nsRenderingContext* aRenderingContext,
+ nscoord& aDesiredSize, nscoord& aCurrentSize)
+{
+ NS_PRECONDITION(aCol, "aCol must not be null");
+ NS_PRECONDITION(aRenderingContext, "aRenderingContext must not be null");
+
+ // The rect for the current cell.
+ nscoord colWidth;
+ nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsRect cellRect(0, 0, colWidth, mRowHeight);
+
+ int32_t overflow = cellRect.x+cellRect.width-(mInnerBox.x+mInnerBox.width);
+ if (overflow > 0)
+ cellRect.width -= overflow;
+
+ // Adjust borders and padding for the cell.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(cellContext, bp);
+
+ aCurrentSize = cellRect.width;
+ aDesiredSize = bp.left + bp.right;
+
+ if (aCol->IsPrimary()) {
+ // If the current Column is a Primary, then we need to take into account
+ // the indentation and possibly a twisty.
+
+ // The amount of indentation is the indentation width (|mIndentation|) by the level.
+ int32_t level;
+ mView->GetLevel(aRow, &level);
+ aDesiredSize += mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ nsRect twistyRect(cellRect);
+ GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(),
+ twistyContext);
+
+ // Add in the margins of the twisty element.
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ aDesiredSize += twistyRect.width;
+ }
+
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ // Account for the width of the cell image.
+ nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext);
+ // Add in the margins of the cell image.
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageSize.Inflate(imageMargin);
+
+ aDesiredSize += imageSize.width;
+
+ // Get the cell text.
+ nsAutoString cellText;
+ mView->GetCellText(aRow, aCol, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ // Get the borders and padding for the text.
+ GetBorderPadding(textContext, bp);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ // Get the width of the text itself
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm,
+ *aRenderingContext);
+ nscoord totalTextWidth = width + bp.left + bp.right;
+ aDesiredSize += totalTextWidth;
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval)
+{
+ nscoord currentSize, desiredSize;
+ nsresult rv;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nsRenderingContext rc(
+ PresContext()->PresShell()->CreateReferenceRenderingContext());
+
+ rv = GetCellWidth(aRow, col, &rc, desiredSize, currentSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = desiredSize > currentSize;
+
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::MarkDirtyIfSelect()
+{
+ nsIContent* baseElement = GetBaseElement();
+
+ if (baseElement && baseElement->IsHTMLElement(nsGkAtoms::select)) {
+ // If we are an intrinsically sized select widget, we may need to
+ // resize, if the widest item was removed or a new item was added.
+ // XXX optimize this more
+
+ mStringWidth = -1;
+ PresContext()->PresShell()->FrameNeedsReflow(this,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+nsresult
+nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
+ nsTimerCallbackFunc aFunc, int32_t aType,
+ nsITimer** aTimer)
+{
+ // Get the delay from the look and feel service.
+ int32_t delay = LookAndFeel::GetInt(aID, 0);
+
+ nsCOMPtr<nsITimer> timer;
+
+ // Create a new timer only if the delay is greater than zero.
+ // Zero value means that this feature is completely disabled.
+ if (delay > 0) {
+ timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (timer)
+ timer->InitWithFuncCallback(aFunc, this, delay, aType);
+ }
+
+ NS_IF_ADDREF(*aTimer = timer);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount)
+{
+ if (aCount == 0 || !mView)
+ return NS_OK; // Nothing to do.
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireRowCountChangedEvent(aIndex, aCount);
+#endif
+
+ // Adjust our selection.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->AdjustSelection(aIndex, aCount);
+
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ mRowCount += aCount;
+#ifdef DEBUG
+ int32_t rowCount = mRowCount;
+ mView->GetRowCount(&rowCount);
+ NS_ASSERTION(rowCount == mRowCount, "row count did not change by the amount suggested, check caller");
+#endif
+
+ int32_t count = Abs(aCount);
+ int32_t last = LastVisibleRow();
+ if (aIndex >= mTopRowIndex && aIndex <= last)
+ InvalidateRange(aIndex, last);
+
+ ScrollParts parts = GetScrollParts();
+
+ if (mTopRowIndex == 0) {
+ // Just update the scrollbar and return.
+ if (FullScrollbarsUpdate(false)) {
+ MarkDirtyIfSelect();
+ }
+ return NS_OK;
+ }
+
+ bool needsInvalidation = false;
+ // Adjust our top row index.
+ if (aCount > 0) {
+ if (mTopRowIndex > aIndex) {
+ // Rows came in above us. Augment the top row index.
+ mTopRowIndex += aCount;
+ }
+ }
+ else if (aCount < 0) {
+ if (mTopRowIndex > aIndex+count-1) {
+ // No need to invalidate. The remove happened
+ // completely above us (offscreen).
+ mTopRowIndex -= count;
+ }
+ else if (mTopRowIndex >= aIndex) {
+ // This is a full-blown invalidate.
+ if (mTopRowIndex + mPageLength > mRowCount - 1) {
+ mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
+ }
+ needsInvalidation = true;
+ }
+ }
+
+ if (FullScrollbarsUpdate(needsInvalidation)) {
+ MarkDirtyIfSelect();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::BeginUpdateBatch()
+{
+ ++mUpdateBatchNest;
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::EndUpdateBatch()
+{
+ NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
+
+ if (--mUpdateBatchNest == 0) {
+ if (mView) {
+ Invalidate();
+ int32_t countBeforeUpdate = mRowCount;
+ mView->GetRowCount(&mRowCount);
+ if (countBeforeUpdate != mRowCount) {
+ if (mTopRowIndex + mPageLength > mRowCount - 1) {
+ mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
+ }
+ FullScrollbarsUpdate(false);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol)
+{
+ NS_PRECONDITION(!aCol || aCol->GetFrame(), "invalid column passed");
+ mScratchArray.Clear();
+
+ // focus
+ if (mFocused)
+ mScratchArray.AppendElement(nsGkAtoms::focus);
+
+ // sort
+ bool sorted = false;
+ mView->IsSorted(&sorted);
+ if (sorted)
+ mScratchArray.AppendElement(nsGkAtoms::sorted);
+
+ // drag session
+ if (mSlots && mSlots->mIsDragging)
+ mScratchArray.AppendElement(nsGkAtoms::dragSession);
+
+ if (aRowIndex != -1) {
+ if (aRowIndex == mMouseOverRow)
+ mScratchArray.AppendElement(nsGkAtoms::hover);
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+
+ if (selection) {
+ // selected
+ bool isSelected;
+ selection->IsSelected(aRowIndex, &isSelected);
+ if (isSelected)
+ mScratchArray.AppendElement(nsGkAtoms::selected);
+
+ // current
+ int32_t currentIndex;
+ selection->GetCurrentIndex(&currentIndex);
+ if (aRowIndex == currentIndex)
+ mScratchArray.AppendElement(nsGkAtoms::current);
+
+ // active
+ if (aCol) {
+ nsCOMPtr<nsITreeColumn> currentColumn;
+ selection->GetCurrentColumn(getter_AddRefs(currentColumn));
+ if (aCol == currentColumn)
+ mScratchArray.AppendElement(nsGkAtoms::active);
+ }
+ }
+
+ // container or leaf
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ mScratchArray.AppendElement(nsGkAtoms::container);
+
+ // open or closed
+ bool isOpen = false;
+ mView->IsContainerOpen(aRowIndex, &isOpen);
+ if (isOpen)
+ mScratchArray.AppendElement(nsGkAtoms::open);
+ else
+ mScratchArray.AppendElement(nsGkAtoms::closed);
+ }
+ else {
+ mScratchArray.AppendElement(nsGkAtoms::leaf);
+ }
+
+ // drop orientation
+ if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
+ if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE)
+ mScratchArray.AppendElement(nsGkAtoms::dropBefore);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_ON)
+ mScratchArray.AppendElement(nsGkAtoms::dropOn);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
+ mScratchArray.AppendElement(nsGkAtoms::dropAfter);
+ }
+
+ // odd or even
+ if (aRowIndex % 2)
+ mScratchArray.AppendElement(nsGkAtoms::odd);
+ else
+ mScratchArray.AppendElement(nsGkAtoms::even);
+
+ nsIContent* baseContent = GetBaseElement();
+ if (baseContent && baseContent->HasAttr(kNameSpaceID_None, nsGkAtoms::editing))
+ mScratchArray.AppendElement(nsGkAtoms::editing);
+
+ // multiple columns
+ if (mColumns->GetColumnAt(1))
+ mScratchArray.AppendElement(nsGkAtoms::multicol);
+ }
+
+ if (aCol) {
+ mScratchArray.AppendElement(aCol->GetAtom());
+
+ if (aCol->IsPrimary())
+ mScratchArray.AppendElement(nsGkAtoms::primary);
+
+ if (aCol->GetType() == nsITreeColumn::TYPE_CHECKBOX) {
+ mScratchArray.AppendElement(nsGkAtoms::checkbox);
+
+ if (aRowIndex != -1) {
+ nsAutoString value;
+ mView->GetCellValue(aRowIndex, aCol, value);
+ if (value.EqualsLiteral("true"))
+ mScratchArray.AppendElement(nsGkAtoms::checked);
+ }
+ }
+ else if (aCol->GetType() == nsITreeColumn::TYPE_PROGRESSMETER) {
+ mScratchArray.AppendElement(nsGkAtoms::progressmeter);
+
+ if (aRowIndex != -1) {
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aCol, &state);
+ if (state == nsITreeView::PROGRESS_NORMAL)
+ mScratchArray.AppendElement(nsGkAtoms::progressNormal);
+ else if (state == nsITreeView::PROGRESS_UNDETERMINED)
+ mScratchArray.AppendElement(nsGkAtoms::progressUndetermined);
+ }
+ }
+
+ // Read special properties from attributes on the column content node
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::insertbefore,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement(nsGkAtoms::insertbefore);
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::insertafter,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement(nsGkAtoms::insertafter);
+ }
+}
+
+nsITheme*
+nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRect& aImageRect,
+ nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsStyleContext* aTwistyContext)
+{
+ // The twisty rect extends all the way to the end of the cell. This is incorrect. We need to
+ // determine the twisty rect's true width. This is done by examining the style context for
+ // a width first. If it has one, we use that. If it doesn't, we use the image's natural width.
+ // If the image hasn't loaded and if no width is specified, then we just bail. If there is
+ // a -moz-appearance involved, adjust the rect by the minimum widget size provided by
+ // the theme implementation.
+ aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext);
+ if (aImageRect.height > aTwistyRect.height)
+ aImageRect.height = aTwistyRect.height;
+ if (aImageRect.width > aTwistyRect.width)
+ aImageRect.width = aTwistyRect.width;
+ else
+ aTwistyRect.width = aImageRect.width;
+
+ bool useTheme = false;
+ nsITheme *theme = nullptr;
+ const nsStyleDisplay* twistyDisplayData = aTwistyContext->StyleDisplay();
+ if (twistyDisplayData->mAppearance) {
+ theme = aPresContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, twistyDisplayData->mAppearance))
+ useTheme = true;
+ }
+
+ if (useTheme) {
+ LayoutDeviceIntSize minTwistySizePx;
+ bool canOverride = true;
+ theme->GetMinimumWidgetSize(aPresContext, this, twistyDisplayData->mAppearance,
+ &minTwistySizePx, &canOverride);
+
+ // GMWS() returns size in pixels, we need to convert it back to app units
+ nsSize minTwistySize;
+ minTwistySize.width = aPresContext->DevPixelsToAppUnits(minTwistySizePx.width);
+ minTwistySize.height = aPresContext->DevPixelsToAppUnits(minTwistySizePx.height);
+
+ if (aTwistyRect.width < minTwistySize.width || !canOverride)
+ aTwistyRect.width = minTwistySize.width;
+ }
+
+ return useTheme ? theme : nullptr;
+}
+
+nsresult
+nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult)
+{
+ *aResult = nullptr;
+
+ nsAutoString imageSrc;
+ mView->GetImageSrc(aRowIndex, aCol, imageSrc);
+ RefPtr<imgRequestProxy> styleRequest;
+ if (!aUseContext && !imageSrc.IsEmpty()) {
+ aAllowImageRegions = false;
+ }
+ else {
+ // Obtain the URL from the style context.
+ aAllowImageRegions = true;
+ styleRequest = aStyleContext->StyleList()->GetListStyleImage();
+ if (!styleRequest)
+ return NS_OK;
+ nsCOMPtr<nsIURI> uri;
+ styleRequest->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, imageSrc);
+ }
+
+ // Look the image up in our cache.
+ nsTreeImageCacheEntry entry;
+ if (mImageCache.Get(imageSrc, &entry)) {
+ // Find out if the image has loaded.
+ uint32_t status;
+ imgIRequest *imgReq = entry.request;
+ imgReq->GetImageStatus(&status);
+ imgReq->GetImage(aResult); // We hand back the image here. The GetImage call addrefs *aResult.
+ bool animated = true; // Assuming animated is the safe option
+
+ // We can only call GetAnimated if we're decoded
+ if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
+ (*aResult)->GetAnimated(&animated);
+
+ if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
+ // We either aren't done loading, or we're animating. Add our row as a listener for invalidations.
+ nsCOMPtr<imgINotificationObserver> obs;
+ imgReq->GetNotificationObserver(getter_AddRefs(obs));
+
+ if (obs) {
+ static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol);
+ }
+
+ return NS_OK;
+ }
+ }
+
+ if (!*aResult) {
+ // Create a new nsTreeImageListener object and pass it our row and column
+ // information.
+ nsTreeImageListener* listener = new nsTreeImageListener(this);
+ if (!listener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (!mCreatedListeners.PutEntry(listener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ listener->AddCell(aRowIndex, aCol);
+ nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
+
+ RefPtr<imgRequestProxy> imageRequest;
+ if (styleRequest) {
+ styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest));
+ } else {
+ nsIDocument* doc = mContent->GetComposedDoc();
+ if (!doc)
+ // The page is currently being torn down. Why bother.
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+
+ nsCOMPtr<nsIURI> srcURI;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcURI),
+ imageSrc,
+ doc,
+ baseURI);
+ if (!srcURI)
+ return NS_ERROR_FAILURE;
+
+ // XXXbz what's the origin principal for this stuff that comes from our
+ // view? I guess we should assume that it's the node's principal...
+ nsresult rv = nsContentUtils::LoadImage(srcURI,
+ mContent,
+ doc,
+ mContent->NodePrincipal(),
+ doc->GetDocumentURI(),
+ doc->GetReferrerPolicy(),
+ imgNotificationObserver,
+ nsIRequest::LOAD_NORMAL,
+ EmptyString(),
+ getter_AddRefs(imageRequest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ listener->UnsuppressInvalidation();
+
+ if (!imageRequest)
+ return NS_ERROR_FAILURE;
+
+ // We don't want discarding/decode-on-draw for xul images
+ imageRequest->StartDecoding();
+ imageRequest->LockImage();
+
+ // In a case it was already cached.
+ imageRequest->GetImage(aResult);
+ nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
+ mImageCache.Put(imageSrc, cacheEntry);
+ }
+ return NS_OK;
+}
+
+nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext)
+{
+ // XXX We should respond to visibility rules for collapsed vs. hidden.
+
+ // This method returns the width of the twisty INCLUDING borders and padding.
+ // It first checks the style context for a width. If none is found, it tries to
+ // use the default image width for the twisty. If no image is found, it defaults
+ // to border+padding.
+ nsRect r(0,0,0,0);
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(aStyleContext, bp);
+ r.Inflate(bp);
+
+ // Now r contains our border+padding info. We now need to get our width and
+ // height.
+ bool needWidth = false;
+ bool needHeight = false;
+
+ // We have to load image even though we already have a size.
+ // Don't change this, otherwise things start to go crazy.
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aCol, aUseContext, aStyleContext, useImageRegion, getter_AddRefs(image));
+
+ const nsStylePosition* myPosition = aStyleContext->StylePosition();
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion) {
+ r.x += myList->mImageRegion.x;
+ r.y += myList->mImageRegion.y;
+ }
+
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ int32_t val = myPosition->mWidth.GetCoordValue();
+ r.width += val;
+ }
+ else if (useImageRegion && myList->mImageRegion.width > 0)
+ r.width += myList->mImageRegion.width;
+ else
+ needWidth = true;
+
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
+ int32_t val = myPosition->mHeight.GetCoordValue();
+ r.height += val;
+ }
+ else if (useImageRegion && myList->mImageRegion.height > 0)
+ r.height += myList->mImageRegion.height;
+ else
+ needHeight = true;
+
+ if (image) {
+ if (needWidth || needHeight) {
+ // Get the natural image size.
+
+ if (needWidth) {
+ // Get the size from the image.
+ nscoord width;
+ image->GetWidth(&width);
+ r.width += nsPresContext::CSSPixelsToAppUnits(width);
+ }
+
+ if (needHeight) {
+ nscoord height;
+ image->GetHeight(&height);
+ r.height += nsPresContext::CSSPixelsToAppUnits(height);
+ }
+ }
+ }
+
+ return r;
+}
+
+// GetImageDestSize returns the destination size of the image.
+// The width and height do not include borders and padding.
+// The width and height have not been adjusted to fit in the row height
+// or cell width.
+// The width and height reflect the destination size specified in CSS,
+// or the image region specified in CSS, or the natural size of the
+// image.
+// If only the destination width has been specified in CSS, the height is
+// calculated to maintain the aspect ratio of the image.
+// If only the destination height has been specified in CSS, the width is
+// calculated to maintain the aspect ratio of the image.
+nsSize
+nsTreeBodyFrame::GetImageDestSize(nsStyleContext* aStyleContext,
+ bool useImageRegion,
+ imgIContainer* image)
+{
+ nsSize size(0,0);
+
+ // We need to get the width and height.
+ bool needWidth = false;
+ bool needHeight = false;
+
+ // Get the style position to see if the CSS has specified the
+ // destination width/height.
+ const nsStylePosition* myPosition = aStyleContext->StylePosition();
+
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ // CSS has specified the destination width.
+ size.width = myPosition->mWidth.GetCoordValue();
+ }
+ else {
+ // We'll need to get the width of the image/region.
+ needWidth = true;
+ }
+
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
+ // CSS has specified the destination height.
+ size.height = myPosition->mHeight.GetCoordValue();
+ }
+ else {
+ // We'll need to get the height of the image/region.
+ needHeight = true;
+ }
+
+ if (needWidth || needHeight) {
+ // We need to get the size of the image/region.
+ nsSize imageSize(0,0);
+
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion && myList->mImageRegion.width > 0) {
+ // CSS has specified an image region.
+ // Use the width of the image region.
+ imageSize.width = myList->mImageRegion.width;
+ }
+ else if (image) {
+ nscoord width;
+ image->GetWidth(&width);
+ imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
+ }
+
+ if (useImageRegion && myList->mImageRegion.height > 0) {
+ // CSS has specified an image region.
+ // Use the height of the image region.
+ imageSize.height = myList->mImageRegion.height;
+ }
+ else if (image) {
+ nscoord height;
+ image->GetHeight(&height);
+ imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
+ }
+
+ if (needWidth) {
+ if (!needHeight && imageSize.height != 0) {
+ // The CSS specified the destination height, but not the destination
+ // width. We need to calculate the width so that we maintain the
+ // image's aspect ratio.
+ size.width = imageSize.width * size.height / imageSize.height;
+ }
+ else {
+ size.width = imageSize.width;
+ }
+ }
+
+ if (needHeight) {
+ if (!needWidth && imageSize.width != 0) {
+ // The CSS specified the destination width, but not the destination
+ // height. We need to calculate the height so that we maintain the
+ // image's aspect ratio.
+ size.height = imageSize.height * size.width / imageSize.width;
+ }
+ else {
+ size.height = imageSize.height;
+ }
+ }
+ }
+
+ return size;
+}
+
+// GetImageSourceRect returns the source rectangle of the image to be
+// displayed.
+// The width and height reflect the image region specified in CSS, or
+// the natural size of the image.
+// The width and height do not include borders and padding.
+// The width and height do not reflect the destination size specified
+// in CSS.
+nsRect
+nsTreeBodyFrame::GetImageSourceRect(nsStyleContext* aStyleContext,
+ bool useImageRegion,
+ imgIContainer* image)
+{
+ nsRect r(0,0,0,0);
+
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion &&
+ (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) {
+ // CSS has specified an image region.
+ r = myList->mImageRegion;
+ }
+ else if (image) {
+ // Use the actual image size.
+ nscoord coord;
+ image->GetWidth(&coord);
+ r.width = nsPresContext::CSSPixelsToAppUnits(coord);
+ image->GetHeight(&coord);
+ r.height = nsPresContext::CSSPixelsToAppUnits(coord);
+ }
+
+ return r;
+}
+
+int32_t nsTreeBodyFrame::GetRowHeight()
+{
+ // Look up the correct height. It is equal to the specified height
+ // + the specified margins.
+ mScratchArray.Clear();
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ if (rowContext) {
+ const nsStylePosition* myPosition = rowContext->StylePosition();
+
+ nscoord minHeight = 0;
+ if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord)
+ minHeight = myPosition->mMinHeight.GetCoordValue();
+
+ nscoord height = 0;
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = myPosition->mHeight.GetCoordValue();
+
+ if (height < minHeight)
+ height = minHeight;
+
+ if (height > 0) {
+ height = nsPresContext::AppUnitsToIntCSSPixels(height);
+ height += height % 2;
+ height = nsPresContext::CSSPixelsToAppUnits(height);
+
+ // XXX Check box-sizing to determine if border/padding should augment the height
+ // Inflate the height by our margins.
+ nsRect rowRect(0,0,0,height);
+ nsMargin rowMargin;
+ rowContext->StyleMargin()->GetMargin(rowMargin);
+ rowRect.Inflate(rowMargin);
+ height = rowRect.height;
+ return height;
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any.
+}
+
+int32_t nsTreeBodyFrame::GetIndentation()
+{
+ // Look up the correct indentation. It is equal to the specified indentation width.
+ mScratchArray.Clear();
+ nsStyleContext* indentContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeindentation);
+ if (indentContext) {
+ const nsStylePosition* myPosition = indentContext->StylePosition();
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ nscoord val = myPosition->mWidth.GetCoordValue();
+ return val;
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
+}
+
+void nsTreeBodyFrame::CalcInnerBox()
+{
+ mInnerBox.SetRect(0, 0, mRect.width, mRect.height);
+ AdjustForBorderPadding(mStyleContext, mInnerBox);
+}
+
+nscoord
+nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts)
+{
+ // Compute the adjustment to the last column. This varies depending on the
+ // visibility of the columnpicker and the scrollbar.
+ if (aParts.mColumnsFrame)
+ mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width;
+ else
+ mAdjustWidth = 0;
+
+ nscoord width = 0;
+
+ // We calculate this from the scrollable frame, so that it
+ // properly covers all contingencies of what could be
+ // scrollable (columns, body, etc...)
+
+ if (aParts.mColumnsScrollFrame) {
+ width = aParts.mColumnsScrollFrame->GetScrollRange().width +
+ aParts.mColumnsScrollFrame->GetScrollPortRect().width;
+ }
+
+ // If no horz scrolling periphery is present, then just return our width
+ if (width == 0)
+ width = mRect.width;
+
+ return width;
+}
+
+nsresult
+nsTreeBodyFrame::GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor)
+{
+ // Check the GetScriptHandlingObject so we don't end up running code when
+ // the document is a zombie.
+ bool dummy;
+ if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
+ int32_t row;
+ nsTreeColumn* col;
+ nsIAtom* child;
+ GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
+
+ if (child) {
+ // Our scratch array is already prefilled.
+ nsStyleContext* childContext = GetPseudoStyleContext(child);
+
+ FillCursorInformationFromStyle(childContext->StyleUserInterface(),
+ aCursor);
+ if (aCursor.mCursor == NS_STYLE_CURSOR_AUTO)
+ aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
+
+ return NS_OK;
+ }
+ }
+
+ return nsLeafBoxFrame::GetCursor(aPoint, aCursor);
+}
+
+static uint32_t GetDropEffect(WidgetGUIEvent* aEvent)
+{
+ NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
+ WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
+ nsContentUtils::SetDataTransferInEvent(dragEvent);
+
+ uint32_t action = 0;
+ if (dragEvent->mDataTransfer) {
+ dragEvent->mDataTransfer->GetDropEffectInt(&action);
+ }
+ return action;
+}
+
+nsresult
+nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+ int32_t newrow = GetRowAt(xTwips, yTwips);
+ if (mMouseOverRow != newrow) {
+ // redraw the old and the new row
+ if (mMouseOverRow != -1)
+ InvalidateRow(mMouseOverRow);
+ mMouseOverRow = newrow;
+ if (mMouseOverRow != -1)
+ InvalidateRow(mMouseOverRow);
+ }
+ } else if (aEvent->mMessage == eMouseOut) {
+ if (mMouseOverRow != -1) {
+ InvalidateRow(mMouseOverRow);
+ mMouseOverRow = -1;
+ }
+ } else if (aEvent->mMessage == eDragEnter) {
+ if (!mSlots)
+ mSlots = new Slots();
+
+ // Cache several things we'll need throughout the course of our work. These
+ // will all get released on a drag exit.
+
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ // Cache the drag session.
+ mSlots->mIsDragging = true;
+ mSlots->mDropRow = -1;
+ mSlots->mDropOrient = -1;
+ mSlots->mDragAction = GetDropEffect(aEvent);
+ } else if (aEvent->mMessage == eDragOver) {
+ // The mouse is hovering over this tree. If we determine things are
+ // different from the last time, invalidate the drop feedback at the old
+ // position, query the view to see if the current location is droppable,
+ // and then invalidate the drop feedback at the new location if it is.
+ // The mouse may or may not have changed position from the last time
+ // we were called, so optimize out a lot of the extra notifications by
+ // checking if anything changed first. For drop feedback we use drop,
+ // dropBefore and dropAfter property.
+
+ if (!mView || !mSlots)
+ return NS_OK;
+
+ // Save last values, we will need them.
+ int32_t lastDropRow = mSlots->mDropRow;
+ int16_t lastDropOrient = mSlots->mDropOrient;
+#ifndef XP_MACOSX
+ int16_t lastScrollLines = mSlots->mScrollLines;
+#endif
+
+ // Find out the current drag action
+ uint32_t lastDragAction = mSlots->mDragAction;
+ mSlots->mDragAction = GetDropEffect(aEvent);
+
+ // Compute the row mouse is over and the above/below/on state.
+ // Below we'll use this to see if anything changed.
+ // Also check if we want to auto-scroll.
+ ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient, &mSlots->mScrollLines);
+
+ // While we're here, handle tracking of scrolling during a drag.
+ if (mSlots->mScrollLines) {
+ if (mSlots->mDropAllowed) {
+ // Invalidate primary cell at old location.
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(lastDropRow, lastDropOrient);
+ }
+#ifdef XP_MACOSX
+ ScrollByLines(mSlots->mScrollLines);
+#else
+ if (!lastScrollLines) {
+ // Cancel any previously initialized timer.
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ // Set a timer to trigger the tree scrolling.
+ CreateTimer(LookAndFeel::eIntID_TreeLazyScrollDelay,
+ LazyScrollCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+#endif
+ // Bail out to prevent spring loaded timer and feedback line settings.
+ return NS_OK;
+ }
+
+ // If changed from last time, invalidate primary cell at the old location and if allowed,
+ // invalidate primary cell at the new location. If nothing changed, just bail.
+ if (mSlots->mDropRow != lastDropRow ||
+ mSlots->mDropOrient != lastDropOrient ||
+ mSlots->mDragAction != lastDragAction) {
+
+ // Invalidate row at the old location.
+ if (mSlots->mDropAllowed) {
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(lastDropRow, lastDropOrient);
+ }
+
+ if (mSlots->mTimer) {
+ // Timer is active but for a different row than the current one, kill it.
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ if (mSlots->mDropRow >= 0) {
+ if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) {
+ // Either there wasn't a timer running or it was just killed above.
+ // If over a folder, start up a timer to open the folder.
+ bool isContainer = false;
+ mView->IsContainer(mSlots->mDropRow, &isContainer);
+ if (isContainer) {
+ bool isOpen = false;
+ mView->IsContainerOpen(mSlots->mDropRow, &isOpen);
+ if (!isOpen) {
+ // This node isn't expanded, set a timer to expand it.
+ CreateTimer(LookAndFeel::eIntID_TreeOpenDelay,
+ OpenCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+ }
+ }
+
+ // The dataTransfer was initialized by the call to GetDropEffect above.
+ bool canDropAtNewLocation = false;
+ mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient,
+ aEvent->AsDragEvent()->mDataTransfer,
+ &canDropAtNewLocation);
+
+ if (canDropAtNewLocation) {
+ // Invalidate row at the new location.
+ mSlots->mDropAllowed = canDropAtNewLocation;
+ InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
+ }
+ }
+ }
+
+ // Indicate that the drop is allowed by preventing the default behaviour.
+ if (mSlots->mDropAllowed)
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ } else if (aEvent->mMessage == eDrop) {
+ // this event was meant for another frame, so ignore it
+ if (!mSlots)
+ return NS_OK;
+
+ // Tell the view where the drop happened.
+
+ // Remove the drop folder and all its parents from the array.
+ int32_t parentIndex;
+ nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex);
+ while (NS_SUCCEEDED(rv) && parentIndex >= 0) {
+ mSlots->mArray.RemoveElement(parentIndex);
+ rv = mView->GetParentIndex(parentIndex, &parentIndex);
+ }
+
+ NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
+ WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
+ nsContentUtils::SetDataTransferInEvent(dragEvent);
+
+ mView->Drop(mSlots->mDropRow, mSlots->mDropOrient,
+ dragEvent->mDataTransfer);
+ mSlots->mDropRow = -1;
+ mSlots->mDropOrient = -1;
+ mSlots->mIsDragging = false;
+ *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop
+ } else if (aEvent->mMessage == eDragExit) {
+ // this event was meant for another frame, so ignore it
+ if (!mSlots)
+ return NS_OK;
+
+ // Clear out all our tracking vars.
+
+ if (mSlots->mDropAllowed) {
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
+ }
+ else
+ mSlots->mDropAllowed = false;
+ mSlots->mIsDragging = false;
+ mSlots->mScrollLines = 0;
+ // If a drop is occuring, the exit event will fire just before the drop
+ // event, so don't reset mDropRow or mDropOrient as these fields are used
+ // by the drop event.
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ if (!mSlots->mArray.IsEmpty()) {
+ // Close all spring loaded folders except the drop folder.
+ CreateTimer(LookAndFeel::eIntID_TreeCloseDelay,
+ CloseCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+ }
+
+ return NS_OK;
+}
+
+class nsDisplayTreeBody final : public nsDisplayItem {
+public:
+ nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame),
+ mDisableSubpixelAA(false) {
+ MOZ_COUNT_CTOR(nsDisplayTreeBody);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTreeBody() {
+ MOZ_COUNT_DTOR(nsDisplayTreeBody);
+ }
+#endif
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override
+ {
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override
+ {
+ DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
+ mDisableSubpixelAA);
+
+ DrawResult result = static_cast<nsTreeBodyFrame*>(mFrame)
+ ->PaintTreeBody(*aCtx, mVisibleRect, ToReferenceFrame());
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ }
+
+ NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+ virtual void DisableComponentAlpha() override {
+ mDisableSubpixelAA = true;
+ }
+
+ bool mDisableSubpixelAA;
+};
+
+// Painting routines
+void
+nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // REVIEW: why did we paint if we were collapsed? that makes no sense!
+ if (!IsVisibleForPainting(aBuilder))
+ return; // We're invisible. Don't paint.
+
+ // Handles painting our background, border, and outline.
+ nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ // Bail out now if there's no view or we can't run script because the
+ // document is a zombie
+ if (!mView || !GetContent ()->GetComposedDoc()->GetWindow())
+ return;
+
+#ifdef XP_MACOSX
+ nsIContent* baseElement = GetBaseElement();
+ nsIFrame* treeFrame =
+ baseElement ? baseElement->GetPrimaryFrame() : nullptr;
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+ nsITheme* theme = PresContext()->GetTheme();
+ // On Mac, we support native theming of selected rows. On 10.10 and higher,
+ // this means applying vibrancy which require us to register the theme
+ // geometrics for the row. In order to make the vibrancy effect to work
+ // properly, we also need the tree to be themed as a source list.
+ if (selection && treeFrame && theme &&
+ treeFrame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ // Loop through our onscreen rows. If the row is selected and a
+ // -moz-appearance is provided, RegisterThemeGeometry might be necessary.
+ const auto end = std::min(mRowCount, LastVisibleRow() + 1);
+ for (auto i = FirstVisibleRow(); i < end; i++) {
+ bool isSelected;
+ selection->IsSelected(i, &isSelected);
+ if (isSelected) {
+ PrefillPropertyArray(i, nullptr);
+ nsAutoString properties;
+ mView->GetRowProperties(i, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+ nsStyleContext* rowContext =
+ GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ auto appearance = rowContext->StyleDisplay()->mAppearance;
+ if (appearance) {
+ if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) {
+ nsITheme::ThemeGeometryType type =
+ theme->ThemeGeometryTypeForWidget(this, appearance);
+ if (type != nsITheme::eThemeGeometryTypeUnknown) {
+ nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight *
+ (i - FirstVisibleRow()), mInnerBox.width,
+ mRowHeight);
+ aBuilder->RegisterThemeGeometry(type,
+ LayoutDeviceIntRect::FromUnknownRect(
+ (rowRect + aBuilder->ToReferenceFrame(this)).ToNearestPixels(
+ PresContext()->AppUnitsPerDevPixel())));
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayTreeBody(aBuilder, this));
+}
+
+DrawResult
+nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt)
+{
+ // Update our available height and our page count.
+ CalcInnerBox();
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ gfxContext* gfx = aRenderingContext.ThebesContext();
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(mInnerBox + aPt,
+ PresContext()->AppUnitsPerDevPixel(),
+ *drawTarget));
+ int32_t oldPageCount = mPageLength;
+ if (!mHasFixedRowCount)
+ mPageLength = mInnerBox.height/mRowHeight;
+
+ if (oldPageCount != mPageLength || mHorzWidth != CalcHorzWidth(GetScrollParts())) {
+ // Schedule a ResizeReflow that will update our info properly.
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ }
+#ifdef DEBUG
+ int32_t rowCount = mRowCount;
+ mView->GetRowCount(&rowCount);
+ NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly");
+#endif
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Loop through our columns and paint them (e.g., for sorting). This is only
+ // relevant when painting backgrounds, since columns contain no content. Content
+ // is contained in the rows.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect colRect;
+ nsresult rv = currCol->GetRect(this, mInnerBox.y, mInnerBox.height,
+ &colRect);
+ // Don't paint hidden columns.
+ if (NS_FAILED(rv) || colRect.width == 0) continue;
+
+ if (OffsetForHorzScroll(colRect, false)) {
+ nsRect dirtyRect;
+ colRect += aPt;
+ if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
+ result &=
+ PaintColumn(currCol, colRect, PresContext(), aRenderingContext, aDirtyRect);
+ }
+ }
+ }
+ // Loop through our on-screen rows.
+ for (int32_t i = mTopRowIndex; i < mRowCount && i <= mTopRowIndex+mPageLength; i++) {
+ nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*(i-mTopRowIndex), mInnerBox.width, mRowHeight);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
+ rowRect.y < (mInnerBox.y+mInnerBox.height)) {
+ result &=
+ PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext, aDirtyRect, aPt);
+ }
+ }
+
+ if (mSlots && mSlots->mDropAllowed && (mSlots->mDropOrient == nsITreeView::DROP_BEFORE ||
+ mSlots->mDropOrient == nsITreeView::DROP_AFTER)) {
+ nscoord yPos = mInnerBox.y + mRowHeight * (mSlots->mDropRow - mTopRowIndex) - mRowHeight / 2;
+ nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight);
+ if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
+ feedbackRect.y += mRowHeight;
+
+ nsRect dirtyRect;
+ feedbackRect += aPt;
+ if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) {
+ result &=
+ PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext,
+ aDirtyRect, aPt);
+ }
+ }
+ gfx->Restore();
+
+ return result;
+}
+
+
+
+DrawResult
+nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
+ const nsRect& aColumnRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Now obtain the properties for our cell.
+ PrefillPropertyArray(-1, aColumn);
+ nsAutoString properties;
+ mView->GetColumnProperties(aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the column. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* colContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecolumn);
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect colRect(aColumnRect);
+ nsMargin colMargin;
+ colContext->StyleMargin()->GetMargin(colMargin);
+ colRect.Deflate(colMargin);
+
+ return PaintBackgroundLayer(colContext, aPresContext, aRenderingContext,
+ colRect, aDirtyRect);
+}
+
+DrawResult
+nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
+ const nsRect& aRowRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // We have been given a rect for our row. We treat this row like a full-blown
+ // frame, meaning that it can have borders, margins, padding, and a background.
+
+ // Without a view, we have no data. Check for this up front.
+ if (!mView) {
+ return DrawResult::SUCCESS;
+ }
+
+ nsresult rv;
+
+ // Now obtain the properties for our row.
+ // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused
+ PrefillPropertyArray(aRowIndex, nullptr);
+
+ nsAutoString properties;
+ mView->GetRowProperties(aRowIndex, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the row. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+
+ // Obtain the margins for the row and then deflate our rect by that
+ // amount. The row is assumed to be contained within the deflated rect.
+ nsRect rowRect(aRowRect);
+ nsMargin rowMargin;
+ rowContext->StyleMargin()->GetMargin(rowMargin);
+ rowRect.Deflate(rowMargin);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Paint our borders and background for our row rect.
+ nsITheme* theme = nullptr;
+ auto appearance = rowContext->StyleDisplay()->mAppearance;
+ if (appearance) {
+ theme = aPresContext->GetTheme();
+ }
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ // Save the current font smoothing background color in case we change it.
+ Color originalColor(ctx->GetFontSmoothingBackgroundColor());
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) {
+ nscolor color;
+ if (theme->WidgetProvidesFontSmoothingBackgroundColor(this, appearance,
+ &color)) {
+ // Set the font smoothing background color provided by the widget.
+ ctx->SetFontSmoothingBackgroundColor(ToDeviceColor(color));
+ }
+ nsRect dirty;
+ dirty.IntersectRect(rowRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this, appearance, rowRect,
+ dirty);
+ } else {
+ result &= PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext,
+ rowRect, aDirtyRect);
+ }
+
+ // Adjust the rect for its border and padding.
+ nsRect originalRowRect = rowRect;
+ AdjustForBorderPadding(rowContext, rowRect);
+
+ bool isSeparator = false;
+ mView->IsSeparator(aRowIndex, &isSeparator);
+ if (isSeparator) {
+ // The row is a separator.
+
+ nscoord primaryX = rowRect.x;
+ nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
+ if (primaryCol) {
+ // Paint the primary cell.
+ nsRect cellRect;
+ rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("primary column is invalid");
+ return result;
+ }
+
+ if (OffsetForHorzScroll(cellRect, false)) {
+ cellRect.x += aPt.x;
+ nsRect dirtyRect;
+ nsRect checkRect(cellRect.x, originalRowRect.y,
+ cellRect.width, originalRowRect.height);
+ if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) {
+ result &= PaintCell(aRowIndex, primaryCol, cellRect, aPresContext,
+ aRenderingContext, aDirtyRect, primaryX, aPt);
+ }
+ }
+
+ // Paint the left side of the separator.
+ nscoord currX;
+ nsTreeColumn* previousCol = primaryCol->GetPrevious();
+ if (previousCol) {
+ nsRect prevColRect;
+ rv = previousCol->GetRect(this, 0, 0, &prevColRect);
+ if (NS_SUCCEEDED(rv)) {
+ currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x;
+ } else {
+ NS_NOTREACHED("The column before the primary column is invalid");
+ currX = rowRect.x;
+ }
+ } else {
+ currX = rowRect.x;
+ }
+
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+ if (level == 0)
+ currX += mIndentation;
+
+ if (currX > rowRect.x) {
+ nsRect separatorRect(rowRect);
+ separatorRect.width -= rowRect.x + rowRect.width - currX;
+ result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ }
+ }
+
+ // Paint the right side (whole) separator.
+ nsRect separatorRect(rowRect);
+ if (primaryX > rowRect.x) {
+ separatorRect.width -= primaryX - rowRect.x;
+ separatorRect.x += primaryX - rowRect.x;
+ }
+ result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ }
+ else {
+ // Now loop over our cells. Only paint a cell if it intersects with our dirty rect.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect cellRect;
+ rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
+ // Don't paint cells in hidden columns.
+ if (NS_FAILED(rv) || cellRect.width == 0)
+ continue;
+
+ if (OffsetForHorzScroll(cellRect, false)) {
+ cellRect.x += aPt.x;
+
+ // for primary columns, use the row's vertical size so that the
+ // lines get drawn properly
+ nsRect checkRect = cellRect;
+ if (currCol->IsPrimary())
+ checkRect = nsRect(cellRect.x, originalRowRect.y,
+ cellRect.width, originalRowRect.height);
+
+ nsRect dirtyRect;
+ nscoord dummy;
+ if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
+ result &= PaintCell(aRowIndex, currCol, cellRect, aPresContext,
+ aRenderingContext, aDirtyRect, dummy, aPt);
+ }
+ }
+ }
+ // If we've changed the font smoothing background color for this row, restore
+ // the color to the original one.
+ if (originalColor != ctx->GetFontSmoothingBackgroundColor()) {
+ ctx->SetFontSmoothingBackgroundColor(originalColor);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
+ const nsRect& aSeparatorRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ // Resolve style for the separator.
+ nsStyleContext* separatorContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeseparator);
+ bool useTheme = false;
+ nsITheme *theme = nullptr;
+ const nsStyleDisplay* displayData = separatorContext->StyleDisplay();
+ if ( displayData->mAppearance ) {
+ theme = aPresContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance))
+ useTheme = true;
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // use -moz-appearance if provided.
+ if (useTheme) {
+ nsRect dirty;
+ dirty.IntersectRect(aSeparatorRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this,
+ displayData->mAppearance, aSeparatorRect, dirty);
+ }
+ else {
+ const nsStylePosition* stylePosition = separatorContext->StylePosition();
+
+ // Obtain the height for the separator or use the default value.
+ nscoord height;
+ if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = stylePosition->mHeight.GetCoordValue();
+ else {
+ // Use default height 2px.
+ height = nsPresContext::CSSPixelsToAppUnits(2);
+ }
+
+ // Obtain the margins for the separator and then deflate our rect by that
+ // amount. The separator is assumed to be contained within the deflated rect.
+ nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width, height);
+ nsMargin separatorMargin;
+ separatorContext->StyleMargin()->GetMargin(separatorMargin);
+ separatorRect.Deflate(separatorMargin);
+
+ // Center the separator.
+ separatorRect.y += (aSeparatorRect.height - height) / 2;
+
+ result &= PaintBackgroundLayer(separatorContext, aPresContext,
+ aRenderingContext, separatorRect,
+ aDirtyRect);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintCell(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCellRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX,
+ nsPoint aPt)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Now obtain the properties for our cell.
+ // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused, and the col ID.
+ PrefillPropertyArray(aRowIndex, aColumn);
+ nsAutoString properties;
+ mView->GetCellProperties(aRowIndex, aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the cell. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect cellRect(aCellRect);
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ cellRect.Deflate(cellMargin);
+
+ // Paint our borders and background for our row rect.
+ DrawResult result = PaintBackgroundLayer(cellContext, aPresContext,
+ aRenderingContext, cellRect,
+ aDirtyRect);
+
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ nscoord currX = cellRect.x;
+ nscoord remainingWidth = cellRect.width;
+
+ // Now we paint the contents of the cells.
+ // Directionality of the tree determines the order in which we paint.
+ // NS_STYLE_DIRECTION_LTR means paint from left to right.
+ // NS_STYLE_DIRECTION_RTL means paint from right to left.
+
+ if (aColumn->IsPrimary()) {
+ // If we're the primary column, we need to indent and paint the twisty and any connecting lines
+ // between siblings.
+
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+
+ if (!isRTL)
+ currX += mIndentation * level;
+ remainingWidth -= mIndentation * level;
+
+ // Resolve the style to use for the connecting lines.
+ nsStyleContext* lineContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeline);
+
+ if (mIndentation && level &&
+ lineContext->StyleVisibility()->IsVisibleOrCollapsed()) {
+ // Paint the thread lines.
+
+ // Get the size of the twisty. We don't want to paint the twisty
+ // before painting of connecting lines since it would paint lines over
+ // the twisty. But we need to leave a place for it.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ nsRect twistyRect(aCellRect);
+ GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
+ twistyContext);
+
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ aRenderingContext.ThebesContext()->Save();
+
+ const nsStyleBorder* borderStyle = lineContext->StyleBorder();
+ // Resolve currentcolor values against the treeline context
+ nscolor color = lineContext->StyleColor()->
+ CalcComplexColor(borderStyle->mBorderLeftColor);
+ ColorPattern colorPatt(ToDeviceColor(color));
+
+ uint8_t style = borderStyle->GetBorderStyle(NS_SIDE_LEFT);
+ StrokeOptions strokeOptions;
+ nsLayoutUtils::InitDashPattern(strokeOptions, style);
+
+ nscoord srcX = currX + twistyRect.width - mIndentation / 2;
+ nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y;
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ nsPresContext* pc = PresContext();
+
+ // Don't paint off our cell.
+ if (srcX <= cellRect.x + cellRect.width) {
+ nscoord destX = currX + twistyRect.width;
+ if (destX > cellRect.x + cellRect.width)
+ destX = cellRect.x + cellRect.width;
+ if (isRTL) {
+ srcX = currX + remainingWidth - (srcX - cellRect.x);
+ destX = currX + remainingWidth - (destX - cellRect.x);
+ }
+ Point p1(pc->AppUnitsToGfxUnits(srcX),
+ pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
+ Point p2(pc->AppUnitsToGfxUnits(destX),
+ pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
+ SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
+ strokeOptions.mLineWidth);
+ drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
+ }
+
+ int32_t currentParent = aRowIndex;
+ for (int32_t i = level; i > 0; i--) {
+ if (srcX <= cellRect.x + cellRect.width) {
+ // Paint full vertical line only if we have next sibling.
+ bool hasNextSibling;
+ mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
+ if (hasNextSibling || i == level) {
+ Point p1(pc->AppUnitsToGfxUnits(srcX),
+ pc->AppUnitsToGfxUnits(lineY));
+ Point p2;
+ p2.x = pc->AppUnitsToGfxUnits(srcX);
+
+ if (hasNextSibling)
+ p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight);
+ else if (i == level)
+ p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2);
+
+ SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
+ strokeOptions.mLineWidth);
+ drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
+ }
+ }
+
+ int32_t parent;
+ if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0)
+ break;
+ currentParent = parent;
+ srcX -= mIndentation;
+ }
+
+ aRenderingContext.ThebesContext()->Restore();
+ }
+
+ // Always leave space for the twisty.
+ nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ result &= PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext,
+ aRenderingContext, aDirtyRect, remainingWidth,
+ currX);
+ }
+
+ // Now paint the icon for our cell.
+ nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) {
+ result &= PaintImage(aRowIndex, aColumn, iconRect, aPresContext,
+ aRenderingContext, aDirtyRect, remainingWidth,
+ currX);
+ }
+
+ // Now paint our element, but only if we aren't a cycler column.
+ // XXX until we have the ability to load images, allow the view to
+ // insert text into cycler columns...
+ if (!aColumn->IsCycler()) {
+ nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) {
+ switch (aColumn->GetType()) {
+ case nsITreeColumn::TYPE_TEXT:
+ case nsITreeColumn::TYPE_PASSWORD:
+ result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect, currX);
+ break;
+ case nsITreeColumn::TYPE_CHECKBOX:
+ result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ break;
+ case nsITreeColumn::TYPE_PROGRESSMETER:
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aColumn, &state);
+ switch (state) {
+ case nsITreeView::PROGRESS_NORMAL:
+ case nsITreeView::PROGRESS_UNDETERMINED:
+ result &= PaintProgressMeter(aRowIndex, aColumn, elementRect,
+ aPresContext, aRenderingContext,
+ aDirtyRect);
+ break;
+ case nsITreeView::PROGRESS_NONE:
+ default:
+ result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect, currX);
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ aCurrX = currX;
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintTwisty(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Paint the twisty, but only if we are a non-empty container.
+ bool shouldPaint = false;
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty)
+ shouldPaint = true;
+ }
+
+ // Resolve style for the twisty.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ // Obtain the margins for the twisty and then deflate our rect by that
+ // amount. The twisty is assumed to be contained within the deflated rect.
+ nsRect twistyRect(aTwistyRect);
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Deflate(twistyMargin);
+
+ nsRect imageSize;
+ nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect,
+ aPresContext, twistyContext);
+
+ // Subtract out the remaining width. This is done even when we don't actually paint a twisty in
+ // this cell, so that cells in different rows still line up.
+ nsRect copyRect(twistyRect);
+ copyRect.Inflate(twistyMargin);
+ aRemainingWidth -= copyRect.width;
+ if (!isRTL)
+ aCurrX += copyRect.width;
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (shouldPaint) {
+ // Paint our borders and background for our image rect.
+ result &= PaintBackgroundLayer(twistyContext, aPresContext,
+ aRenderingContext, twistyRect,
+ aDirtyRect);
+
+ if (theme) {
+ if (isRTL)
+ twistyRect.x = rightEdge - twistyRect.width;
+ // yeah, I know it says we're drawing a background, but a twisty is really a fg
+ // object since it doesn't have anything that gecko would want to draw over it. Besides,
+ // we have to prevent imagelib from drawing it.
+ nsRect dirty;
+ dirty.IntersectRect(twistyRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this,
+ twistyContext->StyleDisplay()->mAppearance, twistyRect, dirty);
+ }
+ else {
+ // Time to paint the twisty.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(twistyContext, bp);
+ twistyRect.Deflate(bp);
+ if (isRTL)
+ twistyRect.x = rightEdge - twistyRect.width;
+ imageSize.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ bool useImageRegion = true;
+ GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ nsPoint pt = twistyRect.TopLeft();
+
+ // Center the image. XXX Obey vertical-align style prop?
+ if (imageSize.height < twistyRect.height) {
+ pt.y += (twistyRect.height - imageSize.height)/2;
+ }
+
+ // Paint the image.
+ result &=
+ nsLayoutUtils::DrawSingleUnscaledImage(
+ *aRenderingContext.ThebesContext(), aPresContext, image,
+ SamplingFilter::POINT, pt, &aDirtyRect,
+ imgIContainer::FLAG_NONE, &imageSize);
+ }
+ }
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintImage(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aImageRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Resolve style for the image.
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ // Obtain opacity value for the image.
+ float opacity = imageContext->StyleEffects()->mOpacity;
+
+ // Obtain the margins for the image and then deflate our rect by that
+ // amount. The image is assumed to be contained within the deflated rect.
+ nsRect imageRect(aImageRect);
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageRect.Deflate(imageMargin);
+
+ // Get the image.
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion, getter_AddRefs(image));
+
+ // Get the image destination size.
+ nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image);
+ if (!imageDestSize.width || !imageDestSize.height) {
+ return DrawResult::SUCCESS;
+ }
+
+ // Get the borders and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(imageContext, bp);
+
+ // destRect will be passed as the aDestRect argument in the DrawImage method.
+ // Start with the imageDestSize width and height.
+ nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height);
+ // Inflate destRect for borders and padding so that we can compare/adjust
+ // with respect to imageRect.
+ destRect.Inflate(bp);
+
+ // The destRect width and height have not been adjusted to fit within the
+ // cell width and height.
+ // We must adjust the width even if image is null, because the width is used
+ // to update the aRemainingWidth and aCurrX values.
+ // Since the height isn't used unless the image is not null, we will adjust
+ // the height inside the if (image) block below.
+
+ if (destRect.width > imageRect.width) {
+ // The destRect is too wide to fit within the cell width.
+ // Adjust destRect width to fit within the cell width.
+ destRect.width = imageRect.width;
+ }
+ else {
+ // The cell is wider than the destRect.
+ // In a cycler column, the image is centered horizontally.
+ if (!aColumn->IsCycler()) {
+ // If this column is not a cycler, we won't center the image horizontally.
+ // We adjust the imageRect width so that the image is placed at the start
+ // of the cell.
+ imageRect.width = destRect.width;
+ }
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (image) {
+ if (isRTL)
+ imageRect.x = rightEdge - imageRect.width;
+ // Paint our borders and background for our image rect
+ result &= PaintBackgroundLayer(imageContext, aPresContext,
+ aRenderingContext, imageRect,
+ aDirtyRect);
+
+ // The destRect x and y have not been set yet. Let's do that now.
+ // Initially, we use the imageRect x and y.
+ destRect.x = imageRect.x;
+ destRect.y = imageRect.y;
+
+ if (destRect.width < imageRect.width) {
+ // The destRect width is smaller than the cell width.
+ // Center the image horizontally in the cell.
+ // Adjust the destRect x accordingly.
+ destRect.x += (imageRect.width - destRect.width)/2;
+ }
+
+ // Now it's time to adjust the destRect height to fit within the cell height.
+ if (destRect.height > imageRect.height) {
+ // The destRect height is larger than the cell height.
+ // Adjust destRect height to fit within the cell height.
+ destRect.height = imageRect.height;
+ }
+ else if (destRect.height < imageRect.height) {
+ // The destRect height is smaller than the cell height.
+ // Center the image vertically in the cell.
+ // Adjust the destRect y accordingly.
+ destRect.y += (imageRect.height - destRect.height)/2;
+ }
+
+ // It's almost time to paint the image.
+ // Deflate destRect for the border and padding.
+ destRect.Deflate(bp);
+
+ // Compute the area where our whole image would be mapped, to get the
+ // desired subregion onto our actual destRect:
+ nsRect wholeImageDest;
+ CSSIntSize rawImageCSSIntSize;
+ if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) &&
+ NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) {
+ // Get the image source rectangle - the rectangle containing the part of
+ // the image that we are going to display. sourceRect will be passed as
+ // the aSrcRect argument in the DrawImage method.
+ nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image);
+
+ // Let's say that the image is 100 pixels tall and that the CSS has
+ // specified that the destination height should be 50 pixels tall. Let's
+ // say that the cell height is only 20 pixels. So, in those 20 visible
+ // pixels, we want to see the top 20/50ths of the image. So, the
+ // sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
+ // Essentially, we are scaling the image as dictated by the CSS
+ // destination height and width, and we are then clipping the scaled
+ // image by the cell width and height.
+ nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize));
+ wholeImageDest =
+ nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect,
+ nsRect(destRect.TopLeft(),
+ imageDestSize));
+ } else {
+ // GetWidth/GetHeight failed, so we can't easily map a subregion of the
+ // source image onto the destination area.
+ // * If this happens with a RasterImage, it probably means the image is
+ // in an error state, and we shouldn't draw anything. Hence, we leave
+ // wholeImageDest as an empty rect (its initial state).
+ // * If this happens with a VectorImage, it probably means the image has
+ // no explicit width or height attribute -- but we can still proceed and
+ // just treat the destination area as our whole SVG image area. Hence, we
+ // set wholeImageDest to the full destRect.
+ if (image->GetType() == imgIContainer::TYPE_VECTOR) {
+ wholeImageDest = destRect;
+ }
+ }
+
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ if (opacity != 1.0f) {
+ ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ result &=
+ nsLayoutUtils::DrawImage(*ctx, aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect,
+ imgIContainer::FLAG_NONE);
+
+ if (opacity != 1.0f) {
+ ctx->PopGroupAndBlend();
+ }
+ }
+
+ // Update the aRemainingWidth and aCurrX values.
+ imageRect.Inflate(imageMargin);
+ aRemainingWidth -= imageRect.width;
+ if (!isRTL) {
+ aCurrX += imageRect.width;
+ }
+
+ return result;
+}
+
+// Disable PGO for PaintText because MSVC 2015 seems to have decided
+// that it can null out the alreadyAddRefed<nsFontMetrics> used to
+// initialize fontMet after storing fontMet on the stack in the same
+// space, overwriting fontMet's stack storage with null.
+#ifdef _MSC_VER
+# pragma optimize("g", off)
+#endif
+DrawResult
+nsTreeBodyFrame::PaintText(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTextRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ // Now obtain the text for our cell.
+ nsAutoString text;
+ mView->GetCellText(aRowIndex, aColumn, text);
+
+ if (aColumn->Type() == nsITreeColumn::TYPE_PASSWORD) {
+ TextEditRules::FillBufWithPWChars(&text, text.Length());
+ }
+
+ // We're going to paint this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(text);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (text.Length() == 0) {
+ // Don't paint an empty string. XXX What about background/borders? Still paint?
+ return result;
+ }
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Resolve style for the text. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ // Obtain opacity value for the image.
+ float opacity = textContext->StyleEffects()->mOpacity;
+
+ // Obtain the margins for the text and then deflate our rect by that
+ // amount. The text is assumed to be contained within the deflated rect.
+ nsRect textRect(aTextRect);
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(textContext, bp);
+ textRect.Deflate(bp);
+
+ // Compute our text size.
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+
+ nscoord height = fontMet->MaxHeight();
+ nscoord baseline = fontMet->MaxAscent();
+
+ // Center the text. XXX Obey vertical-align style prop?
+ if (height < textRect.height) {
+ textRect.y += (textRect.height - height)/2;
+ textRect.height = height;
+ }
+
+ // Set our font.
+ AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, *fontMet, textRect);
+ textRect.Inflate(bp);
+
+ // Subtract out the remaining width.
+ if (!isRTL)
+ aCurrX += textRect.width + textMargin.LeftRight();
+
+ result &= PaintBackgroundLayer(textContext, aPresContext, aRenderingContext,
+ textRect, aDirtyRect);
+
+ // Time to paint our text.
+ textRect.Deflate(bp);
+
+ // Set our color.
+ ColorPattern color(ToDeviceColor(textContext->StyleColor()->mColor));
+
+ // Draw decorations.
+ uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine;
+
+ nscoord offset;
+ nscoord size;
+ if (decorations & (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) {
+ fontMet->GetUnderline(offset, size);
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
+ nsRect r(textRect.x, textRect.y, textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
+ nsRect r(textRect.x, textRect.y + baseline - offset,
+ textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ }
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
+ fontMet->GetStrikeout(offset, size);
+ nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ if (opacity != 1.0f) {
+ ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ ctx->SetColor(Color::FromABGR(textContext->StyleColor()->mColor));
+ nsLayoutUtils::DrawString(this, *fontMet, &aRenderingContext, text.get(),
+ text.Length(),
+ textRect.TopLeft() + nsPoint(0, baseline),
+ cellContext);
+
+ if (opacity != 1.0f) {
+ ctx->PopGroupAndBlend();
+ }
+
+ return result;
+}
+#ifdef _MSC_VER
+# pragma optimize("", on)
+#endif
+
+DrawResult
+nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCheckboxRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Resolve style for the checkbox.
+ nsStyleContext* checkboxContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecheckbox);
+
+ nscoord rightEdge = aCheckboxRect.XMost();
+
+ // Obtain the margins for the checkbox and then deflate our rect by that
+ // amount. The checkbox is assumed to be contained within the deflated rect.
+ nsRect checkboxRect(aCheckboxRect);
+ nsMargin checkboxMargin;
+ checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
+ checkboxRect.Deflate(checkboxMargin);
+
+ nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext);
+
+ if (imageSize.height > checkboxRect.height)
+ imageSize.height = checkboxRect.height;
+ if (imageSize.width > checkboxRect.width)
+ imageSize.width = checkboxRect.width;
+
+ if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
+ checkboxRect.x = rightEdge - checkboxRect.width;
+
+ // Paint our borders and background for our image rect.
+ DrawResult result = PaintBackgroundLayer(checkboxContext, aPresContext,
+ aRenderingContext, checkboxRect,
+ aDirtyRect);
+
+ // Time to paint the checkbox.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(checkboxContext, bp);
+ checkboxRect.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ bool useImageRegion = true;
+ GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ nsPoint pt = checkboxRect.TopLeft();
+
+ if (imageSize.height < checkboxRect.height) {
+ pt.y += (checkboxRect.height - imageSize.height)/2;
+ }
+
+ if (imageSize.width < checkboxRect.width) {
+ pt.x += (checkboxRect.width - imageSize.width)/2;
+ }
+
+ // Paint the image.
+ result &=
+ nsLayoutUtils::DrawSingleUnscaledImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ image, SamplingFilter::POINT, pt, &aDirtyRect,
+ imgIContainer::FLAG_NONE, &imageSize);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintProgressMeter(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aProgressMeterRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Resolve style for the progress meter. It contains all the info we need
+ // to lay ourselves out and to paint.
+ nsStyleContext* meterContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeprogressmeter);
+
+ // Obtain the margins for the progress meter and then deflate our rect by that
+ // amount. The progress meter is assumed to be contained within the deflated
+ // rect.
+ nsRect meterRect(aProgressMeterRect);
+ nsMargin meterMargin;
+ meterContext->StyleMargin()->GetMargin(meterMargin);
+ meterRect.Deflate(meterMargin);
+
+ // Paint our borders and background for our progress meter rect.
+ DrawResult result = PaintBackgroundLayer(meterContext, aPresContext,
+ aRenderingContext, meterRect,
+ aDirtyRect);
+
+ // Time to paint our progress.
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aColumn, &state);
+ if (state == nsITreeView::PROGRESS_NORMAL) {
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(meterContext, meterRect);
+
+ // Now obtain the value for our cell.
+ nsAutoString value;
+ mView->GetCellValue(aRowIndex, aColumn, value);
+
+ nsresult rv;
+ int32_t intValue = value.ToInteger(&rv);
+ if (intValue < 0)
+ intValue = 0;
+ else if (intValue > 100)
+ intValue = 100;
+
+ nscoord meterWidth = NSToCoordRound((float)intValue / 100 * meterRect.width);
+ if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
+ meterRect.x += meterRect.width - meterWidth; // right align
+ meterRect.width = meterWidth;
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ int32_t width, height;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
+ height*nsDeviceContext::AppUnitsPerCSSPixel());
+ result &=
+ nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
+ aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+ aDirtyRect, imgIContainer::FLAG_NONE);
+ } else {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ Rect rect =
+ NSRectToSnappedRect(meterRect, appUnitsPerDevPixel, *drawTarget);
+ ColorPattern color(ToDeviceColor(meterContext->StyleColor()->mColor));
+ drawTarget->FillRect(rect, color);
+ }
+ }
+ else if (state == nsITreeView::PROGRESS_UNDETERMINED) {
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(meterContext, meterRect);
+
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ int32_t width, height;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
+ height*nsDeviceContext::AppUnitsPerCSSPixel());
+ result &=
+ nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
+ aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+ aDirtyRect, imgIContainer::FLAG_NONE);
+ }
+ }
+
+ return result;
+}
+
+
+DrawResult
+nsTreeBodyFrame::PaintDropFeedback(const nsRect& aDropFeedbackRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // Paint the drop feedback in between rows.
+
+ nscoord currX;
+
+ // Adjust for the primary cell.
+ nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
+
+ if (primaryCol) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ primaryCol->GetXInTwips(this, &currX);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?");
+
+ currX += aPt.x - mHorzPosition;
+ } else {
+ currX = aDropFeedbackRect.x;
+ }
+
+ PrefillPropertyArray(mSlots->mDropRow, primaryCol);
+
+ // Resolve the style to use for the drop feedback.
+ nsStyleContext* feedbackContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreedropfeedback);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Paint only if it is visible.
+ if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
+ int32_t level;
+ mView->GetLevel(mSlots->mDropRow, &level);
+
+ // If our previous or next row has greater level use that for
+ // correct visual indentation.
+ if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
+ if (mSlots->mDropRow > 0) {
+ int32_t previousLevel;
+ mView->GetLevel(mSlots->mDropRow - 1, &previousLevel);
+ if (previousLevel > level)
+ level = previousLevel;
+ }
+ }
+ else {
+ if (mSlots->mDropRow < mRowCount - 1) {
+ int32_t nextLevel;
+ mView->GetLevel(mSlots->mDropRow + 1, &nextLevel);
+ if (nextLevel > level)
+ level = nextLevel;
+ }
+ }
+
+ currX += mIndentation * level;
+
+ if (primaryCol){
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+ nsRect imageSize;
+ nsRect twistyRect;
+ GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect,
+ aPresContext, twistyContext);
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+ currX += twistyRect.width;
+ }
+
+ const nsStylePosition* stylePosition = feedbackContext->StylePosition();
+
+ // Obtain the width for the drop feedback or use default value.
+ nscoord width;
+ if (stylePosition->mWidth.GetUnit() == eStyleUnit_Coord)
+ width = stylePosition->mWidth.GetCoordValue();
+ else {
+ // Use default width 50px.
+ width = nsPresContext::CSSPixelsToAppUnits(50);
+ }
+
+ // Obtain the height for the drop feedback or use default value.
+ nscoord height;
+ if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = stylePosition->mHeight.GetCoordValue();
+ else {
+ // Use default height 2px.
+ height = nsPresContext::CSSPixelsToAppUnits(2);
+ }
+
+ // Obtain the margins for the drop feedback and then deflate our rect
+ // by that amount.
+ nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
+ nsMargin margin;
+ feedbackContext->StyleMargin()->GetMargin(margin);
+ feedbackRect.Deflate(margin);
+
+ feedbackRect.y += (aDropFeedbackRect.height - height) / 2;
+
+ // Finally paint the drop feedback.
+ result &= PaintBackgroundLayer(feedbackContext, aPresContext,
+ aRenderingContext, feedbackRect,
+ aDirtyRect);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ const nsStyleBorder* myBorder = aStyleContext->StyleBorder();
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*aPresContext, aRenderingContext,
+ aDirtyRect, aRect, this,
+ nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
+ DrawResult result =
+ nsCSSRendering::PaintBackgroundWithSC(params, aStyleContext, *myBorder);
+
+ result &=
+ nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext,
+ this, aDirtyRect, aRect,
+ *myBorder, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES);
+
+ nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
+ aDirtyRect, aRect, aStyleContext);
+
+ return result;
+}
+
+// Scrolling
+nsresult
+nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = EnsureRowIsVisibleInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ if (!mView || !mPageLength)
+ return NS_OK;
+
+ if (mTopRowIndex <= aRow && mTopRowIndex+mPageLength > aRow)
+ return NS_OK;
+
+ if (aRow < mTopRowIndex)
+ ScrollToRowInternal(aParts, aRow);
+ else {
+ // Bring it just on-screen.
+ int32_t distance = aRow - (mTopRowIndex+mPageLength)+1;
+ ScrollToRowInternal(aParts, mTopRowIndex+distance);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol)
+{
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ ScrollParts parts = GetScrollParts();
+
+ nscoord result = -1;
+ nsresult rv;
+
+ nscoord columnPos;
+ rv = col->GetXInTwips(this, &columnPos);
+ if(NS_FAILED(rv)) return rv;
+
+ nscoord columnWidth;
+ rv = col->GetWidthInTwips(this, &columnWidth);
+ if(NS_FAILED(rv)) return rv;
+
+ // If the start of the column is before the
+ // start of the horizontal view, then scroll
+ if (columnPos < mHorzPosition)
+ result = columnPos;
+ // If the end of the column is past the end of
+ // the horizontal view, then scroll
+ else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width))
+ result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) + mHorzPosition;
+
+ if (result != -1) {
+ rv = ScrollHorzInternal(parts, result);
+ if(NS_FAILED(rv)) return rv;
+ }
+
+ rv = EnsureRowIsVisibleInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = ScrollToRowInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ScrollToColumnInternal(parts, aCol);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToColumn(nsITreeColumn* aCol)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = ScrollToColumnInternal(parts, aCol);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult nsTreeBodyFrame::ScrollToColumnInternal(const ScrollParts& aParts,
+ nsITreeColumn* aCol)
+{
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nscoord x;
+ nsresult rv = col->GetXInTwips(this, &x);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return ScrollHorzInternal(aParts, x);
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToHorizontalPosition(int32_t aHorizontalPosition)
+{
+ ScrollParts parts = GetScrollParts();
+ int32_t position = nsPresContext::CSSPixelsToAppUnits(aHorizontalPosition);
+ nsresult rv = ScrollHorzInternal(parts, position);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToRow(int32_t aRow)
+{
+ ScrollParts parts = GetScrollParts();
+ ScrollToRowInternal(parts, aRow);
+ UpdateScrollbars(parts);
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ ScrollInternal(aParts, aRow);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollByLines(int32_t aNumLines)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+ int32_t newIndex = mTopRowIndex + aNumLines;
+ ScrollToRow(newIndex);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollByPages(int32_t aNumPages)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+ int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
+ ScrollToRow(newIndex);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+
+ // Note that we may be "over scrolled" at this point; that is the
+ // current mTopRowIndex may be larger than mRowCount - mPageLength.
+ // This can happen when items are removed for example. (bug 1085050)
+
+ int32_t maxTopRowIndex = std::max(0, mRowCount - mPageLength);
+ aRow = mozilla::clamped(aRow, 0, maxTopRowIndex);
+ if (aRow == mTopRowIndex) {
+ return NS_OK;
+ }
+ mTopRowIndex = aRow;
+ Invalidate();
+ PostScrollEvent();
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition)
+{
+ if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar)
+ return NS_OK;
+
+ if (aPosition == mHorzPosition)
+ return NS_OK;
+
+ if (aPosition < 0 || aPosition > mHorzWidth)
+ return NS_OK;
+
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ if (aPosition > (mHorzWidth - bounds.width))
+ aPosition = mHorzWidth - bounds.width;
+
+ mHorzPosition = aPosition;
+
+ Invalidate();
+
+ // Update the column scroll view
+ nsWeakFrame weakFrame(this);
+ aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
+ nsIScrollableFrame::INSTANT);
+ if (!weakFrame.IsAlive()) {
+ return NS_ERROR_FAILURE;
+ }
+ // And fire off an event about it all
+ PostScrollEvent();
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ ScrollByPages(aDirection);
+}
+
+void
+nsTreeBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex;
+ ScrollToRow(newIndex);
+}
+
+void
+nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ ScrollByLines(aDirection);
+}
+
+void
+nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
+{
+ ScrollParts parts = GetScrollParts();
+ int32_t increment = aScrollbar->GetIncrement();
+ int32_t direction = 0;
+ if (increment < 0) {
+ direction = -1;
+ } else if (increment > 0) {
+ direction = 1;
+ }
+ bool isHorizontal = aScrollbar->IsXULHorizontal();
+
+ nsWeakFrame weakFrame(this);
+ if (isHorizontal) {
+ int32_t curpos = aScrollbar->MoveToNewPosition();
+ if (weakFrame.IsAlive()) {
+ ScrollHorzInternal(parts, curpos);
+ }
+ } else {
+ ScrollToRowInternal(parts, mTopRowIndex+direction);
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+ if (weakFrame.IsAlive()) {
+ UpdateScrollbars(parts);
+ }
+}
+
+void
+nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos)
+{
+ ScrollParts parts = GetScrollParts();
+
+ if (aOldPos == aNewPos)
+ return;
+
+ nsWeakFrame weakFrame(this);
+
+ // Vertical Scrollbar
+ if (parts.mVScrollbar == aScrollbar) {
+ nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+ nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+ nscoord newrow = newIndex/rh;
+ ScrollInternal(parts, newrow);
+ // Horizontal Scrollbar
+ } else if (parts.mHScrollbar == aScrollbar) {
+ int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+ ScrollHorzInternal(parts, newIndex);
+ }
+ if (weakFrame.IsAlive()) {
+ UpdateScrollbars(parts);
+ }
+}
+
+// The style cache.
+nsStyleContext*
+nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement)
+{
+ return mStyleCache.GetStyleContext(this, PresContext(), mContent,
+ mStyleContext, aPseudoElement,
+ mScratchArray);
+}
+
+// Our comparator for resolving our complex pseudos
+bool
+nsTreeBodyFrame::PseudoMatches(nsCSSSelector* aSelector)
+{
+ // Iterate the class list. For each item in the list, see if
+ // it is contained in our scratch array. If we have a miss, then
+ // we aren't a match. If all items in the class list are
+ // present in the scratch array, then we have a match.
+ nsAtomList* curr = aSelector->mClassList;
+ while (curr) {
+ if (!mScratchArray.Contains(curr->mAtom))
+ return false;
+ curr = curr->mNext;
+ }
+ return true;
+}
+
+nsIContent*
+nsTreeBodyFrame::GetBaseElement()
+{
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ nsIContent* content = parent->GetContent();
+ if (content) {
+ dom::NodeInfo* ni = content->NodeInfo();
+
+ if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) ||
+ (ni->Equals(nsGkAtoms::select) &&
+ content->IsHTMLElement()))
+ return content;
+ }
+
+ parent = parent->GetParent();
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsTreeBodyFrame::ClearStyleAndImageCaches()
+{
+ mStyleCache.Clear();
+ CancelImageRequests();
+ mImageCache.Clear();
+ return NS_OK;
+}
+
+/* virtual */ void
+nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
+
+ // Clear the style cache; the pointers are no longer even valid
+ mStyleCache.Clear();
+ // XXX The following is hacky, but it's not incorrect,
+ // and appears to fix a few bugs with style changes, like text zoom and
+ // dpi changes
+ mIndentation = GetIndentation();
+ mRowHeight = GetRowHeight();
+ mStringWidth = -1;
+}
+
+bool
+nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip)
+{
+ rect.x -= mHorzPosition;
+
+ // Scrolled out before
+ if (rect.XMost() <= mInnerBox.x)
+ return false;
+
+ // Scrolled out after
+ if (rect.x > mInnerBox.XMost())
+ return false;
+
+ if (clip) {
+ nscoord leftEdge = std::max(rect.x, mInnerBox.x);
+ nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost());
+ rect.x = leftEdge;
+ rect.width = rightEdge - leftEdge;
+
+ // Should have returned false above
+ NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync");
+ }
+
+ return true;
+}
+
+bool
+nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex)
+{
+ // Check first for partially visible last row.
+ if (aRowIndex == mRowCount - 1) {
+ nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight;
+ if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height)
+ return true;
+ }
+
+ if (aRowIndex > 0 && aRowIndex < mRowCount - 1)
+ return true;
+
+ return false;
+}
+
+// Given a dom event, figure out which row in the tree the mouse is over,
+// if we should drop before/after/on that row or we should auto-scroll.
+// Doesn't query the content about if the drag is allowable, that's done elsewhere.
+//
+// For containers, we break up the vertical space of the row as follows: if in
+// the topmost 25%, the drop is _before_ the row the mouse is over; if in the
+// last 25%, _after_; in the middle 50%, we consider it a drop _on_ the container.
+//
+// For non-containers, if the mouse is in the top 50% of the row, the drop is
+// _before_ and the bottom 50% _after_
+void
+nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent,
+ int32_t* aRow,
+ int16_t* aOrient,
+ int16_t* aScrollLines)
+{
+ *aOrient = -1;
+ *aScrollLines = 0;
+
+ // Convert the event's point to our coordinates. We want it in
+ // the coordinates of our inner box's coordinates.
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+
+ *aRow = GetRowAt(xTwips, yTwips);
+ if (*aRow >=0) {
+ // Compute the top/bottom of the row in question.
+ int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
+
+ bool isContainer = false;
+ mView->IsContainer (*aRow, &isContainer);
+ if (isContainer) {
+ // for a container, use a 25%/50%/25% breakdown
+ if (yOffset < mRowHeight / 4)
+ *aOrient = nsITreeView::DROP_BEFORE;
+ else if (yOffset > mRowHeight - (mRowHeight / 4))
+ *aOrient = nsITreeView::DROP_AFTER;
+ else
+ *aOrient = nsITreeView::DROP_ON;
+ }
+ else {
+ // for a non-container use a 50%/50% breakdown
+ if (yOffset < mRowHeight / 2)
+ *aOrient = nsITreeView::DROP_BEFORE;
+ else
+ *aOrient = nsITreeView::DROP_AFTER;
+ }
+ }
+
+ if (CanAutoScroll(*aRow)) {
+ // Get the max value from the look and feel service.
+ int32_t scrollLinesMax =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_TreeScrollLinesMax, 0);
+ scrollLinesMax--;
+ if (scrollLinesMax < 0)
+ scrollLinesMax = 0;
+
+ // Determine if we're w/in a margin of the top/bottom of the tree during a drag.
+ // This will ultimately cause us to scroll, but that's done elsewhere.
+ nscoord height = (3 * mRowHeight) / 4;
+ if (yTwips < height) {
+ // scroll up
+ *aScrollLines = NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
+ }
+ else if (yTwips > mRect.height - height) {
+ // scroll down
+ *aScrollLines = NSToIntRound(scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
+ }
+ }
+} // ComputeDropPosition
+
+void
+nsTreeBodyFrame::OpenCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ if (self->mSlots->mDropRow >= 0) {
+ self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
+ self->mView->ToggleOpenState(self->mSlots->mDropRow);
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::CloseCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ for (uint32_t i = self->mSlots->mArray.Length(); i--; ) {
+ if (self->mView)
+ self->mView->ToggleOpenState(self->mSlots->mArray[i]);
+ }
+ self->mSlots->mArray.Clear();
+ }
+}
+
+void
+nsTreeBodyFrame::LazyScrollCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ if (self->mView) {
+ // Set a new timer to scroll the tree repeatedly.
+ self->CreateTimer(LookAndFeel::eIntID_TreeScrollDelay,
+ ScrollCallback, nsITimer::TYPE_REPEATING_SLACK,
+ getter_AddRefs(self->mSlots->mTimer));
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ // ScrollByLines may have deleted |self|.
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ // Don't scroll if we are already at the top or bottom of the view.
+ if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ }
+ else {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsTreeBodyFrame::ScrollEvent::Run()
+{
+ if (mInner) {
+ mInner->FireScrollEvent();
+ }
+ return NS_OK;
+}
+
+
+void
+nsTreeBodyFrame::FireScrollEvent()
+{
+ mScrollEvent.Forget();
+ WidgetGUIEvent event(true, eScroll, nullptr);
+ // scroll events fired at elements don't bubble
+ event.mFlags.mBubbles = false;
+ EventDispatcher::Dispatch(GetContent(), PresContext(), &event);
+}
+
+void
+nsTreeBodyFrame::PostScrollEvent()
+{
+ if (mScrollEvent.IsPending())
+ return;
+
+ RefPtr<ScrollEvent> ev = new ScrollEvent(this);
+ if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
+ NS_WARNING("failed to dispatch ScrollEvent");
+ } else {
+ mScrollEvent = ev;
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollbarActivityStarted() const
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStarted();
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollbarActivityStopped() const
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStopped();
+ }
+}
+
+void
+nsTreeBodyFrame::DetachImageListeners()
+{
+ mCreatedListeners.Clear();
+}
+
+void
+nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener)
+{
+ if (aListener) {
+ mCreatedListeners.RemoveEntry(aListener);
+ }
+}
+
+#ifdef ACCESSIBILITY
+void
+nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount)
+{
+ nsCOMPtr<nsIContent> content(GetBaseElement());
+ if (!content)
+ return;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
+ if (!domDoc)
+ return;
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
+ getter_AddRefs(event));
+
+ nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
+ if (!treeEvent)
+ return;
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag)
+ return;
+
+ // Set 'index' data - the row index rows are changed from.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("index"), aIndex);
+
+ // Set 'count' data - the number of changed rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount);
+
+ RefPtr<nsVariant> detailVariant(new nsVariant());
+
+ detailVariant->SetAsISupports(propBag);
+ treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"),
+ true, false, detailVariant);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(content, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx,
+ nsITreeColumn *aStartCol,
+ nsITreeColumn *aEndCol)
+{
+ nsCOMPtr<nsIContent> content(GetBaseElement());
+ if (!content)
+ return;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
+ if (!domDoc)
+ return;
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
+ getter_AddRefs(event));
+
+ nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
+ if (!treeEvent)
+ return;
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag)
+ return;
+
+ if (aStartRowIdx != -1 && aEndRowIdx != -1) {
+ // Set 'startrow' data - the start index of invalidated rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startrow"),
+ aStartRowIdx);
+
+ // Set 'endrow' data - the end index of invalidated rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endrow"),
+ aEndRowIdx);
+ }
+
+ if (aStartCol && aEndCol) {
+ // Set 'startcolumn' data - the start index of invalidated rows.
+ int32_t startColIdx = 0;
+ nsresult rv = aStartCol->GetIndex(&startColIdx);
+ if (NS_FAILED(rv))
+ return;
+
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"),
+ startColIdx);
+
+ // Set 'endcolumn' data - the start index of invalidated rows.
+ int32_t endColIdx = 0;
+ rv = aEndCol->GetIndex(&endColIdx);
+ if (NS_FAILED(rv))
+ return;
+
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"),
+ endColIdx);
+ }
+
+ RefPtr<nsVariant> detailVariant(new nsVariant());
+
+ detailVariant->SetAsISupports(propBag);
+ treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"),
+ true, false, detailVariant);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(content, event);
+ asyncDispatcher->PostDOMEvent();
+}
+#endif
+
+class nsOverflowChecker : public Runnable
+{
+public:
+ explicit nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {}
+ NS_IMETHOD Run() override
+ {
+ if (mFrame.IsAlive()) {
+ nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
+ nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
+ tree->CheckOverflow(parts);
+ }
+ return NS_OK;
+ }
+private:
+ nsWeakFrame mFrame;
+};
+
+bool
+nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation)
+{
+ ScrollParts parts = GetScrollParts();
+ nsWeakFrame weakFrame(this);
+ nsWeakFrame weakColumnsFrame(parts.mColumnsFrame);
+ UpdateScrollbars(parts);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ if (aNeedsFullInvalidation) {
+ Invalidate();
+ }
+ InvalidateScrollbars(parts, weakColumnsFrame);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+
+ // Overflow checking dispatches synchronous events, which can cause infinite
+ // recursion during reflow. Do the first overflow check synchronously, but
+ // force any nested checks to round-trip through the event loop. See bug
+ // 905909.
+ RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
+ if (!mCheckingOverflow) {
+ nsContentUtils::AddScriptRunner(checker);
+ } else {
+ NS_DispatchToCurrentThread(checker);
+ }
+ return weakFrame.IsAlive();
+}
+
+nsresult
+nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest)
+{
+ nsLayoutUtils::RegisterImageRequest(PresContext(),
+ aRequest, nullptr);
+
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeBodyFrame.h b/layout/xul/tree/nsTreeBodyFrame.h
new file mode 100644
index 000000000..9620c8ccb
--- /dev/null
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -0,0 +1,651 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeBodyFrame_h
+#define nsTreeBodyFrame_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsLeafBoxFrame.h"
+#include "nsITreeView.h"
+#include "nsICSSPseudoComparator.h"
+#include "nsIScrollbarMediator.h"
+#include "nsITimer.h"
+#include "nsIReflowCallback.h"
+#include "nsTArray.h"
+#include "nsTreeStyleCache.h"
+#include "nsTreeColumns.h"
+#include "nsDataHashtable.h"
+#include "imgIRequest.h"
+#include "imgINotificationObserver.h"
+#include "nsScrollbarFrame.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+class nsFontMetrics;
+class nsOverflowChecker;
+class nsTreeImageListener;
+
+namespace mozilla {
+namespace layout {
+class ScrollbarActivity;
+} // namespace layout
+} // namespace mozilla
+
+// An entry in the tree's image cache
+struct nsTreeImageCacheEntry
+{
+ nsTreeImageCacheEntry() {}
+ nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
+ : request(aRequest), listener(aListener) {}
+
+ nsCOMPtr<imgIRequest> request;
+ nsCOMPtr<imgINotificationObserver> listener;
+};
+
+// The actual frame that paints the cells and rows.
+class nsTreeBodyFrame final
+ : public nsLeafBoxFrame
+ , public nsICSSPseudoComparator
+ , public nsIScrollbarMediator
+ , public nsIReflowCallback
+{
+ typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+ explicit nsTreeBodyFrame(nsStyleContext* aContext);
+ ~nsTreeBodyFrame();
+
+ NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // Callback handler methods for refresh driver based animations.
+ // Calls to these functions are forwarded from nsTreeImageListener. These
+ // mirror how nsImageFrame works.
+ nsresult OnImageIsAnimated(imgIRequest* aRequest);
+
+ // non-virtual signatures like nsITreeBodyFrame
+ already_AddRefed<nsTreeColumns> Columns() const
+ {
+ RefPtr<nsTreeColumns> cols = mColumns;
+ return cols.forget();
+ }
+ already_AddRefed<nsITreeView> GetExistingView() const
+ {
+ nsCOMPtr<nsITreeView> view = mView;
+ return view.forget();
+ }
+ nsresult GetView(nsITreeView **aView);
+ nsresult SetView(nsITreeView *aView);
+ bool GetFocused() const { return mFocused; }
+ nsresult SetFocused(bool aFocused);
+ nsresult GetTreeBody(nsIDOMElement **aElement);
+ int32_t RowHeight() const;
+ int32_t RowWidth();
+ int32_t GetHorizontalPosition() const;
+ nsresult GetSelectionRegion(nsIScriptableRegion **aRegion);
+ int32_t FirstVisibleRow() const { return mTopRowIndex; }
+ int32_t LastVisibleRow() const { return mTopRowIndex + mPageLength; }
+ int32_t PageLength() const { return mPageLength; }
+ nsresult EnsureRowIsVisible(int32_t aRow);
+ nsresult EnsureCellIsVisible(int32_t aRow, nsITreeColumn *aCol);
+ nsresult ScrollToRow(int32_t aRow);
+ nsresult ScrollByLines(int32_t aNumLines);
+ nsresult ScrollByPages(int32_t aNumPages);
+ nsresult ScrollToCell(int32_t aRow, nsITreeColumn *aCol);
+ nsresult ScrollToColumn(nsITreeColumn *aCol);
+ nsresult ScrollToHorizontalPosition(int32_t aValue);
+ nsresult Invalidate();
+ nsresult InvalidateColumn(nsITreeColumn *aCol);
+ nsresult InvalidateRow(int32_t aRow);
+ nsresult InvalidateCell(int32_t aRow, nsITreeColumn *aCol);
+ nsresult InvalidateRange(int32_t aStart, int32_t aEnd);
+ nsresult InvalidateColumnRange(int32_t aStart, int32_t aEnd,
+ nsITreeColumn *aCol);
+ nsresult GetRowAt(int32_t aX, int32_t aY, int32_t *aValue);
+ nsresult GetCellAt(int32_t aX, int32_t aY, int32_t *aRow,
+ nsITreeColumn **aCol, nsACString &aChildElt);
+ nsresult GetCoordsForCellItem(int32_t aRow, nsITreeColumn *aCol,
+ const nsACString &aElt,
+ int32_t *aX, int32_t *aY,
+ int32_t *aWidth, int32_t *aHeight);
+ nsresult IsCellCropped(int32_t aRow, nsITreeColumn *aCol, bool *aResult);
+ nsresult RowCountChanged(int32_t aIndex, int32_t aCount);
+ nsresult BeginUpdateBatch();
+ nsresult EndUpdateBatch();
+ nsresult ClearStyleAndImageCaches();
+
+ void CancelImageRequests();
+
+ void ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth);
+
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea = false) override;
+
+ // nsIReflowCallback
+ virtual bool ReflowFinished() override;
+ virtual void ReflowCallbackCanceled() override;
+
+ // nsICSSPseudoComparator
+ virtual bool PseudoMatches(nsCSSSelector* aSelector) override;
+
+ // nsIScrollbarMediator
+ virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) override;
+ virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos) override;
+ virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) override {}
+ virtual void VisibilityChanged(bool aVisible) override { Invalidate(); }
+ virtual nsIFrame* GetScrollbarBox(bool aVertical) override {
+ ScrollParts parts = GetScrollParts();
+ return aVertical ? parts.mVScrollbar : parts.mHScrollbar;
+ }
+ virtual void ScrollbarActivityStarted() const override;
+ virtual void ScrollbarActivityStopped() const override;
+ virtual bool IsScrollbarOnRight() const override {
+ return (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR);
+ }
+ virtual bool ShouldSuppressScrollbarRepaints() const override {
+ return false;
+ }
+
+ // Overridden from nsIFrame to cache our pres context.
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ friend nsIFrame* NS_NewTreeBodyFrame(nsIPresShell* aPresShell);
+ friend class nsTreeColumn;
+
+ struct ScrollParts {
+ nsScrollbarFrame* mVScrollbar;
+ nsCOMPtr<nsIContent> mVScrollbarContent;
+ nsScrollbarFrame* mHScrollbar;
+ nsCOMPtr<nsIContent> mHScrollbarContent;
+ nsIFrame* mColumnsFrame;
+ nsIScrollableFrame* mColumnsScrollFrame;
+ };
+
+ DrawResult PaintTreeBody(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt);
+
+ nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; }
+
+ // Get the base element, <tree> or <select>
+ nsIContent* GetBaseElement();
+
+ bool GetVerticalOverflow() const { return mVerticalOverflow; }
+ bool GetHorizontalOverflow() const {return mHorizontalOverflow; }
+
+protected:
+ friend class nsOverflowChecker;
+
+ // This method paints a specific column background of the tree.
+ DrawResult PaintColumn(nsTreeColumn* aColumn,
+ const nsRect& aColumnRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a single row in the tree.
+ DrawResult PaintRow(int32_t aRowIndex,
+ const nsRect& aRowRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt);
+
+ // This method paints a single separator in the tree.
+ DrawResult PaintSeparator(int32_t aRowIndex,
+ const nsRect& aSeparatorRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a specific cell in a given row of the tree.
+ DrawResult PaintCell(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCellRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX,
+ nsPoint aPt);
+
+ // This method paints the twisty inside a cell in the primary column of an tree.
+ DrawResult PaintTwisty(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX);
+
+ // This method paints the image inside the cell of an tree.
+ DrawResult PaintImage(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aImageRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX);
+
+ // This method paints the text string inside a particular cell of the tree.
+ DrawResult PaintText(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTextRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX);
+
+ // This method paints the checkbox inside a particular cell of the tree.
+ DrawResult PaintCheckbox(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCheckboxRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints the progress meter inside a particular cell of the tree.
+ DrawResult PaintProgressMeter(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aProgressMeterRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a drop feedback of the tree.
+ DrawResult PaintDropFeedback(const nsRect& aDropFeedbackRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt);
+
+ // This method is called with a specific style context and rect to
+ // paint the background rect as if it were a full-blown frame.
+ DrawResult PaintBackgroundLayer(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect);
+
+
+ // An internal hit test. aX and aY are expected to be in twips in the
+ // coordinate system of this frame.
+ int32_t GetRowAt(nscoord aX, nscoord aY);
+
+ // Check for bidi characters in the text, and if there are any, ensure
+ // that the prescontext is in bidi mode.
+ void CheckTextForBidi(nsAutoString& aText);
+
+ void AdjustForCellText(nsAutoString& aText,
+ int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRenderingContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsRect& aTextRect);
+
+ // A helper used when hit testing.
+ nsIAtom* GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
+ int32_t aRowIndex, nsTreeColumn* aColumn);
+
+ // An internal hit test. aX and aY are expected to be in twips in the
+ // coordinate system of this frame.
+ void GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, nsTreeColumn** aCol,
+ nsIAtom** aChildElt);
+
+ // Retrieve the area for the twisty for a cell.
+ nsITheme* GetTwistyRect(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRect& aImageRect,
+ nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsStyleContext* aTwistyContext);
+
+ // Fetch an image from the image cache.
+ nsresult GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult);
+
+ // Returns the size of a given image. This size *includes* border and
+ // padding. It does not include margins.
+ nsRect GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, nsStyleContext* aStyleContext);
+
+ // Returns the destination size of the image, not including borders and padding.
+ nsSize GetImageDestSize(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);
+
+ // Returns the source rectangle of the image to be displayed.
+ nsRect GetImageSourceRect(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);
+
+ // Returns the height of rows in the tree.
+ int32_t GetRowHeight();
+
+ // Returns our indentation width.
+ int32_t GetIndentation();
+
+ // Calculates our width/height once border and padding have been removed.
+ void CalcInnerBox();
+
+ // Calculate the total width of our scrollable portion
+ nscoord CalcHorzWidth(const ScrollParts& aParts);
+
+ // Looks up a style context in the style cache. On a cache miss we resolve
+ // the pseudo-styles passed in and place them into the cache.
+ nsStyleContext* GetPseudoStyleContext(nsIAtom* aPseudoElement);
+
+ // Retrieves the scrollbars and scrollview relevant to this treebody. We
+ // traverse the frame tree under our base element, in frame order, looking
+ // for the first relevant vertical scrollbar, horizontal scrollbar, and
+ // scrollable frame (with associated content and scrollable view). These
+ // are all volatile and should not be retained.
+ ScrollParts GetScrollParts();
+
+ // Update the curpos of the scrollbar.
+ void UpdateScrollbars(const ScrollParts& aParts);
+
+ // Update the maxpos of the scrollbar.
+ void InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame);
+
+ // Check overflow and generate events.
+ void CheckOverflow(const ScrollParts& aParts);
+
+ // Calls UpdateScrollbars, Invalidate aNeedsFullInvalidation if true,
+ // InvalidateScrollbars and finally CheckOverflow.
+ // returns true if the frame is still alive after the method call.
+ bool FullScrollbarsUpdate(bool aNeedsFullInvalidation);
+
+ // Use to auto-fill some of the common properties without the view having to do it.
+ // Examples include container, open, selected, and focus.
+ void PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol);
+
+ // Our internal scroll method, used by all the public scroll methods.
+ nsresult ScrollInternal(const ScrollParts& aParts, int32_t aRow);
+ nsresult ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow);
+ nsresult ScrollToColumnInternal(const ScrollParts& aParts, nsITreeColumn* aCol);
+ nsresult ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition);
+ nsresult EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow);
+
+ // Convert client pixels into appunits in our coordinate space.
+ nsPoint AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY);
+
+ // Cache the box object
+ void EnsureBoxObject();
+
+ void EnsureView();
+
+ nsresult GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
+ nsRenderingContext* aRenderingContext,
+ nscoord& aDesiredSize, nscoord& aCurrentSize);
+ nscoord CalcMaxRowWidth();
+
+ // Translate the given rect horizontally from tree coordinates into the
+ // coordinate system of our nsTreeBodyFrame. If clip is true, then clip the
+ // rect to its intersection with mInnerBox in the horizontal direction.
+ // Return whether the result has a nonempty intersection with mInnerBox
+ // after projecting both onto the horizontal coordinate axis.
+ bool OffsetForHorzScroll(nsRect& rect, bool clip);
+
+ bool CanAutoScroll(int32_t aRowIndex);
+
+ // Calc the row and above/below/on status given where the mouse currently is hovering.
+ // Also calc if we're in the region in which we want to auto-scroll the tree.
+ // A positive value of |aScrollLines| means scroll down, a negative value
+ // means scroll up, a zero value means that we aren't in drag scroll region.
+ void ComputeDropPosition(mozilla::WidgetGUIEvent* aEvent,
+ int32_t* aRow,
+ int16_t* aOrient,
+ int16_t* aScrollLines);
+
+ // Mark ourselves dirty if we're a select widget
+ void MarkDirtyIfSelect();
+
+ void InvalidateDropFeedback(int32_t aRow, int16_t aOrientation) {
+ InvalidateRow(aRow);
+ if (aOrientation != nsITreeView::DROP_ON)
+ InvalidateRow(aRow + aOrientation);
+ }
+
+public:
+ static
+ already_AddRefed<nsTreeColumn> GetColumnImpl(nsITreeColumn* aUnknownCol) {
+ if (!aUnknownCol)
+ return nullptr;
+
+ nsCOMPtr<nsTreeColumn> col = do_QueryInterface(aUnknownCol);
+ return col.forget();
+ }
+
+ /**
+ * Remove an nsITreeImageListener from being tracked by this frame. Only tree
+ * image listeners that are created by this frame are tracked.
+ *
+ * @param aListener A pointer to an nsTreeImageListener to no longer
+ * track.
+ */
+ void RemoveTreeImageListener(nsTreeImageListener* aListener);
+
+protected:
+
+ // Create a new timer. This method is used to delay various actions like
+ // opening/closing folders or tree scrolling.
+ // aID is type of the action, aFunc is the function to be called when
+ // the timer fires and aType is type of timer - one shot or repeating.
+ nsresult CreateTimer(const mozilla::LookAndFeel::IntID aID,
+ nsTimerCallbackFunc aFunc, int32_t aType,
+ nsITimer** aTimer);
+
+ static void OpenCallback(nsITimer *aTimer, void *aClosure);
+
+ static void CloseCallback(nsITimer *aTimer, void *aClosure);
+
+ static void LazyScrollCallback(nsITimer *aTimer, void *aClosure);
+
+ static void ScrollCallback(nsITimer *aTimer, void *aClosure);
+
+ class ScrollEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit ScrollEvent(nsTreeBodyFrame *aInner) : mInner(aInner) {}
+ void Revoke() { mInner = nullptr; }
+ private:
+ nsTreeBodyFrame* mInner;
+ };
+
+ void PostScrollEvent();
+ void FireScrollEvent();
+
+ /**
+ * Clear the pointer to this frame for all nsTreeImageListeners that were
+ * created by this frame.
+ */
+ void DetachImageListeners();
+
+#ifdef ACCESSIBILITY
+ /**
+ * Fires 'treeRowCountChanged' event asynchronously. The event supports
+ * nsIDOMCustomEvent interface that is used to expose the following
+ * information structures.
+ *
+ * @param aIndex the row index rows are added/removed from
+ * @param aCount the number of added/removed rows (the sign points to
+ * an operation, plus - addition, minus - removing)
+ */
+ void FireRowCountChangedEvent(int32_t aIndex, int32_t aCount);
+
+ /**
+ * Fires 'treeInvalidated' event asynchronously. The event supports
+ * nsIDOMCustomEvent interface that is used to expose the information
+ * structures described by method arguments.
+ *
+ * @param aStartRow the start index of invalidated rows, -1 means that
+ * columns have been invalidated only
+ * @param aEndRow the end index of invalidated rows, -1 means that columns
+ * have been invalidated only
+ * @param aStartCol the start invalidated column, nullptr means that only rows
+ * have been invalidated
+ * @param aEndCol the end invalidated column, nullptr means that rows have
+ * been invalidated only
+ */
+ void FireInvalidateEvent(int32_t aStartRow, int32_t aEndRow,
+ nsITreeColumn *aStartCol, nsITreeColumn *aEndCol);
+#endif
+
+protected: // Data Members
+
+ class Slots {
+ public:
+ Slots() {
+ }
+
+ ~Slots() {
+ if (mTimer)
+ mTimer->Cancel();
+ }
+
+ friend class nsTreeBodyFrame;
+
+ protected:
+ // If the drop is actually allowed here or not.
+ bool mDropAllowed;
+
+ // True while dragging over the tree.
+ bool mIsDragging;
+
+ // The row the mouse is hovering over during a drop.
+ int32_t mDropRow;
+
+ // Where we want to draw feedback (above/on this row/below) if allowed.
+ int16_t mDropOrient;
+
+ // Number of lines to be scrolled.
+ int16_t mScrollLines;
+
+ // The drag action that was received for this slot
+ uint32_t mDragAction;
+
+ // Timer for opening/closing spring loaded folders or scrolling the tree.
+ nsCOMPtr<nsITimer> mTimer;
+
+ // An array used to keep track of all spring loaded folders.
+ nsTArray<int32_t> mArray;
+ };
+
+ Slots* mSlots;
+
+ nsRevocableEventPtr<ScrollEvent> mScrollEvent;
+
+ RefPtr<ScrollbarActivity> mScrollbarActivity;
+
+ // The cached box object parent.
+ nsCOMPtr<nsITreeBoxObject> mTreeBoxObject;
+
+ // Cached column information.
+ RefPtr<nsTreeColumns> mColumns;
+
+ // The current view for this tree widget. We get all of our row and cell data
+ // from the view.
+ nsCOMPtr<nsITreeView> mView;
+
+ // A cache of all the style contexts we have seen for rows and cells of the tree. This is a mapping from
+ // a list of atoms to a corresponding style context. This cache stores every combination that
+ // occurs in the tree, so for n distinct properties, this cache could have 2 to the n entries
+ // (the power set of all row properties).
+ nsTreeStyleCache mStyleCache;
+
+ // A hashtable that maps from URLs to image request/listener pairs. The URL
+ // is provided by the view or by the style context. The style context
+ // represents a resolved :-moz-tree-cell-image (or twisty) pseudo-element.
+ // It maps directly to an imgIRequest.
+ nsDataHashtable<nsStringHashKey, nsTreeImageCacheEntry> mImageCache;
+
+ // A scratch array used when looking up cached style contexts.
+ AtomArray mScratchArray;
+
+ // The index of the first visible row and the # of rows visible onscreen.
+ // The tree only examines onscreen rows, starting from
+ // this index and going up to index+pageLength.
+ int32_t mTopRowIndex;
+ int32_t mPageLength;
+
+ // The horizontal scroll position
+ nscoord mHorzPosition;
+
+ // The original desired horizontal width before changing it and posting a
+ // reflow callback. In some cases, the desired horizontal width can first be
+ // different from the current desired horizontal width, only to return to
+ // the same value later during the same reflow. In this case, we can cancel
+ // the posted reflow callback and prevent an unnecessary reflow.
+ nscoord mOriginalHorzWidth;
+ // Our desired horizontal width (the width for which we actually have tree
+ // columns).
+ nscoord mHorzWidth;
+ // The amount by which to adjust the width of the last cell.
+ // This depends on whether or not the columnpicker and scrollbars are present.
+ nscoord mAdjustWidth;
+
+ // Cached heights and indent info.
+ nsRect mInnerBox; // 4-byte aligned
+ int32_t mRowHeight;
+ int32_t mIndentation;
+ nscoord mStringWidth;
+
+ int32_t mUpdateBatchNest;
+
+ // Cached row count.
+ int32_t mRowCount;
+
+ // The row the mouse is hovering over.
+ int32_t mMouseOverRow;
+
+ // Whether or not we're currently focused.
+ bool mFocused;
+
+ // Do we have a fixed number of onscreen rows?
+ bool mHasFixedRowCount;
+
+ bool mVerticalOverflow;
+ bool mHorizontalOverflow;
+
+ bool mReflowCallbackPosted;
+
+ // Set while we flush layout to take account of effects of
+ // overflow/underflow event handlers
+ bool mCheckingOverflow;
+
+ // Hash table to keep track of which listeners we created and thus
+ // have pointers to us.
+ nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners;
+
+}; // class nsTreeBodyFrame
+
+#endif
diff --git a/layout/xul/tree/nsTreeColFrame.cpp b/layout/xul/tree/nsTreeColFrame.cpp
new file mode 100644
index 000000000..649c0b0b4
--- /dev/null
+++ b/layout/xul/tree/nsTreeColFrame.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsTreeColFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsIBoxObject.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "nsIDOMElement.h"
+#include "nsITreeColumns.h"
+#include "nsIDOMXULTreeElement.h"
+#include "nsDisplayList.h"
+#include "nsTreeBodyFrame.h"
+
+//
+// NS_NewTreeColFrame
+//
+// Creates a new col frame
+//
+nsIFrame*
+NS_NewTreeColFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTreeColFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTreeColFrame)
+
+// Destructor
+nsTreeColFrame::~nsTreeColFrame()
+{
+}
+
+void
+nsTreeColFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+ InvalidateColumns();
+}
+
+void
+nsTreeColFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ InvalidateColumns(false);
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+class nsDisplayXULTreeColSplitterTarget : public nsDisplayItem {
+public:
+ nsDisplayXULTreeColSplitterTarget(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULTreeColSplitterTarget);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULTreeColSplitterTarget() {
+ MOZ_COUNT_DTOR(nsDisplayXULTreeColSplitterTarget);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ NS_DISPLAY_DECL_NAME("XULTreeColSplitterTarget", TYPE_XUL_TREE_COL_SPLITTER_TARGET)
+};
+
+void
+nsDisplayXULTreeColSplitterTarget::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsRect rect = aRect - ToReferenceFrame();
+ // If we are in either in the first 4 pixels or the last 4 pixels, we're going to
+ // do something really strange. Check for an adjacent splitter.
+ bool left = false;
+ bool right = false;
+ if (mFrame->GetSize().width - nsPresContext::CSSPixelsToAppUnits(4) <= rect.XMost()) {
+ right = true;
+ } else if (nsPresContext::CSSPixelsToAppUnits(4) > rect.x) {
+ left = true;
+ }
+
+ // Swap left and right for RTL trees in order to find the correct splitter
+ if (mFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+ bool tmp = left;
+ left = right;
+ right = tmp;
+ }
+
+ if (left || right) {
+ // We are a header. Look for the correct splitter.
+ nsIFrame* child;
+ if (left)
+ child = mFrame->GetPrevSibling();
+ else
+ child = mFrame->GetNextSibling();
+
+ if (child && child->GetContent()->NodeInfo()->Equals(nsGkAtoms::splitter,
+ kNameSpaceID_XUL)) {
+ aOutFrames->AppendElement(child);
+ }
+ }
+
+}
+
+void
+nsTreeColFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!aBuilder->IsForEventDelivery()) {
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
+
+ WrapListsInRedirector(aBuilder, set, aLists);
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayXULTreeColSplitterTarget(aBuilder, this));
+}
+
+nsresult
+nsTreeColFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ if (aAttribute == nsGkAtoms::ordinal || aAttribute == nsGkAtoms::primary) {
+ InvalidateColumns();
+ }
+
+ return rv;
+}
+
+void
+nsTreeColFrame::SetXULBounds(nsBoxLayoutState& aBoxLayoutState,
+ const nsRect& aRect,
+ bool aRemoveOverflowArea)
+{
+ nscoord oldWidth = mRect.width;
+
+ nsBoxFrame::SetXULBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
+ if (mRect.width != oldWidth) {
+ nsITreeBoxObject* treeBoxObject = GetTreeBoxObject();
+ if (treeBoxObject) {
+ treeBoxObject->Invalidate();
+ }
+ }
+}
+
+nsITreeBoxObject*
+nsTreeColFrame::GetTreeBoxObject()
+{
+ nsITreeBoxObject* result = nullptr;
+
+ nsIContent* parent = mContent->GetParent();
+ if (parent) {
+ nsIContent* grandParent = parent->GetParent();
+ nsCOMPtr<nsIDOMXULElement> treeElement = do_QueryInterface(grandParent);
+ if (treeElement) {
+ nsCOMPtr<nsIBoxObject> boxObject;
+ treeElement->GetBoxObject(getter_AddRefs(boxObject));
+
+ nsCOMPtr<nsITreeBoxObject> treeBoxObject = do_QueryInterface(boxObject);
+ result = treeBoxObject.get();
+ }
+ }
+ return result;
+}
+
+void
+nsTreeColFrame::InvalidateColumns(bool aCanWalkFrameTree)
+{
+ nsITreeBoxObject* treeBoxObject = GetTreeBoxObject();
+ if (treeBoxObject) {
+ nsCOMPtr<nsITreeColumns> columns;
+
+ if (aCanWalkFrameTree) {
+ treeBoxObject->GetColumns(getter_AddRefs(columns));
+ } else {
+ nsTreeBodyFrame* body = static_cast<mozilla::dom::TreeBoxObject*>
+ (treeBoxObject)->GetCachedTreeBodyFrame();
+ if (body) {
+ columns = body->Columns();
+ }
+ }
+
+ if (columns)
+ columns->InvalidateColumns();
+ }
+}
diff --git a/layout/xul/tree/nsTreeColFrame.h b/layout/xul/tree/nsTreeColFrame.h
new file mode 100644
index 000000000..8fc3219d5
--- /dev/null
+++ b/layout/xul/tree/nsTreeColFrame.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsITreeBoxObject;
+
+nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+class nsTreeColFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsTreeColFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext) {}
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea = false) override;
+
+ friend nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+protected:
+ virtual ~nsTreeColFrame();
+
+ /**
+ * @return the tree box object of the tree this column belongs to, or nullptr.
+ */
+ nsITreeBoxObject* GetTreeBoxObject();
+
+ /**
+ * Helper method that gets the nsITreeColumns object this column belongs to
+ * and calls InvalidateColumns() on it.
+ */
+ void InvalidateColumns(bool aCanWalkFrameTree = true);
+};
diff --git a/layout/xul/tree/nsTreeColumns.cpp b/layout/xul/tree/nsTreeColumns.cpp
new file mode 100644
index 000000000..c6ee19342
--- /dev/null
+++ b/layout/xul/tree/nsTreeColumns.cpp
@@ -0,0 +1,765 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsIBoxObject.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+#include "nsStyleContext.h"
+#include "nsDOMClassInfoID.h"
+#include "nsContentUtils.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "mozilla/dom/TreeColumnBinding.h"
+#include "mozilla/dom/TreeColumnsBinding.h"
+
+using namespace mozilla;
+
+// Column class that caches all the info about our column.
+nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent)
+ : mContent(aContent),
+ mColumns(aColumns),
+ mPrevious(nullptr)
+{
+ NS_ASSERTION(aContent &&
+ aContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL),
+ "nsTreeColumn's content must be a <xul:treecol>");
+
+ Invalidate();
+}
+
+nsTreeColumn::~nsTreeColumn()
+{
+ if (mNext) {
+ mNext->SetPrevious(nullptr);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsTreeColumn)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+ if (tmp->mNext) {
+ tmp->mNext->SetPrevious(nullptr);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext)
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsTreeColumn)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn)
+
+// QueryInterface implementation for nsTreeColumn
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITreeColumn)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ if (aIID.Equals(NS_GET_IID(nsTreeColumn))) {
+ AddRef();
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+ else
+NS_INTERFACE_MAP_END
+
+nsIFrame*
+nsTreeColumn::GetFrame()
+{
+ NS_ENSURE_TRUE(mContent, nullptr);
+
+ return mContent->GetPrimaryFrame();
+}
+
+bool
+nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame)
+{
+ NS_ASSERTION(GetFrame(), "should have checked for this already");
+
+ // cyclers are fixed width, don't adjust them
+ if (IsCycler())
+ return false;
+
+ // we're certainly not the last visible if we're not visible
+ if (GetFrame()->GetRect().width == 0)
+ return false;
+
+ // try to find a visible successor
+ for (nsTreeColumn *next = GetNext(); next; next = next->GetNext()) {
+ nsIFrame* frame = next->GetFrame();
+ if (frame && frame->GetRect().width > 0)
+ return false;
+ }
+ return true;
+}
+
+nsresult
+nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = nsRect();
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isRTL = aBodyFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ *aResult = frame->GetRect();
+ aResult->y = aY;
+ aResult->height = aHeight;
+ if (isRTL)
+ aResult->x += aBodyFrame->mAdjustWidth;
+ else if (IsLastVisible(aBodyFrame))
+ aResult->width += aBodyFrame->mAdjustWidth;
+ return NS_OK;
+}
+
+nsresult
+nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = frame->GetRect().x;
+ return NS_OK;
+}
+
+nsresult
+nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = frame->GetRect().width;
+ if (IsLastVisible(aBodyFrame))
+ *aResult += aBodyFrame->mAdjustWidth;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsTreeColumn::GetElement(nsIDOMElement** aElement)
+{
+ if (mContent) {
+ return CallQueryInterface(mContent, aElement);
+ }
+ *aElement = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetColumns(nsITreeColumns** aColumns)
+{
+ NS_IF_ADDREF(*aColumns = mColumns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetX(int32_t* aX)
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ *aX = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetWidth(int32_t* aWidth)
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetId(nsAString& aId)
+{
+ aId = GetId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetIdConst(const char16_t** aIdConst)
+{
+ *aIdConst = mId.get();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetAtom(nsIAtom** aAtom)
+{
+ NS_IF_ADDREF(*aAtom = GetAtom());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetIndex(int32_t* aIndex)
+{
+ *aIndex = GetIndex();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetPrimary(bool* aPrimary)
+{
+ *aPrimary = IsPrimary();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetCycler(bool* aCycler)
+{
+ *aCycler = IsCycler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetEditable(bool* aEditable)
+{
+ *aEditable = IsEditable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetSelectable(bool* aSelectable)
+{
+ *aSelectable = IsSelectable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetType(int16_t* aType)
+{
+ *aType = GetType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetNext(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetNext());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetPrevious(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetPrevious());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::Invalidate()
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ // Fetch the Id.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mId);
+
+ // If we have an Id, cache the Id as an atom.
+ if (!mId.IsEmpty()) {
+ mAtom = NS_Atomize(mId);
+ }
+
+ // Cache our index.
+ nsTreeUtils::GetColumnIndex(mContent, &mIndex);
+
+ const nsStyleVisibility* vis = frame->StyleVisibility();
+
+ // Cache our text alignment policy.
+ const nsStyleText* textStyle = frame->StyleText();
+
+ mTextAlignment = textStyle->mTextAlign;
+ // START or END alignment sometimes means RIGHT
+ if ((mTextAlignment == NS_STYLE_TEXT_ALIGN_START &&
+ vis->mDirection == NS_STYLE_DIRECTION_RTL) ||
+ (mTextAlignment == NS_STYLE_TEXT_ALIGN_END &&
+ vis->mDirection == NS_STYLE_DIRECTION_LTR)) {
+ mTextAlignment = NS_STYLE_TEXT_ALIGN_RIGHT;
+ } else if (mTextAlignment == NS_STYLE_TEXT_ALIGN_START ||
+ mTextAlignment == NS_STYLE_TEXT_ALIGN_END) {
+ mTextAlignment = NS_STYLE_TEXT_ALIGN_LEFT;
+ }
+
+ // Figure out if we're the primary column (that has to have indentation
+ // and twisties drawn.
+ mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // Figure out if we're a cycling column (one that doesn't cause a selection
+ // to happen).
+ mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler,
+ nsGkAtoms::_true, eCaseMatters);
+
+ mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_true, eCaseMatters);
+
+ mIsSelectable = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable,
+ nsGkAtoms::_false, eCaseMatters);
+
+ mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // Figure out our column type. Default type is text.
+ mType = nsITreeColumn::TYPE_TEXT;
+ static nsIContent::AttrValuesArray typestrings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::progressmeter, &nsGkAtoms::password,
+ nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ typestrings, eCaseMatters)) {
+ case 0: mType = nsITreeColumn::TYPE_CHECKBOX; break;
+ case 1: mType = nsITreeColumn::TYPE_PROGRESSMETER; break;
+ case 2: mType = nsITreeColumn::TYPE_PASSWORD; break;
+ }
+
+ // Fetch the crop style.
+ mCropStyle = 0;
+ static nsIContent::AttrValuesArray cropstrings[] =
+ {&nsGkAtoms::center, &nsGkAtoms::left, &nsGkAtoms::start, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
+ cropstrings, eCaseMatters)) {
+ case 0:
+ mCropStyle = 1;
+ break;
+ case 1:
+ case 2:
+ mCropStyle = 2;
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsIContent*
+nsTreeColumn::GetParentObject() const
+{
+ return mContent;
+}
+
+/* virtual */ JSObject*
+nsTreeColumn::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::TreeColumnBinding::Wrap(aCx, this, aGivenProto);
+}
+
+mozilla::dom::Element*
+nsTreeColumn::GetElement(mozilla::ErrorResult& aRv)
+{
+ nsCOMPtr<nsIDOMElement> element;
+ aRv = GetElement(getter_AddRefs(element));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsINode> node = do_QueryInterface(element);
+ return node->AsElement();
+}
+
+int32_t
+nsTreeColumn::GetX(mozilla::ErrorResult& aRv)
+{
+ int32_t x;
+ aRv = GetX(&x);
+ return x;
+}
+
+int32_t
+nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv)
+{
+ int32_t width;
+ aRv = GetWidth(&width);
+ return width;
+}
+
+void
+nsTreeColumn::Invalidate(mozilla::ErrorResult& aRv)
+{
+ aRv = Invalidate();
+}
+
+nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree)
+ : mTree(aTree)
+{
+}
+
+nsTreeColumns::~nsTreeColumns()
+{
+ nsTreeColumns::InvalidateColumns();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns)
+
+// QueryInterface implementation for nsTreeColumns
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITreeColumns)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns)
+
+nsIContent*
+nsTreeColumns::GetParentObject() const
+{
+ return mTree ? mTree->GetBaseElement() : nullptr;
+}
+
+/* virtual */ JSObject*
+nsTreeColumns::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::TreeColumnsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+dom::TreeBoxObject*
+nsTreeColumns::GetTree() const
+{
+ return mTree ? static_cast<mozilla::dom::TreeBoxObject*>(mTree->GetTreeBoxObject()) : nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetTree(nsITreeBoxObject** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetTree());
+ return NS_OK;
+}
+
+uint32_t
+nsTreeColumns::Count()
+{
+ EnsureColumns();
+ uint32_t count = 0;
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ ++count;
+ }
+ return count;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetCount(int32_t* _retval)
+{
+ *_retval = Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetLength(int32_t* _retval)
+{
+ *_retval = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetFirstColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetFirstColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetLastColumn()
+{
+ EnsureColumns();
+ nsTreeColumn* currCol = mFirstColumn;
+ while (currCol) {
+ nsTreeColumn* next = currCol->GetNext();
+ if (!next) {
+ return currCol;
+ }
+ currCol = next;
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetLastColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetLastColumn());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetPrimaryColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetPrimaryColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetSortedColumn()
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->mContent &&
+ nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
+ nsGkAtoms::sortDirection)) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetSortedColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetSortedColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetKeyColumn()
+{
+ EnsureColumns();
+
+ nsTreeColumn* first = nullptr;
+ nsTreeColumn* primary = nullptr;
+ nsTreeColumn* sorted = nullptr;
+
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ // Skip hidden columns.
+ if (!currCol->mContent ||
+ currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters))
+ continue;
+
+ // Skip non-text column
+ if (currCol->GetType() != nsITreeColumn::TYPE_TEXT)
+ continue;
+
+ if (!first)
+ first = currCol;
+
+ if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
+ nsGkAtoms::sortDirection)) {
+ // Use sorted column as the key.
+ sorted = currCol;
+ break;
+ }
+
+ if (currCol->IsPrimary())
+ if (!primary)
+ primary = currCol;
+ }
+
+ if (sorted)
+ return sorted;
+ if (primary)
+ return primary;
+ return first;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetKeyColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetKeyColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetColumnFor(dom::Element* aElement)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->mContent == aElement) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetColumnFor(nsIDOMElement* aElement, nsITreeColumn** _retval)
+{
+ nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
+ NS_ADDREF(*_retval = GetColumnFor(element));
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->GetId().Equals(aId)) {
+ aFound = true;
+ return currCol;
+ }
+ }
+ aFound = false;
+ return nullptr;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetNamedColumn(const nsAString& aId)
+{
+ bool dummy;
+ return NamedGetter(aId, dummy);
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetNamedColumn(const nsAString& aId, nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetNamedColumn(aId));
+ return NS_OK;
+}
+
+void
+nsTreeColumns::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ aNames.AppendElement(currCol->GetId());
+ }
+}
+
+
+nsTreeColumn*
+nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->GetIndex() == static_cast<int32_t>(aIndex)) {
+ aFound = true;
+ return currCol;
+ }
+ }
+ aFound = false;
+ return nullptr;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetColumnAt(uint32_t aIndex)
+{
+ bool dummy;
+ return IndexedGetter(aIndex, dummy);
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetColumnAt(int32_t aIndex, nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetColumnAt(static_cast<uint32_t>(aIndex)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::InvalidateColumns()
+{
+ for (nsTreeColumn* currCol = mFirstColumn; currCol;
+ currCol = currCol->GetNext()) {
+ currCol->SetColumns(nullptr);
+ }
+ mFirstColumn = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::RestoreNaturalOrder()
+{
+ if (!mTree)
+ return NS_OK;
+
+ nsIContent* content = mTree->GetBaseElement();
+
+ // Strong ref, since we'll be setting attributes
+ nsCOMPtr<nsIContent> colsContent =
+ nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treecols);
+ if (!colsContent)
+ return NS_OK;
+
+ for (uint32_t i = 0; i < colsContent->GetChildCount(); ++i) {
+ nsCOMPtr<nsIContent> child = colsContent->GetChildAt(i);
+ nsAutoString ordinal;
+ ordinal.AppendInt(i);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, true);
+ }
+
+ nsTreeColumns::InvalidateColumns();
+
+ if (mTree) {
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetPrimaryColumn()
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->IsPrimary()) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+void
+nsTreeColumns::EnsureColumns()
+{
+ if (mTree && !mFirstColumn) {
+ nsIContent* treeContent = mTree->GetBaseElement();
+ nsIContent* colsContent =
+ nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols);
+ if (!colsContent)
+ return;
+
+ nsIContent* colContent =
+ nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol);
+ if (!colContent)
+ return;
+
+ nsIFrame* colFrame = colContent->GetPrimaryFrame();
+ if (!colFrame)
+ return;
+
+ colFrame = colFrame->GetParent();
+ if (!colFrame)
+ return;
+
+ colFrame = colFrame->PrincipalChildList().FirstChild();
+ if (!colFrame)
+ return;
+
+ // Now that we have the first visible column,
+ // we can enumerate the columns in visible order
+ nsTreeColumn* currCol = nullptr;
+ while (colFrame) {
+ nsIContent* colContent = colFrame->GetContent();
+
+ if (colContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ // Create a new column structure.
+ nsTreeColumn* col = new nsTreeColumn(this, colContent);
+ if (!col)
+ return;
+
+ if (currCol) {
+ currCol->SetNext(col);
+ col->SetPrevious(currCol);
+ }
+ else {
+ mFirstColumn = col;
+ }
+ currCol = col;
+ }
+
+ colFrame = colFrame->GetNextSibling();
+ }
+ }
+}
diff --git a/layout/xul/tree/nsTreeColumns.h b/layout/xul/tree/nsTreeColumns.h
new file mode 100644
index 000000000..0e27c6a03
--- /dev/null
+++ b/layout/xul/tree/nsTreeColumns.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeColumns_h__
+#define nsTreeColumns_h__
+
+#include "nsITreeColumns.h"
+#include "nsITreeBoxObject.h"
+#include "mozilla/Attributes.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+class nsTreeBodyFrame;
+class nsTreeColumns;
+class nsIFrame;
+class nsIContent;
+struct nsRect;
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class Element;
+class TreeBoxObject;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_TREECOLUMN_IMPL_CID \
+{ /* 02cd1963-4b5d-4a6c-9223-814d3ade93a3 */ \
+ 0x02cd1963, \
+ 0x4b5d, \
+ 0x4a6c, \
+ {0x92, 0x23, 0x81, 0x4d, 0x3a, 0xde, 0x93, 0xa3} \
+}
+
+// This class is our column info. We use it to iterate our columns and to obtain
+// information about each column.
+class nsTreeColumn final : public nsITreeColumn
+ , public nsWrapperCache
+{
+public:
+ nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TREECOLUMN_IMPL_CID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumn)
+ NS_DECL_NSITREECOLUMN
+
+ // WebIDL
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ mozilla::dom::Element* GetElement(mozilla::ErrorResult& aRv);
+
+ nsTreeColumns* GetColumns() const { return mColumns; }
+
+ int32_t GetX(mozilla::ErrorResult& aRv);
+ int32_t GetWidth(mozilla::ErrorResult& aRv);
+
+ // GetId is fine
+ int32_t Index() const { return mIndex; }
+
+ bool Primary() const { return mIsPrimary; }
+ bool Cycler() const { return mIsCycler; }
+ bool Editable() const { return mIsEditable; }
+ bool Selectable() const { return mIsSelectable; }
+ int16_t Type() const { return mType; }
+
+ nsTreeColumn* GetNext() const { return mNext; }
+ nsTreeColumn* GetPrevious() const { return mPrevious; }
+
+ void Invalidate(mozilla::ErrorResult& aRv);
+
+ friend class nsTreeBodyFrame;
+ friend class nsTreeColumns;
+
+protected:
+ ~nsTreeColumn();
+ nsIFrame* GetFrame();
+ nsIFrame* GetFrame(nsTreeBodyFrame* aBodyFrame);
+ // Don't call this if GetWidthInTwips or GetRect fails
+ bool IsLastVisible(nsTreeBodyFrame* aBodyFrame);
+
+ /**
+ * Returns a rect with x and width taken from the frame's rect and specified
+ * y and height. May fail in case there's no frame for the column.
+ */
+ nsresult GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight,
+ nsRect* aResult);
+
+ nsresult GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult);
+ nsresult GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult);
+
+ void SetColumns(nsTreeColumns* aColumns) { mColumns = aColumns; }
+
+ const nsAString& GetId() { return mId; }
+ nsIAtom* GetAtom() { return mAtom; }
+
+ int32_t GetIndex() { return mIndex; }
+
+ bool IsPrimary() { return mIsPrimary; }
+ bool IsCycler() { return mIsCycler; }
+ bool IsEditable() { return mIsEditable; }
+ bool IsSelectable() { return mIsSelectable; }
+ bool Overflow() { return mOverflow; }
+
+ int16_t GetType() { return mType; }
+
+ int8_t GetCropStyle() { return mCropStyle; }
+ int32_t GetTextAlignment() { return mTextAlignment; }
+
+ void SetNext(nsTreeColumn* aNext) {
+ NS_ASSERTION(!mNext, "already have a next sibling");
+ mNext = aNext;
+ }
+ void SetPrevious(nsTreeColumn* aPrevious) { mPrevious = aPrevious; }
+
+private:
+ /**
+ * Non-null nsIContent for the associated <treecol> element.
+ */
+ nsCOMPtr<nsIContent> mContent;
+
+ nsTreeColumns* mColumns;
+
+ nsString mId;
+ nsCOMPtr<nsIAtom> mAtom;
+
+ int32_t mIndex;
+
+ bool mIsPrimary;
+ bool mIsCycler;
+ bool mIsEditable;
+ bool mIsSelectable;
+ bool mOverflow;
+
+ int16_t mType;
+
+ int8_t mCropStyle;
+ int8_t mTextAlignment;
+
+ RefPtr<nsTreeColumn> mNext;
+ nsTreeColumn* mPrevious;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsTreeColumn, NS_TREECOLUMN_IMPL_CID)
+
+class nsTreeColumns final : public nsITreeColumns
+ , public nsWrapperCache
+{
+private:
+ ~nsTreeColumns();
+
+public:
+ explicit nsTreeColumns(nsTreeBodyFrame* aTree);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumns)
+ NS_DECL_NSITREECOLUMNS
+
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ mozilla::dom::TreeBoxObject* GetTree() const;
+ uint32_t Count();
+ uint32_t Length()
+ {
+ return Count();
+ }
+
+ nsTreeColumn* GetFirstColumn() { EnsureColumns(); return mFirstColumn; }
+ nsTreeColumn* GetLastColumn();
+
+ nsTreeColumn* GetPrimaryColumn();
+ nsTreeColumn* GetSortedColumn();
+ nsTreeColumn* GetKeyColumn();
+
+ nsTreeColumn* GetColumnFor(mozilla::dom::Element* aElement);
+
+ nsTreeColumn* IndexedGetter(uint32_t aIndex, bool& aFound);
+ nsTreeColumn* GetColumnAt(uint32_t aIndex);
+ nsTreeColumn* NamedGetter(const nsAString& aId, bool& aFound);
+ nsTreeColumn* GetNamedColumn(const nsAString& aId);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ // Uses XPCOM InvalidateColumns().
+ // Uses XPCOM RestoreNaturalOrder().
+
+ friend class nsTreeBodyFrame;
+protected:
+ void SetTree(nsTreeBodyFrame* aTree) { mTree = aTree; }
+
+ // Builds our cache of column info.
+ void EnsureColumns();
+
+private:
+ nsTreeBodyFrame* mTree;
+
+ /**
+ * The first column in the list of columns. All of the columns are supposed
+ * to be "alive", i.e. have a frame. This is achieved by clearing the columns
+ * list each time an nsTreeColFrame is destroyed.
+ *
+ * XXX this means that new nsTreeColumn objects are unnecessarily created
+ * for untouched columns.
+ */
+ RefPtr<nsTreeColumn> mFirstColumn;
+};
+
+#endif // nsTreeColumns_h__
diff --git a/layout/xul/tree/nsTreeContentView.cpp b/layout/xul/tree/nsTreeContentView.cpp
new file mode 100644
index 000000000..66cc0072f
--- /dev/null
+++ b/layout/xul/tree/nsTreeContentView.cpp
@@ -0,0 +1,1372 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIBoxObject.h"
+#include "nsTreeUtils.h"
+#include "nsTreeContentView.h"
+#include "ChildIterator.h"
+#include "nsDOMClassInfoID.h"
+#include "nsError.h"
+#include "nsIXULSortService.h"
+#include "nsContentUtils.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/Element.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIDocument.h"
+
+using namespace mozilla;
+
+#define NS_ENSURE_NATIVE_COLUMN(_col) \
+ RefPtr<nsTreeColumn> col = nsTreeBodyFrame::GetColumnImpl(_col); \
+ if (!col) { \
+ return NS_ERROR_INVALID_ARG; \
+ }
+
+// A content model view implementation for the tree.
+
+#define ROW_FLAG_CONTAINER 0x01
+#define ROW_FLAG_OPEN 0x02
+#define ROW_FLAG_EMPTY 0x04
+#define ROW_FLAG_SEPARATOR 0x08
+
+class Row
+{
+ public:
+ Row(nsIContent* aContent, int32_t aParentIndex)
+ : mContent(aContent), mParentIndex(aParentIndex),
+ mSubtreeSize(0), mFlags(0) {
+ }
+
+ ~Row() {
+ }
+
+ void SetContainer(bool aContainer) {
+ aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER;
+ }
+ bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; }
+
+ void SetOpen(bool aOpen) {
+ aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN;
+ }
+ bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); }
+
+ void SetEmpty(bool aEmpty) {
+ aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY;
+ }
+ bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); }
+
+ void SetSeparator(bool aSeparator) {
+ aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR;
+ }
+ bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); }
+
+ // Weak reference to a content item.
+ nsIContent* mContent;
+
+ // The parent index of the item, set to -1 for the top level items.
+ int32_t mParentIndex;
+
+ // Subtree size for this item.
+ int32_t mSubtreeSize;
+
+ private:
+ // State flags
+ int8_t mFlags;
+};
+
+
+// We don't reference count the reference to the document
+// If the document goes away first, we'll be informed and we
+// can drop our reference.
+// If we go away first, we'll get rid of ourselves from the
+// document's observer list.
+
+nsTreeContentView::nsTreeContentView(void) :
+ mBoxObject(nullptr),
+ mSelection(nullptr),
+ mRoot(nullptr),
+ mDocument(nullptr)
+{
+}
+
+nsTreeContentView::~nsTreeContentView(void)
+{
+ // Remove ourselves from mDocument's observers.
+ if (mDocument)
+ mDocument->RemoveObserver(this);
+}
+
+nsresult
+NS_NewTreeContentView(nsITreeView** aResult)
+{
+ *aResult = new nsTreeContentView;
+ if (! *aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsTreeContentView,
+ mBoxObject,
+ mSelection,
+ mRoot,
+ mBody)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+ NS_INTERFACE_MAP_ENTRY(nsITreeContentView)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeContentView)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsTreeContentView::GetRowCount(int32_t* aRowCount)
+{
+ *aRowCount = mRows.Length();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetSelection(nsITreeSelection** aSelection)
+{
+ NS_IF_ADDREF(*aSelection = mSelection);
+
+ return NS_OK;
+}
+
+bool
+nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue)
+{
+ // Untrusted content is only allowed to specify known-good views
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode())
+ return true;
+ nsCOMPtr<nsINativeTreeSelection> nativeTreeSel = do_QueryInterface(aValue);
+ return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative());
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetSelection(nsITreeSelection* aSelection)
+{
+ NS_ENSURE_TRUE(!aSelection || CanTrustTreeSelection(aSelection),
+ NS_ERROR_DOM_SECURITY_ERR);
+
+ mSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aIndex].get();
+ nsIContent* realRow;
+ if (row->IsSeparator())
+ realRow = row->mContent;
+ else
+ realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+
+ if (realRow) {
+ realRow->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aProps)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell) {
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ nsCOMPtr<nsIDOMElement> element;
+ aCol->GetElement(getter_AddRefs(element));
+
+ element->GetAttribute(NS_LITERAL_STRING("properties"), aProps);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = mRows[aIndex]->IsContainer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = mRows[aIndex]->IsOpen();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = mRows[aIndex]->IsEmpty();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsSeparator(int32_t aIndex, bool *_retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = mRows[aIndex]->IsSeparator();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsSorted(bool *_retval)
+{
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation,
+ nsIDOMDataTransfer* aDataTransfer, bool *_retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer)
+{
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval)
+{
+ NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()),
+ "bad row index");
+ if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = mRows[aRowIndex]->mParentIndex;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* _retval)
+{
+ NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()),
+ "bad row index");
+ if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ // We have a next sibling if the row is not the last in the subtree.
+ int32_t parentIndex = mRows[aRowIndex]->mParentIndex;
+ if (parentIndex >= 0) {
+ // Compute the last index in this subtree.
+ int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize;
+ Row* row = mRows[lastIndex].get();
+ while (row->mParentIndex != parentIndex) {
+ lastIndex = row->mParentIndex;
+ row = mRows[lastIndex].get();
+ }
+
+ *_retval = aRowIndex < lastIndex;
+ }
+ else {
+ *_retval = uint32_t(aRowIndex) < mRows.Length() - 1;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ int32_t level = 0;
+ Row* row = mRows[aIndex].get();
+ while (row->mParentIndex >= 0) {
+ level++;
+ row = mRows[row->mParentIndex].get();
+ }
+ *_retval = level;
+
+ return NS_OK;
+}
+
+ NS_IMETHODIMP
+nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval)
+{
+ _retval.Truncate();
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell)
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, _retval);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = nsITreeView::PROGRESS_NONE;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::normal, &nsGkAtoms::undetermined, nullptr};
+ switch (cell->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::mode,
+ strings, eCaseMatters)) {
+ case 0: *_retval = nsITreeView::PROGRESS_NORMAL; break;
+ case 1: *_retval = nsITreeView::PROGRESS_UNDETERMINED; break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval)
+{
+ _retval.Truncate();
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell)
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, _retval);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval)
+{
+ _retval.Truncate();
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ NS_PRECONDITION(aCol, "bad column");
+
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()) || !aCol)
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+
+ // Check for a "label" attribute - this is valid on an <treeitem>
+ // with a single implied column.
+ if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval)
+ && !_retval.IsEmpty())
+ return NS_OK;
+
+ if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) {
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell)
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetTree(nsITreeBoxObject* aTree)
+{
+ ClearRows();
+
+ mBoxObject = aTree;
+
+ MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows");
+
+ if (aTree) {
+ // Get our root element
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject);
+ if (!boxObject) {
+ mBoxObject = nullptr;
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIDOMElement> element;
+ boxObject->GetElement(getter_AddRefs(element));
+
+ mRoot = do_QueryInterface(element);
+ NS_ENSURE_STATE(mRoot);
+
+ // Add ourselves to document's observers.
+ nsIDocument* document = mRoot->GetComposedDoc();
+ if (document) {
+ document->AddObserver(this);
+ mDocument = document;
+ }
+
+ nsCOMPtr<nsIDOMElement> bodyElement;
+ mBoxObject->GetTreeBody(getter_AddRefs(bodyElement));
+ if (bodyElement) {
+ mBody = do_QueryInterface(bodyElement);
+ int32_t index = 0;
+ Serialize(mBody, -1, &index, mRows);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::ToggleOpenState(int32_t aIndex)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ // We don't serialize content right here, since content might be generated
+ // lazily.
+ Row* row = mRows[aIndex].get();
+
+ if (row->IsOpen())
+ row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("false"), true);
+ else
+ row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::CycleHeader(nsITreeColumn* aCol)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+
+ if (!mRoot)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> element;
+ aCol->GetElement(getter_AddRefs(element));
+ if (element) {
+ nsCOMPtr<nsIContent> column = do_QueryInterface(element);
+ nsAutoString sort;
+ column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+ if (!sort.IsEmpty()) {
+ nsCOMPtr<nsIXULSortService> xs = do_GetService("@mozilla.org/xul/xul-sort-service;1");
+ if (xs) {
+ nsAutoString sortdirection;
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr};
+ switch (column->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::sortDirection,
+ strings, eCaseMatters)) {
+ case 0: sortdirection.AssignLiteral("descending"); break;
+ case 1: sortdirection.AssignLiteral("natural"); break;
+ default: sortdirection.AssignLiteral("ascending"); break;
+ }
+
+ nsAutoString hints;
+ column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
+ sortdirection.Append(' ');
+ sortdirection += hints;
+
+ nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
+ xs->Sort(rootnode, sort, sortdirection);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SelectionChanged()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval)
+{
+ *_retval = false;
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = true;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_false, eCaseMatters)) {
+ *_retval = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = true;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable,
+ nsGkAtoms::_false, eCaseMatters)) {
+ *_retval = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell)
+ cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue)
+{
+ NS_ENSURE_NATIVE_COLUMN(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row");
+ if (aRow < 0 || aRow >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ nsIContent* cell = GetCell(realRow, aCol);
+ if (cell)
+ cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::PerformAction(const char16_t* aAction)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index");
+ if (aIndex < 0 || aIndex >= int32_t(mRows.Length()))
+ return NS_ERROR_INVALID_ARG;
+
+ Row* row = mRows[aIndex].get();
+ row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aItem);
+ *_retval = FindContent(content);
+
+ return NS_OK;
+}
+
+void
+nsTreeContentView::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ // Lots of codepaths under here that do all sorts of stuff, so be safe.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ // Make sure this notification concerns us.
+ // First check the tag to see if it's one that we care about.
+
+ if (mBoxObject && (aElement == mRoot || aElement == mBody)) {
+ mBoxObject->ClearStyleAndImageCaches();
+ mBoxObject->Invalidate();
+ }
+
+ // We don't consider non-XUL nodes.
+ nsIContent* parent = nullptr;
+ if (!aElement->IsXULElement() ||
+ ((parent = aElement->GetParent()) && !parent->IsXULElement())) {
+ return;
+ }
+ if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol,
+ nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator,
+ nsGkAtoms::treerow,
+ nsGkAtoms::treecell)) {
+ return;
+ }
+
+ // If we have a legal tag, go up to the tree/select and make sure
+ // that it's ours.
+
+ for (nsIContent* element = aElement; element != mBody; element = element->GetParent()) {
+ if (!element)
+ return; // this is not for us
+ if (element->IsXULElement(nsGkAtoms::tree))
+ return; // this is not for us
+ }
+
+ // Handle changes of the hidden attribute.
+ if (aAttribute == nsGkAtoms::hidden &&
+ aElement->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator)) {
+ bool hidden = aElement->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters);
+
+ int32_t index = FindContent(aElement);
+ if (hidden && index >= 0) {
+ // Hide this row along with its children.
+ int32_t count = RemoveRow(index);
+ if (mBoxObject)
+ mBoxObject->RowCountChanged(index, -count);
+ }
+ else if (!hidden && index < 0) {
+ // Show this row along with its children.
+ nsCOMPtr<nsIContent> parent = aElement->GetParent();
+ if (parent) {
+ InsertRowFor(parent, aElement);
+ }
+ }
+
+ return;
+ }
+
+ if (aElement->IsXULElement(nsGkAtoms::treecol)) {
+ if (aAttribute == nsGkAtoms::properties) {
+ if (mBoxObject) {
+ nsCOMPtr<nsITreeColumns> cols;
+ mBoxObject->GetColumns(getter_AddRefs(cols));
+ if (cols) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aElement);
+ nsCOMPtr<nsITreeColumn> col;
+ cols->GetColumnFor(element, getter_AddRefs(col));
+ mBoxObject->InvalidateColumn(col);
+ }
+ }
+ }
+ }
+ else if (aElement->IsXULElement(nsGkAtoms::treeitem)) {
+ int32_t index = FindContent(aElement);
+ if (index >= 0) {
+ Row* row = mRows[index].get();
+ if (aAttribute == nsGkAtoms::container) {
+ bool isContainer =
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters);
+ row->SetContainer(isContainer);
+ if (mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ else if (aAttribute == nsGkAtoms::open) {
+ bool isOpen =
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters);
+ bool wasOpen = row->IsOpen();
+ if (! isOpen && wasOpen)
+ CloseContainer(index);
+ else if (isOpen && ! wasOpen)
+ OpenContainer(index);
+ }
+ else if (aAttribute == nsGkAtoms::empty) {
+ bool isEmpty =
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
+ nsGkAtoms::_true, eCaseMatters);
+ row->SetEmpty(isEmpty);
+ if (mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+ }
+ else if (aElement->IsXULElement(nsGkAtoms::treeseparator)) {
+ int32_t index = FindContent(aElement);
+ if (index >= 0) {
+ if (aAttribute == nsGkAtoms::properties && mBoxObject) {
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+ }
+ else if (aElement->IsXULElement(nsGkAtoms::treerow)) {
+ if (aAttribute == nsGkAtoms::properties) {
+ nsCOMPtr<nsIContent> parent = aElement->GetParent();
+ if (parent) {
+ int32_t index = FindContent(parent);
+ if (index >= 0 && mBoxObject) {
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+ }
+ }
+ else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
+ if (aAttribute == nsGkAtoms::ref ||
+ aAttribute == nsGkAtoms::properties ||
+ aAttribute == nsGkAtoms::mode ||
+ aAttribute == nsGkAtoms::src ||
+ aAttribute == nsGkAtoms::value ||
+ aAttribute == nsGkAtoms::label) {
+ nsIContent* parent = aElement->GetParent();
+ if (parent) {
+ nsCOMPtr<nsIContent> grandParent = parent->GetParent();
+ if (grandParent && grandParent->IsXULElement()) {
+ int32_t index = FindContent(grandParent);
+ if (index >= 0 && mBoxObject) {
+ // XXX Should we make an effort to invalidate only cell ?
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+nsTreeContentView::ContentAppended(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ // Our contentinserted doesn't use the index
+ ContentInserted(aDocument, aContainer, cur, 0);
+ }
+}
+
+void
+nsTreeContentView::ContentInserted(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t /* unused */)
+{
+ NS_ASSERTION(aChild, "null ptr");
+
+ // Make sure this notification concerns us.
+ // First check the tag to see if it's one that we care about.
+
+ // Don't allow non-XUL nodes.
+ if (!aChild->IsXULElement() || !aContainer->IsXULElement())
+ return;
+
+ if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator,
+ nsGkAtoms::treechildren,
+ nsGkAtoms::treerow,
+ nsGkAtoms::treecell)) {
+ return;
+ }
+
+ // If we have a legal tag, go up to the tree/select and make sure
+ // that it's ours.
+
+ for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) {
+ if (!element)
+ return; // this is not for us
+ if (element->IsXULElement(nsGkAtoms::tree))
+ return; // this is not for us
+ }
+
+ // Lots of codepaths under here that do all sorts of stuff, so be safe.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
+ int32_t index = FindContent(aContainer);
+ if (index >= 0) {
+ Row* row = mRows[index].get();
+ row->SetEmpty(false);
+ if (mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ if (row->IsContainer() && row->IsOpen()) {
+ int32_t count = EnsureSubtree(index);
+ if (mBoxObject)
+ mBoxObject->RowCountChanged(index + 1, count);
+ }
+ }
+ }
+ else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator)) {
+ InsertRowFor(aContainer, aChild);
+ }
+ else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
+ int32_t index = FindContent(aContainer);
+ if (index >= 0 && mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ int32_t index = FindContent(parent);
+ if (index >= 0 && mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+}
+
+void
+nsTreeContentView::ContentRemoved(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ NS_ASSERTION(aChild, "null ptr");
+
+ // Make sure this notification concerns us.
+ // First check the tag to see if it's one that we care about.
+
+ // We don't consider non-XUL nodes.
+ if (!aChild->IsXULElement() || !aContainer->IsXULElement())
+ return;
+
+ if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator,
+ nsGkAtoms::treechildren,
+ nsGkAtoms::treerow,
+ nsGkAtoms::treecell)) {
+ return;
+ }
+
+ // If we have a legal tag, go up to the tree/select and make sure
+ // that it's ours.
+
+ for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) {
+ if (!element)
+ return; // this is not for us
+ if (element->IsXULElement(nsGkAtoms::tree))
+ return; // this is not for us
+ }
+
+ // Lots of codepaths under here that do all sorts of stuff, so be safe.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
+ int32_t index = FindContent(aContainer);
+ if (index >= 0) {
+ Row* row = mRows[index].get();
+ row->SetEmpty(true);
+ int32_t count = RemoveSubtree(index);
+ // Invalidate also the row to update twisty.
+ if (mBoxObject) {
+ mBoxObject->InvalidateRow(index);
+ mBoxObject->RowCountChanged(index + 1, -count);
+ }
+ }
+ }
+ else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator)) {
+ int32_t index = FindContent(aChild);
+ if (index >= 0) {
+ int32_t count = RemoveRow(index);
+ if (mBoxObject)
+ mBoxObject->RowCountChanged(index, -count);
+ }
+ }
+ else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
+ int32_t index = FindContent(aContainer);
+ if (index >= 0 && mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ int32_t index = FindContent(parent);
+ if (index >= 0 && mBoxObject)
+ mBoxObject->InvalidateRow(index);
+ }
+ }
+}
+
+void
+nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows?
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ ClearRows();
+}
+
+
+// Recursively serialize content, starting with aContent.
+void
+nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<UniquePtr<Row>>& aRows)
+{
+ // Don't allow non-XUL nodes.
+ if (!aContent->IsXULElement())
+ return;
+
+ dom::FlattenedChildIterator iter(aContent);
+ for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) {
+ int32_t count = aRows.Length();
+
+ if (content->IsXULElement(nsGkAtoms::treeitem)) {
+ SerializeItem(content, aParentIndex, aIndex, aRows);
+ } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
+ SerializeSeparator(content, aParentIndex, aIndex, aRows);
+ }
+
+ *aIndex += aRows.Length() - count;
+ }
+}
+
+void
+nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<UniquePtr<Row>>& aRows)
+{
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters))
+ return;
+
+ aRows.AppendElement(MakeUnique<Row>(aContent, aParentIndex));
+ Row* row = aRows.LastElement().get();
+
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters)) {
+ row->SetContainer(true);
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters)) {
+ row->SetOpen(true);
+ nsIContent* child =
+ nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren);
+ if (child && child->IsXULElement()) {
+ // Now, recursively serialize our child.
+ int32_t count = aRows.Length();
+ int32_t index = 0;
+ Serialize(child, aParentIndex + *aIndex + 1, &index, aRows);
+ row->mSubtreeSize += aRows.Length() - count;
+ }
+ else
+ row->SetEmpty(true);
+ } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
+ nsGkAtoms::_true, eCaseMatters)) {
+ row->SetEmpty(true);
+ }
+ }
+}
+
+void
+nsTreeContentView::SerializeSeparator(nsIContent* aContent,
+ int32_t aParentIndex, int32_t* aIndex,
+ nsTArray<UniquePtr<Row>>& aRows)
+{
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters))
+ return;
+
+ auto row = MakeUnique<Row>(aContent, aParentIndex);
+ row->SetSeparator(true);
+ aRows.AppendElement(Move(row));
+}
+
+void
+nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer,
+ nsIContent* aContent, int32_t* aIndex)
+{
+ uint32_t childCount = aContainer->GetChildCount();
+
+ if (!aContainer->IsXULElement())
+ return;
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsIContent *content = aContainer->GetChildAt(i);
+
+ if (content == aContent)
+ break;
+
+ if (content->IsXULElement(nsGkAtoms::treeitem)) {
+ if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters)) {
+ (*aIndex)++;
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters) &&
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsIContent* child =
+ nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren);
+ if (child && child->IsXULElement())
+ GetIndexInSubtree(child, aContent, aIndex);
+ }
+ }
+ }
+ else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
+ if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters))
+ (*aIndex)++;
+ }
+ }
+}
+
+int32_t
+nsTreeContentView::EnsureSubtree(int32_t aIndex)
+{
+ Row* row = mRows[aIndex].get();
+
+ nsIContent* child;
+ child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren);
+ if (!child || !child->IsXULElement()) {
+ return 0;
+ }
+
+ AutoTArray<UniquePtr<Row>, 8> rows;
+ int32_t index = 0;
+ Serialize(child, aIndex, &index, rows);
+ // Insert |rows| into |mRows| at position |aIndex|, by first creating empty
+ // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note
+ // that we can't simply use InsertElementsAt with an array argument, since
+ // the destination can't steal ownership from its const source argument.)
+ UniquePtr<Row>* newRows = mRows.InsertElementsAt(aIndex + 1,
+ rows.Length());
+ for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) {
+ newRows[i] = Move(rows[i]);
+ }
+ int32_t count = rows.Length();
+
+ row->mSubtreeSize += count;
+ UpdateSubtreeSizes(row->mParentIndex, count);
+
+ // Update parent indexes, but skip newly added rows.
+ // They already have correct values.
+ UpdateParentIndexes(aIndex, count + 1, count);
+
+ return count;
+}
+
+int32_t
+nsTreeContentView::RemoveSubtree(int32_t aIndex)
+{
+ Row* row = mRows[aIndex].get();
+ int32_t count = row->mSubtreeSize;
+
+ mRows.RemoveElementsAt(aIndex + 1, count);
+
+ row->mSubtreeSize -= count;
+ UpdateSubtreeSizes(row->mParentIndex, -count);
+
+ UpdateParentIndexes(aIndex, 0, -count);
+
+ return count;
+}
+
+void
+nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild)
+{
+ int32_t grandParentIndex = -1;
+ bool insertRow = false;
+
+ nsCOMPtr<nsIContent> grandParent = aParent->GetParent();
+
+ if (grandParent->IsXULElement(nsGkAtoms::tree)) {
+ // Allow insertion to the outermost container.
+ insertRow = true;
+ }
+ else {
+ // Test insertion to an inner container.
+
+ // First try to find this parent in our array of rows, if we find one
+ // we can be sure that all other parents are open too.
+ grandParentIndex = FindContent(grandParent);
+ if (grandParentIndex >= 0) {
+ // Got it, now test if it is open.
+ if (mRows[grandParentIndex]->IsOpen())
+ insertRow = true;
+ }
+ }
+
+ if (insertRow) {
+ int32_t index = 0;
+ GetIndexInSubtree(aParent, aChild, &index);
+
+ int32_t count = InsertRow(grandParentIndex, index, aChild);
+ if (mBoxObject)
+ mBoxObject->RowCountChanged(grandParentIndex + index + 1, count);
+ }
+}
+
+int32_t
+nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent)
+{
+ AutoTArray<UniquePtr<Row>, 8> rows;
+ if (aContent->IsXULElement(nsGkAtoms::treeitem)) {
+ SerializeItem(aContent, aParentIndex, &aIndex, rows);
+ } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) {
+ SerializeSeparator(aContent, aParentIndex, &aIndex, rows);
+ }
+
+ // We can't use InsertElementsAt since the destination can't steal
+ // ownership from its const source argument.
+ int32_t count = rows.Length();
+ for (nsTArray<Row>::index_type i = 0; i < size_t(count); i++) {
+ mRows.InsertElementAt(aParentIndex + aIndex + i + 1, Move(rows[i]));
+ }
+
+ UpdateSubtreeSizes(aParentIndex, count);
+
+ // Update parent indexes, but skip added rows.
+ // They already have correct values.
+ UpdateParentIndexes(aParentIndex + aIndex, count + 1, count);
+
+ return count;
+}
+
+int32_t
+nsTreeContentView::RemoveRow(int32_t aIndex)
+{
+ Row* row = mRows[aIndex].get();
+ int32_t count = row->mSubtreeSize + 1;
+ int32_t parentIndex = row->mParentIndex;
+
+ mRows.RemoveElementsAt(aIndex, count);
+
+ UpdateSubtreeSizes(parentIndex, -count);
+
+ UpdateParentIndexes(aIndex, 0, -count);
+
+ return count;
+}
+
+void
+nsTreeContentView::ClearRows()
+{
+ mRows.Clear();
+ mRoot = nullptr;
+ mBody = nullptr;
+ // Remove ourselves from mDocument's observers.
+ if (mDocument) {
+ mDocument->RemoveObserver(this);
+ mDocument = nullptr;
+ }
+}
+
+void
+nsTreeContentView::OpenContainer(int32_t aIndex)
+{
+ Row* row = mRows[aIndex].get();
+ row->SetOpen(true);
+
+ int32_t count = EnsureSubtree(aIndex);
+ if (mBoxObject) {
+ mBoxObject->InvalidateRow(aIndex);
+ mBoxObject->RowCountChanged(aIndex + 1, count);
+ }
+}
+
+void
+nsTreeContentView::CloseContainer(int32_t aIndex)
+{
+ Row* row = mRows[aIndex].get();
+ row->SetOpen(false);
+
+ int32_t count = RemoveSubtree(aIndex);
+ if (mBoxObject) {
+ mBoxObject->InvalidateRow(aIndex);
+ mBoxObject->RowCountChanged(aIndex + 1, -count);
+ }
+}
+
+int32_t
+nsTreeContentView::FindContent(nsIContent* aContent)
+{
+ for (uint32_t i = 0; i < mRows.Length(); i++) {
+ if (mRows[i]->mContent == aContent) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void
+nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex, int32_t count)
+{
+ while (aParentIndex >= 0) {
+ Row* row = mRows[aParentIndex].get();
+ row->mSubtreeSize += count;
+ aParentIndex = row->mParentIndex;
+ }
+}
+
+void
+nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount)
+{
+ int32_t count = mRows.Length();
+ for (int32_t i = aIndex + aSkip; i < count; i++) {
+ Row* row = mRows[i].get();
+ if (row->mParentIndex > aIndex) {
+ row->mParentIndex += aCount;
+ }
+ }
+}
+
+nsIContent*
+nsTreeContentView::GetCell(nsIContent* aContainer, nsITreeColumn* aCol)
+{
+ nsCOMPtr<nsIAtom> colAtom;
+ int32_t colIndex;
+ aCol->GetAtom(getter_AddRefs(colAtom));
+ aCol->GetIndex(&colIndex);
+
+ // Traverse through cells, try to find the cell by "ref" attribute or by cell
+ // index in a row. "ref" attribute has higher priority.
+ nsIContent* result = nullptr;
+ int32_t j = 0;
+ dom::FlattenedChildIterator iter(aContainer);
+ for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) {
+ if (cell->IsXULElement(nsGkAtoms::treecell)) {
+ if (colAtom && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref,
+ colAtom, eCaseMatters)) {
+ result = cell;
+ break;
+ }
+ else if (j == colIndex) {
+ result = cell;
+ }
+ j++;
+ }
+ }
+
+ return result;
+}
diff --git a/layout/xul/tree/nsTreeContentView.h b/layout/xul/tree/nsTreeContentView.h
new file mode 100644
index 000000000..c2c3f5030
--- /dev/null
+++ b/layout/xul/tree/nsTreeContentView.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeContentView_h__
+#define nsTreeContentView_h__
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsStubDocumentObserver.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#include "nsITreeView.h"
+#include "nsITreeContentView.h"
+#include "nsITreeSelection.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIDocument;
+class Row;
+
+nsresult NS_NewTreeContentView(nsITreeView** aResult);
+
+class nsTreeContentView final : public nsINativeTreeView,
+ public nsITreeContentView,
+ public nsStubDocumentObserver
+{
+ public:
+ nsTreeContentView(void);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTreeContentView,
+ nsINativeTreeView)
+
+ NS_DECL_NSITREEVIEW
+ // nsINativeTreeView: Untrusted code can use us
+ NS_IMETHOD EnsureNative() override { return NS_OK; }
+
+ NS_DECL_NSITREECONTENTVIEW
+
+ // nsIDocumentObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ static bool CanTrustTreeSelection(nsISupports* aValue);
+
+ protected:
+ ~nsTreeContentView(void);
+
+ // Recursive methods which deal with serializing of nested content.
+ void Serialize(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex,
+ nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void SerializeItem(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void SerializeSeparator(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void GetIndexInSubtree(nsIContent* aContainer, nsIContent* aContent, int32_t* aResult);
+
+ // Helper methods which we use to manage our plain array of rows.
+ int32_t EnsureSubtree(int32_t aIndex);
+
+ int32_t RemoveSubtree(int32_t aIndex);
+
+ int32_t InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent);
+
+ void InsertRowFor(nsIContent* aParent, nsIContent* aChild);
+
+ int32_t RemoveRow(int32_t aIndex);
+
+ void ClearRows();
+
+ void OpenContainer(int32_t aIndex);
+
+ void CloseContainer(int32_t aIndex);
+
+ int32_t FindContent(nsIContent* aContent);
+
+ void UpdateSubtreeSizes(int32_t aIndex, int32_t aCount);
+
+ void UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount);
+
+ // Content helpers.
+ nsIContent* GetCell(nsIContent* aContainer, nsITreeColumn* aCol);
+
+ private:
+ nsCOMPtr<nsITreeBoxObject> mBoxObject;
+ nsCOMPtr<nsITreeSelection> mSelection;
+ nsCOMPtr<nsIContent> mRoot;
+ nsCOMPtr<nsIContent> mBody;
+ nsIDocument* mDocument; // WEAK
+ nsTArray<mozilla::UniquePtr<Row>> mRows;
+};
+
+#endif // nsTreeContentView_h__
diff --git a/layout/xul/tree/nsTreeImageListener.cpp b/layout/xul/tree/nsTreeImageListener.cpp
new file mode 100644
index 000000000..f559be042
--- /dev/null
+++ b/layout/xul/tree/nsTreeImageListener.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTreeImageListener.h"
+#include "nsITreeBoxObject.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsIContent.h"
+
+NS_IMPL_ISUPPORTS(nsTreeImageListener, imgINotificationObserver)
+
+nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
+ : mTreeFrame(aTreeFrame),
+ mInvalidationSuppressed(true),
+ mInvalidationArea(nullptr)
+{
+}
+
+nsTreeImageListener::~nsTreeImageListener()
+{
+ delete mInvalidationArea;
+}
+
+NS_IMETHODIMP
+nsTreeImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return mTreeFrame ? mTreeFrame->OnImageIsAnimated(aRequest) : NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Ensure the animation (if any) is started. Note: There is no
+ // corresponding call to Decrement for this. This Increment will be
+ // 'cleaned up' by the Request when it is destroyed, but only then.
+ aRequest->IncrementAnimationConsumers();
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ Invalidate();
+ }
+
+ return NS_OK;
+}
+
+void
+nsTreeImageListener::AddCell(int32_t aIndex, nsITreeColumn* aCol)
+{
+ if (!mInvalidationArea) {
+ mInvalidationArea = new InvalidationArea(aCol);
+ mInvalidationArea->AddRow(aIndex);
+ }
+ else {
+ InvalidationArea* currArea;
+ for (currArea = mInvalidationArea; currArea; currArea = currArea->GetNext()) {
+ if (currArea->GetCol() == aCol) {
+ currArea->AddRow(aIndex);
+ break;
+ }
+ }
+ if (!currArea) {
+ currArea = new InvalidationArea(aCol);
+ currArea->SetNext(mInvalidationArea);
+ mInvalidationArea = currArea;
+ mInvalidationArea->AddRow(aIndex);
+ }
+ }
+}
+
+
+void
+nsTreeImageListener::Invalidate()
+{
+ if (!mInvalidationSuppressed) {
+ for (InvalidationArea* currArea = mInvalidationArea; currArea;
+ currArea = currArea->GetNext()) {
+ // Loop from min to max, invalidating each cell that was listening for this image.
+ for (int32_t i = currArea->GetMin(); i <= currArea->GetMax(); ++i) {
+ if (mTreeFrame) {
+ nsITreeBoxObject* tree = mTreeFrame->GetTreeBoxObject();
+ if (tree) {
+ tree->InvalidateCell(i, currArea->GetCol());
+ }
+ }
+ }
+ }
+ }
+}
+
+nsTreeImageListener::InvalidationArea::InvalidationArea(nsITreeColumn* aCol)
+ : mCol(aCol),
+ mMin(-1), // min should start out "undefined"
+ mMax(0),
+ mNext(nullptr)
+{
+}
+
+void
+nsTreeImageListener::InvalidationArea::AddRow(int32_t aIndex)
+{
+ if (mMin == -1)
+ mMin = mMax = aIndex;
+ else if (aIndex < mMin)
+ mMin = aIndex;
+ else if (aIndex > mMax)
+ mMax = aIndex;
+}
+
+NS_IMETHODIMP
+nsTreeImageListener::ClearFrame()
+{
+ mTreeFrame = nullptr;
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeImageListener.h b/layout/xul/tree/nsTreeImageListener.h
new file mode 100644
index 000000000..573b246e5
--- /dev/null
+++ b/layout/xul/tree/nsTreeImageListener.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeImageListener_h__
+#define nsTreeImageListener_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsITreeColumns.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/Attributes.h"
+
+// This class handles image load observation.
+class nsTreeImageListener final : public imgINotificationObserver
+{
+public:
+ explicit nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ NS_IMETHOD ClearFrame();
+
+ friend class nsTreeBodyFrame;
+
+protected:
+ ~nsTreeImageListener();
+
+ void UnsuppressInvalidation() { mInvalidationSuppressed = false; }
+ void Invalidate();
+ void AddCell(int32_t aIndex, nsITreeColumn* aCol);
+
+private:
+ nsTreeBodyFrame* mTreeFrame;
+
+ // A guard that prevents us from recursive painting.
+ bool mInvalidationSuppressed;
+
+ class InvalidationArea {
+ public:
+ explicit InvalidationArea(nsITreeColumn* aCol);
+ ~InvalidationArea() { delete mNext; }
+
+ friend class nsTreeImageListener;
+
+ protected:
+ void AddRow(int32_t aIndex);
+ nsITreeColumn* GetCol() { return mCol.get(); }
+ int32_t GetMin() { return mMin; }
+ int32_t GetMax() { return mMax; }
+ InvalidationArea* GetNext() { return mNext; }
+ void SetNext(InvalidationArea* aNext) { mNext = aNext; }
+
+ private:
+ nsCOMPtr<nsITreeColumn> mCol;
+ int32_t mMin;
+ int32_t mMax;
+ InvalidationArea* mNext;
+ };
+
+ InvalidationArea* mInvalidationArea;
+};
+
+#endif // nsTreeImageListener_h__
diff --git a/layout/xul/tree/nsTreeSelection.cpp b/layout/xul/tree/nsTreeSelection.cpp
new file mode 100644
index 000000000..1d251a096
--- /dev/null
+++ b/layout/xul/tree/nsTreeSelection.cpp
@@ -0,0 +1,866 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "nsCOMPtr.h"
+#include "nsTreeSelection.h"
+#include "nsIBoxObject.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeView.h"
+#include "nsString.h"
+#include "nsIDOMElement.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+
+// A helper class for managing our ranges of selection.
+struct nsTreeRange
+{
+ nsTreeSelection* mSelection;
+
+ nsTreeRange* mPrev;
+ nsTreeRange* mNext;
+
+ int32_t mMin;
+ int32_t mMax;
+
+ nsTreeRange(nsTreeSelection* aSel, int32_t aSingleVal)
+ :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aSingleVal), mMax(aSingleVal) {}
+ nsTreeRange(nsTreeSelection* aSel, int32_t aMin, int32_t aMax)
+ :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aMin), mMax(aMax) {}
+
+ ~nsTreeRange() { delete mNext; }
+
+ void Connect(nsTreeRange* aPrev = nullptr, nsTreeRange* aNext = nullptr) {
+ if (aPrev)
+ aPrev->mNext = this;
+ else
+ mSelection->mFirstRange = this;
+
+ if (aNext)
+ aNext->mPrev = this;
+
+ mPrev = aPrev;
+ mNext = aNext;
+ }
+
+ nsresult RemoveRange(int32_t aStart, int32_t aEnd) {
+ // This should so be a loop... sigh...
+ // We start past the range to remove, so no more to remove
+ if (aEnd < mMin)
+ return NS_OK;
+ // We are the last range to be affected
+ if (aEnd < mMax) {
+ if (aStart <= mMin) {
+ // Just chop the start of the range off
+ mMin = aEnd + 1;
+ } else {
+ // We need to split the range
+ nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax);
+ if (!range)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mMax = aStart - 1;
+ range->Connect(this, mNext);
+ }
+ return NS_OK;
+ }
+ nsTreeRange* next = mNext;
+ if (aStart <= mMin) {
+ // The remove includes us, remove ourselves from the list
+ if (mPrev)
+ mPrev->mNext = next;
+ else
+ mSelection->mFirstRange = next;
+
+ if (next)
+ next->mPrev = mPrev;
+ mPrev = mNext = nullptr;
+ delete this;
+ } else if (aStart <= mMax) {
+ // Just chop the end of the range off
+ mMax = aStart - 1;
+ }
+ return next ? next->RemoveRange(aStart, aEnd) : NS_OK;
+ }
+
+ nsresult Remove(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax) {
+ // We have found the range that contains us.
+ if (mMin == mMax) {
+ // Delete the whole range.
+ if (mPrev)
+ mPrev->mNext = mNext;
+ if (mNext)
+ mNext->mPrev = mPrev;
+ nsTreeRange* first = mSelection->mFirstRange;
+ if (first == this)
+ mSelection->mFirstRange = mNext;
+ mNext = mPrev = nullptr;
+ delete this;
+ }
+ else if (aIndex == mMin)
+ mMin++;
+ else if (aIndex == mMax)
+ mMax--;
+ else {
+ // We have to break this range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(this, mNext);
+ mMax = aIndex - 1;
+ }
+ }
+ else if (mNext)
+ return mNext->Remove(aIndex);
+
+ return NS_OK;
+ }
+
+ nsresult Add(int32_t aIndex) {
+ if (aIndex < mMin) {
+ // We have found a spot to insert.
+ if (aIndex + 1 == mMin)
+ mMin = aIndex;
+ else if (mPrev && mPrev->mMax+1 == aIndex)
+ mPrev->mMax = aIndex;
+ else {
+ // We have to create a new range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(mPrev, this);
+ }
+ }
+ else if (mNext)
+ mNext->Add(aIndex);
+ else {
+ // Insert on to the end.
+ if (mMax+1 == aIndex)
+ mMax = aIndex;
+ else {
+ // We have to create a new range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(this, nullptr);
+ }
+ }
+ return NS_OK;
+ }
+
+ bool Contains(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax)
+ return true;
+
+ if (mNext)
+ return mNext->Contains(aIndex);
+
+ return false;
+ }
+
+ int32_t Count() {
+ int32_t total = mMax - mMin + 1;
+ if (mNext)
+ total += mNext->Count();
+ return total;
+ }
+
+ static void CollectRanges(nsTreeRange* aRange, nsTArray<int32_t>& aRanges)
+ {
+ nsTreeRange* cur = aRange;
+ while (cur) {
+ aRanges.AppendElement(cur->mMin);
+ aRanges.AppendElement(cur->mMax);
+ cur = cur->mNext;
+ }
+ }
+
+ static void InvalidateRanges(nsITreeBoxObject* aTree,
+ nsTArray<int32_t>& aRanges)
+ {
+ if (aTree) {
+ nsCOMPtr<nsITreeBoxObject> tree = aTree;
+ for (uint32_t i = 0; i < aRanges.Length(); i += 2) {
+ aTree->InvalidateRange(aRanges[i], aRanges[i + 1]);
+ }
+ }
+ }
+
+ void Invalidate() {
+ nsTArray<int32_t> ranges;
+ CollectRanges(this, ranges);
+ InvalidateRanges(mSelection->mTree, ranges);
+
+ }
+
+ void RemoveAllBut(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax) {
+
+ // Invalidate everything in this list.
+ nsTArray<int32_t> ranges;
+ CollectRanges(mSelection->mFirstRange, ranges);
+
+ mMin = aIndex;
+ mMax = aIndex;
+
+ nsTreeRange* first = mSelection->mFirstRange;
+ if (mPrev)
+ mPrev->mNext = mNext;
+ if (mNext)
+ mNext->mPrev = mPrev;
+ mNext = mPrev = nullptr;
+
+ if (first != this) {
+ delete mSelection->mFirstRange;
+ mSelection->mFirstRange = this;
+ }
+ InvalidateRanges(mSelection->mTree, ranges);
+ }
+ else if (mNext)
+ mNext->RemoveAllBut(aIndex);
+ }
+
+ void Insert(nsTreeRange* aRange) {
+ if (mMin >= aRange->mMax)
+ aRange->Connect(mPrev, this);
+ else if (mNext)
+ mNext->Insert(aRange);
+ else
+ aRange->Connect(this, nullptr);
+ }
+};
+
+nsTreeSelection::nsTreeSelection(nsITreeBoxObject* aTree)
+ : mTree(aTree),
+ mSuppressed(false),
+ mCurrentIndex(-1),
+ mShiftSelectPivot(-1),
+ mFirstRange(nullptr)
+{
+}
+
+nsTreeSelection::~nsTreeSelection()
+{
+ delete mFirstRange;
+ if (mSelectTimer)
+ mSelectTimer->Cancel();
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsTreeSelection, mTree, mCurrentColumn)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsITreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsINativeTreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeSelection)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsTreeSelection::GetTree(nsITreeBoxObject * *aTree)
+{
+ NS_IF_ADDREF(*aTree = mTree);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetTree(nsITreeBoxObject * aTree)
+{
+ if (mSelectTimer) {
+ mSelectTimer->Cancel();
+ mSelectTimer = nullptr;
+ }
+
+ // Make sure aTree really implements nsITreeBoxObject and nsIBoxObject!
+ nsCOMPtr<nsIBoxObject> bo = do_QueryInterface(aTree);
+ mTree = do_QueryInterface(bo);
+ NS_ENSURE_STATE(mTree == aTree);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetSingle(bool* aSingle)
+{
+ if (!mTree)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+
+ nsCOMPtr<nsIDOMElement> element;
+ boxObject->GetElement(getter_AddRefs(element));
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(element);
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::single, &nsGkAtoms::cell, &nsGkAtoms::text, nullptr};
+
+ *aSingle = content->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::seltype,
+ strings, eCaseMatters) >= 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::IsSelected(int32_t aIndex, bool* aResult)
+{
+ if (mFirstRange)
+ *aResult = mFirstRange->Contains(aIndex);
+ else
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::TimedSelect(int32_t aIndex, int32_t aMsec)
+{
+ bool suppressSelect = mSuppressed;
+
+ if (aMsec != -1)
+ mSuppressed = true;
+
+ nsresult rv = Select(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aMsec != -1) {
+ mSuppressed = suppressSelect;
+ if (!mSuppressed) {
+ if (mSelectTimer)
+ mSelectTimer->Cancel();
+
+ mSelectTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mSelectTimer->InitWithFuncCallback(SelectCallback, this, aMsec,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::Select(int32_t aIndex)
+{
+ mShiftSelectPivot = -1;
+
+ nsresult rv = SetCurrentIndex(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFirstRange) {
+ bool alreadySelected = mFirstRange->Contains(aIndex);
+
+ if (alreadySelected) {
+ int32_t count = mFirstRange->Count();
+ if (count > 1) {
+ // We need to deselect everything but our item.
+ mFirstRange->RemoveAllBut(aIndex);
+ FireOnSelectHandler();
+ }
+ return NS_OK;
+ }
+ else {
+ // Clear out our selection.
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ }
+ }
+
+ // Create our new selection.
+ mFirstRange = new nsTreeRange(this, aIndex);
+ if (!mFirstRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mFirstRange->Invalidate();
+
+ // Fire the select event
+ FireOnSelectHandler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ToggleSelect(int32_t aIndex)
+{
+ // There are six cases that can occur on a ToggleSelect with our
+ // range code.
+ // (1) A new range should be made for a selection.
+ // (2) A single range is removed from the selection.
+ // (3) The item is added to an existing range.
+ // (4) The item is removed from an existing range.
+ // (5) The addition of the item causes two ranges to be merged.
+ // (6) The removal of the item causes two ranges to be split.
+ mShiftSelectPivot = -1;
+ nsresult rv = SetCurrentIndex(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mFirstRange)
+ Select(aIndex);
+ else {
+ if (!mFirstRange->Contains(aIndex)) {
+ bool single;
+ rv = GetSingle(&single);
+ if (NS_SUCCEEDED(rv) && !single)
+ rv = mFirstRange->Add(aIndex);
+ }
+ else
+ rv = mFirstRange->Remove(aIndex);
+ if (NS_SUCCEEDED(rv)) {
+ if (mTree)
+ mTree->InvalidateRow(aIndex);
+
+ FireOnSelectHandler();
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsTreeSelection::RangedSelect(int32_t aStartIndex, int32_t aEndIndex, bool aAugment)
+{
+ bool single;
+ nsresult rv = GetSingle(&single);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if ((mFirstRange || (aStartIndex != aEndIndex)) && single)
+ return NS_OK;
+
+ if (!aAugment) {
+ // Clear our selection.
+ if (mFirstRange) {
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ mFirstRange = nullptr;
+ }
+ }
+
+ if (aStartIndex == -1) {
+ if (mShiftSelectPivot != -1)
+ aStartIndex = mShiftSelectPivot;
+ else if (mCurrentIndex != -1)
+ aStartIndex = mCurrentIndex;
+ else
+ aStartIndex = aEndIndex;
+ }
+
+ mShiftSelectPivot = aStartIndex;
+ rv = SetCurrentIndex(aEndIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
+ int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
+
+ if (aAugment && mFirstRange) {
+ // We need to remove all the items within our selected range from the selection,
+ // and then we insert our new range into the list.
+ nsresult rv = mFirstRange->RemoveRange(start, end);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsTreeRange* range = new nsTreeRange(this, start, end);
+ if (!range)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ range->Invalidate();
+
+ if (aAugment && mFirstRange)
+ mFirstRange->Insert(range);
+ else
+ mFirstRange = range;
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ClearRange(int32_t aStartIndex, int32_t aEndIndex)
+{
+ nsresult rv = SetCurrentIndex(aEndIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFirstRange) {
+ int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
+ int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
+
+ mFirstRange->RemoveRange(start, end);
+
+ if (mTree)
+ mTree->InvalidateRange(start, end);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ClearSelection()
+{
+ if (mFirstRange) {
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ mFirstRange = nullptr;
+ }
+ mShiftSelectPivot = -1;
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::InvertSelection()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsTreeSelection::SelectAll()
+{
+ if (!mTree)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeView> view;
+ mTree->GetView(getter_AddRefs(view));
+ if (!view)
+ return NS_OK;
+
+ int32_t rowCount;
+ view->GetRowCount(&rowCount);
+ bool single;
+ nsresult rv = GetSingle(&single);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rowCount == 0 || (rowCount > 1 && single))
+ return NS_OK;
+
+ mShiftSelectPivot = -1;
+
+ // Invalidate not necessary when clearing selection, since
+ // we're going to invalidate the world on the SelectAll.
+ delete mFirstRange;
+
+ mFirstRange = new nsTreeRange(this, 0, rowCount-1);
+ mFirstRange->Invalidate();
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetRangeCount(int32_t* aResult)
+{
+ int32_t count = 0;
+ nsTreeRange* curr = mFirstRange;
+ while (curr) {
+ count++;
+ curr = curr->mNext;
+ }
+
+ *aResult = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetRangeAt(int32_t aIndex, int32_t* aMin, int32_t* aMax)
+{
+ *aMin = *aMax = -1;
+ int32_t i = -1;
+ nsTreeRange* curr = mFirstRange;
+ while (curr) {
+ i++;
+ if (i == aIndex) {
+ *aMin = curr->mMin;
+ *aMax = curr->mMax;
+ break;
+ }
+ curr = curr->mNext;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCount(int32_t *count)
+{
+ if (mFirstRange)
+ *count = mFirstRange->Count();
+ else // No range available, so there's no selected row.
+ *count = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(bool *aSelectEventsSuppressed)
+{
+ *aSelectEventsSuppressed = mSuppressed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(bool aSelectEventsSuppressed)
+{
+ mSuppressed = aSelectEventsSuppressed;
+ if (!mSuppressed)
+ FireOnSelectHandler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(int32_t *aCurrentIndex)
+{
+ *aCurrentIndex = mCurrentIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(int32_t aIndex)
+{
+ if (!mTree) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mCurrentIndex == aIndex) {
+ return NS_OK;
+ }
+ if (mCurrentIndex != -1 && mTree)
+ mTree->InvalidateRow(mCurrentIndex);
+
+ mCurrentIndex = aIndex;
+ if (!mTree)
+ return NS_OK;
+
+ if (aIndex != -1)
+ mTree->InvalidateRow(aIndex);
+
+ // Fire DOMMenuItemActive or DOMMenuItemInactive event for tree.
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+ NS_ASSERTION(boxObject, "no box object!");
+ if (!boxObject)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIDOMElement> treeElt;
+ boxObject->GetElement(getter_AddRefs(treeElt));
+
+ nsCOMPtr<nsINode> treeDOMNode(do_QueryInterface(treeElt));
+ NS_ENSURE_STATE(treeDOMNode);
+
+ NS_NAMED_LITERAL_STRING(DOMMenuItemActive, "DOMMenuItemActive");
+ NS_NAMED_LITERAL_STRING(DOMMenuItemInactive, "DOMMenuItemInactive");
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(treeDOMNode,
+ (aIndex != -1 ? DOMMenuItemActive :
+ DOMMenuItemInactive),
+ true, false);
+ return asyncDispatcher->PostDOMEvent();
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCurrentColumn(nsITreeColumn** aCurrentColumn)
+{
+ NS_IF_ADDREF(*aCurrentColumn = mCurrentColumn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetCurrentColumn(nsITreeColumn* aCurrentColumn)
+{
+ if (!mTree) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mCurrentColumn == aCurrentColumn) {
+ return NS_OK;
+ }
+
+ if (mCurrentColumn) {
+ if (mFirstRange)
+ mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn);
+ if (mCurrentIndex != -1)
+ mTree->InvalidateCell(mCurrentIndex, mCurrentColumn);
+ }
+
+ mCurrentColumn = aCurrentColumn;
+
+ if (mCurrentColumn) {
+ if (mFirstRange)
+ mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn);
+ if (mCurrentIndex != -1)
+ mTree->InvalidateCell(mCurrentIndex, mCurrentColumn);
+ }
+
+ return NS_OK;
+}
+
+#define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \
+ { \
+ int32_t start = macro_start; \
+ int32_t end = macro_end; \
+ if (start > end) { \
+ end = start; \
+ } \
+ nsTreeRange* macro_new_range = new nsTreeRange(macro_selection, start, end); \
+ if (macro_range) \
+ macro_range->Insert(macro_new_range); \
+ else \
+ macro_range = macro_new_range; \
+ }
+
+NS_IMETHODIMP
+nsTreeSelection::AdjustSelection(int32_t aIndex, int32_t aCount)
+{
+ NS_ASSERTION(aCount != 0, "adjusting by zero");
+ if (!aCount) return NS_OK;
+
+ // adjust mShiftSelectPivot, if necessary
+ if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) {
+ // if we are deleting and the delete includes the shift select pivot, reset it
+ if (aCount < 0 && (mShiftSelectPivot <= (aIndex -aCount -1))) {
+ mShiftSelectPivot = -1;
+ }
+ else {
+ mShiftSelectPivot += aCount;
+ }
+ }
+
+ // adjust mCurrentIndex, if necessary
+ if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) {
+ // if we are deleting and the delete includes the current index, reset it
+ if (aCount < 0 && (mCurrentIndex <= (aIndex -aCount -1))) {
+ mCurrentIndex = -1;
+ }
+ else {
+ mCurrentIndex += aCount;
+ }
+ }
+
+ // no selection, so nothing to do.
+ if (!mFirstRange) return NS_OK;
+
+ bool selChanged = false;
+ nsTreeRange* oldFirstRange = mFirstRange;
+ nsTreeRange* curr = mFirstRange;
+ mFirstRange = nullptr;
+ while (curr) {
+ if (aCount > 0) {
+ // inserting
+ if (aIndex > curr->mMax) {
+ // adjustment happens after the range, so no change
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
+ }
+ else if (aIndex <= curr->mMin) {
+ // adjustment happens before the start of the range, so shift down
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount);
+ selChanged = true;
+ }
+ else {
+ // adjustment happen inside the range.
+ // break apart the range and create two ranges
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1);
+ ADD_NEW_RANGE(mFirstRange, this, aIndex + aCount, curr->mMax + aCount);
+ selChanged = true;
+ }
+ }
+ else {
+ // deleting
+ if (aIndex > curr->mMax) {
+ // adjustment happens after the range, so no change
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
+ }
+ else {
+ // remember, aCount is negative
+ selChanged = true;
+ int32_t lastIndexOfAdjustment = aIndex - aCount - 1;
+ if (aIndex <= curr->mMin) {
+ if (lastIndexOfAdjustment < curr->mMin) {
+ // adjustment happens before the start of the range, so shift up
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount);
+ }
+ else if (lastIndexOfAdjustment >= curr->mMax) {
+ // adjustment contains the range. remove the range by not adding it to the newRange
+ }
+ else {
+ // adjustment starts before the range, and ends in the middle of it, so trim the range
+ ADD_NEW_RANGE(mFirstRange, this, aIndex, curr->mMax + aCount)
+ }
+ }
+ else if (lastIndexOfAdjustment >= curr->mMax) {
+ // adjustment starts in the middle of the current range, and contains the end of the range, so trim the range
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1)
+ }
+ else {
+ // range contains the adjustment, so shorten the range
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax + aCount)
+ }
+ }
+ }
+ curr = curr->mNext;
+ }
+
+ delete oldFirstRange;
+
+ // Fire the select event
+ if (selChanged)
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeSelection::InvalidateSelection()
+{
+ if (mFirstRange)
+ mFirstRange->Invalidate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeSelection::GetShiftSelectPivot(int32_t* aIndex)
+{
+ *aIndex = mShiftSelectPivot;
+ return NS_OK;
+}
+
+
+nsresult
+nsTreeSelection::FireOnSelectHandler()
+{
+ if (mSuppressed || !mTree)
+ return NS_OK;
+
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+ NS_ASSERTION(boxObject, "no box object!");
+ if (!boxObject)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIDOMElement> elt;
+ boxObject->GetElement(getter_AddRefs(elt));
+ NS_ENSURE_STATE(elt);
+
+ nsCOMPtr<nsINode> node(do_QueryInterface(elt));
+ NS_ENSURE_STATE(node);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(node, NS_LITERAL_STRING("select"), true, false);
+ asyncDispatcher->RunDOMEventWhenSafe();
+ return NS_OK;
+}
+
+void
+nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure)
+{
+ RefPtr<nsTreeSelection> self = static_cast<nsTreeSelection*>(aClosure);
+ if (self) {
+ self->FireOnSelectHandler();
+ aTimer->Cancel();
+ self->mSelectTimer = nullptr;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult)
+{
+ *aResult = new nsTreeSelection(aTree);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeSelection.h b/layout/xul/tree/nsTreeSelection.h
new file mode 100644
index 000000000..41ffe31fb
--- /dev/null
+++ b/layout/xul/tree/nsTreeSelection.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeSelection_h__
+#define nsTreeSelection_h__
+
+#include "nsITreeSelection.h"
+#include "nsITreeColumns.h"
+#include "nsITimer.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsITreeBoxObject;
+struct nsTreeRange;
+
+class nsTreeSelection final : public nsINativeTreeSelection
+{
+public:
+ explicit nsTreeSelection(nsITreeBoxObject* aTree);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeSelection)
+ NS_DECL_NSITREESELECTION
+
+ // nsINativeTreeSelection: Untrusted code can use us
+ NS_IMETHOD EnsureNative() override { return NS_OK; }
+
+ friend struct nsTreeRange;
+
+protected:
+ ~nsTreeSelection();
+
+ nsresult FireOnSelectHandler();
+ static void SelectCallback(nsITimer *aTimer, void *aClosure);
+
+protected:
+ // Members
+ nsCOMPtr<nsITreeBoxObject> mTree; // The tree will hold on to us through the view and let go when it dies.
+
+ bool mSuppressed; // Whether or not we should be firing onselect events.
+ int32_t mCurrentIndex; // The item to draw the rect around. The last one clicked, etc.
+ nsCOMPtr<nsITreeColumn> mCurrentColumn;
+ int32_t mShiftSelectPivot; // Used when multiple SHIFT+selects are performed to pivot on.
+
+ nsTreeRange* mFirstRange; // Our list of ranges.
+
+ nsCOMPtr<nsITimer> mSelectTimer;
+};
+
+nsresult
+NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult);
+
+#endif
diff --git a/layout/xul/tree/nsTreeStyleCache.cpp b/layout/xul/tree/nsTreeStyleCache.cpp
new file mode 100644
index 000000000..74b8a89c4
--- /dev/null
+++ b/layout/xul/tree/nsTreeStyleCache.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTreeStyleCache.h"
+#include "nsStyleSet.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+
+nsTreeStyleCache::Transition::Transition(DFAState aState, nsIAtom* aSymbol)
+ : mState(aState), mInputSymbol(aSymbol)
+{
+}
+
+bool
+nsTreeStyleCache::Transition::operator==(const Transition& aOther) const
+{
+ return aOther.mState == mState && aOther.mInputSymbol == mInputSymbol;
+}
+
+uint32_t
+nsTreeStyleCache::Transition::Hash() const
+{
+ // Make a 32-bit integer that combines the low-order 16 bits of the state and the input symbol.
+ uint32_t hb = mState << 16;
+ uint32_t lb = (NS_PTR_TO_UINT32(mInputSymbol.get()) << 16) >> 16;
+ return hb+lb;
+}
+
+
+// The style context cache impl
+nsStyleContext*
+nsTreeStyleCache::GetStyleContext(nsICSSPseudoComparator* aComparator,
+ nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aContext,
+ nsIAtom* aPseudoElement,
+ const AtomArray & aInputWord)
+{
+ uint32_t count = aInputWord.Length();
+
+ // Go ahead and init the transition table.
+ if (!mTransitionTable) {
+ // Automatic miss. Build the table
+ mTransitionTable = new TransitionTable();
+ }
+
+ // The first transition is always made off the supplied pseudo-element.
+ Transition transition(0, aPseudoElement);
+ DFAState currState = mTransitionTable->Get(transition);
+
+ if (!currState) {
+ // We had a miss. Make a new state and add it to our hash.
+ currState = mNextState;
+ mNextState++;
+ mTransitionTable->Put(transition, currState);
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ Transition transition(currState, aInputWord[i]);
+ currState = mTransitionTable->Get(transition);
+
+ if (!currState) {
+ // We had a miss. Make a new state and add it to our hash.
+ currState = mNextState;
+ mNextState++;
+ mTransitionTable->Put(transition, currState);
+ }
+ }
+
+ // We're in a final state.
+ // Look up our style context for this state.
+ nsStyleContext* result = nullptr;
+ if (mCache) {
+ result = mCache->GetWeak(currState);
+ }
+ if (!result) {
+ // We missed the cache. Resolve this pseudo-style.
+ // XXXheycam ServoStyleSets do not support XUL tree styles.
+ if (aPresContext->StyleSet()->IsServo()) {
+ MOZ_CRASH("stylo: ServoStyleSets should not support XUL tree styles yet");
+ }
+ RefPtr<nsStyleContext> newResult = aPresContext->StyleSet()->AsGecko()->
+ ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement,
+ aContext, aComparator);
+
+ // Put the style context in our table, transferring the owning reference to the table.
+ if (!mCache) {
+ mCache = new StyleContextCache();
+ }
+ result = newResult.get();
+ mCache->Put(currState, newResult.forget());
+ }
+
+ return result;
+}
+
diff --git a/layout/xul/tree/nsTreeStyleCache.h b/layout/xul/tree/nsTreeStyleCache.h
new file mode 100644
index 000000000..1020b0435
--- /dev/null
+++ b/layout/xul/tree/nsTreeStyleCache.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeStyleCache_h__
+#define nsTreeStyleCache_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAutoPtr.h"
+#include "nsIAtom.h"
+#include "nsCOMArray.h"
+#include "nsICSSPseudoComparator.h"
+#include "nsRefPtrHashtable.h"
+#include "nsStyleContext.h"
+
+typedef nsCOMArray<nsIAtom> AtomArray;
+
+class nsTreeStyleCache
+{
+public:
+ nsTreeStyleCache()
+ : mNextState(0)
+ {
+ }
+
+ ~nsTreeStyleCache()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ mTransitionTable = nullptr;
+ mCache = nullptr;
+ mNextState = 0;
+ }
+
+ nsStyleContext* GetStyleContext(nsICSSPseudoComparator* aComparator,
+ nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aContext,
+ nsIAtom* aPseudoElement,
+ const AtomArray & aInputWord);
+
+protected:
+ typedef uint32_t DFAState;
+
+ class Transition final
+ {
+ public:
+ Transition(DFAState aState, nsIAtom* aSymbol);
+ bool operator==(const Transition& aOther) const;
+ uint32_t Hash() const;
+
+ private:
+ DFAState mState;
+ nsCOMPtr<nsIAtom> mInputSymbol;
+ };
+
+ typedef nsDataHashtable<nsGenericHashKey<Transition>, DFAState> TransitionTable;
+
+ // A transition table for a deterministic finite automaton. The DFA
+ // takes as its input a single pseudoelement and an ordered set of properties.
+ // It transitions on an input word that is the concatenation of the pseudoelement supplied
+ // with the properties in the array.
+ //
+ // It transitions from state to state by looking up entries in the transition table (which is
+ // a mapping from (S,i)->S', where S is the current state, i is the next
+ // property in the input word, and S' is the state to transition to.
+ //
+ // If S' is not found, it is constructed and entered into the hashtable
+ // under the key (S,i).
+ //
+ // Once the entire word has been consumed, the final state is used
+ // to reference the cache table to locate the style context.
+ nsAutoPtr<TransitionTable> mTransitionTable;
+
+ // The cache of all active style contexts. This is a hash from
+ // a final state in the DFA, Sf, to the resultant style context.
+ typedef nsRefPtrHashtable<nsUint32HashKey, nsStyleContext> StyleContextCache;
+ nsAutoPtr<StyleContextCache> mCache;
+
+ // An integer counter that is used when we need to make new states in the
+ // DFA.
+ DFAState mNextState;
+};
+
+#endif // nsTreeStyleCache_h__
diff --git a/layout/xul/tree/nsTreeUtils.cpp b/layout/xul/tree/nsTreeUtils.cpp
new file mode 100644
index 000000000..81a56f0e9
--- /dev/null
+++ b/layout/xul/tree/nsTreeUtils.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsReadableUtils.h"
+#include "nsTreeUtils.h"
+#include "ChildIterator.h"
+#include "nsCRT.h"
+#include "nsIAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+nsresult
+nsTreeUtils::TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray)
+{
+ nsAString::const_iterator end;
+ aProperties.EndReading(end);
+
+ nsAString::const_iterator iter;
+ aProperties.BeginReading(iter);
+
+ do {
+ // Skip whitespace
+ while (iter != end && nsCRT::IsAsciiSpace(*iter))
+ ++iter;
+
+ // If only whitespace, we're done
+ if (iter == end)
+ break;
+
+ // Note the first non-whitespace character
+ nsAString::const_iterator first = iter;
+
+ // Advance to the next whitespace character
+ while (iter != end && ! nsCRT::IsAsciiSpace(*iter))
+ ++iter;
+
+ // XXX this would be nonsensical
+ NS_ASSERTION(iter != first, "eh? something's wrong here");
+ if (iter == first)
+ break;
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(Substring(first, iter));
+ aPropertiesArray.AppendElement(atom);
+ } while (iter != end);
+
+ return NS_OK;
+}
+
+nsIContent*
+nsTreeUtils::GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag)
+{
+ dom::FlattenedChildIterator iter(aContainer);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(aTag)) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+nsIContent*
+nsTreeUtils::GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag)
+{
+ dom::FlattenedChildIterator iter(aContainer);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(aTag)) {
+ return child;
+ }
+
+ child = GetDescendantChild(child, aTag);
+ if (child) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsTreeUtils::UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection)
+{
+ aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, aDirection, true);
+ aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive, NS_LITERAL_STRING("true"), true);
+
+ // Unset sort attribute(s) on the other columns
+ nsCOMPtr<nsIContent> parentContent = aColumn->GetParent();
+ if (parentContent &&
+ parentContent->NodeInfo()->Equals(nsGkAtoms::treecols,
+ kNameSpaceID_XUL)) {
+ uint32_t i, numChildren = parentContent->GetChildCount();
+ for (i = 0; i < numChildren; ++i) {
+ nsCOMPtr<nsIContent> childContent = parentContent->GetChildAt(i);
+
+ if (childContent &&
+ childContent != aColumn &&
+ childContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ childContent->UnsetAttr(kNameSpaceID_None,
+ nsGkAtoms::sortDirection, true);
+ childContent->UnsetAttr(kNameSpaceID_None,
+ nsGkAtoms::sortActive, true);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeUtils::GetColumnIndex(nsIContent* aColumn, int32_t* aResult)
+{
+ nsIContent* parentContent = aColumn->GetParent();
+ if (parentContent &&
+ parentContent->NodeInfo()->Equals(nsGkAtoms::treecols,
+ kNameSpaceID_XUL)) {
+ uint32_t i, numChildren = parentContent->GetChildCount();
+ int32_t colIndex = 0;
+ for (i = 0; i < numChildren; ++i) {
+ nsIContent *childContent = parentContent->GetChildAt(i);
+ if (childContent &&
+ childContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ if (childContent == aColumn) {
+ *aResult = colIndex;
+ return NS_OK;
+ }
+ ++colIndex;
+ }
+ }
+ }
+
+ *aResult = -1;
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeUtils.h b/layout/xul/tree/nsTreeUtils.h
new file mode 100644
index 000000000..f2ed2df68
--- /dev/null
+++ b/layout/xul/tree/nsTreeUtils.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeUtils_h__
+#define nsTreeUtils_h__
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsTreeStyleCache.h"
+
+class nsIAtom;
+class nsIContent;
+
+class nsTreeUtils
+{
+ public:
+ /**
+ * Parse a whitespace separated list of properties into an array
+ * of atoms.
+ */
+ static nsresult
+ TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray);
+
+ static nsIContent*
+ GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag);
+
+ static nsIContent*
+ GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag);
+
+ static nsresult
+ UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection);
+
+ static nsresult
+ GetColumnIndex(nsIContent* aColumn, int32_t* aResult);
+};
+
+#endif // nsTreeUtils_h__