diff options
Diffstat (limited to 'browser/components/customizableui/test')
114 files changed, 9062 insertions, 0 deletions
diff --git a/browser/components/customizableui/test/.eslintrc.js b/browser/components/customizableui/test/.eslintrc.js new file mode 100644 index 000000000..c764b133d --- /dev/null +++ b/browser/components/customizableui/test/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/components/customizableui/test/browser.ini b/browser/components/customizableui/test/browser.ini new file mode 100644 index 000000000..1c1f30498 --- /dev/null +++ b/browser/components/customizableui/test/browser.ini @@ -0,0 +1,154 @@ +[DEFAULT] +support-files = + head.js + support/test_967000_charEncoding_page.html + support/feeds_test_page.html + support/test-feed.xml + +[browser_873501_handle_specials.js] +[browser_876926_customize_mode_wrapping.js] +[browser_876944_customize_mode_create_destroy.js] +[browser_877006_missing_view.js] +[browser_877178_unregisterArea.js] +[browser_877447_skip_missing_ids.js] +[browser_878452_drag_to_panel.js] +[browser_880164_customization_context_menus.js] +[browser_880382_drag_wide_widgets_in_panel.js] +[browser_884402_customize_from_overflow.js] +skip-if = os == "linux" +[browser_885052_customize_mode_observers_disabed.js] +tags = fullscreen +# Bug 951403 - Disabled on OSX for frequent failures +skip-if = os == "mac" + +[browser_885530_showInPrivateBrowsing.js] +[browser_886323_buildArea_removable_nodes.js] +[browser_887438_currentset_shim.js] +[browser_888817_currentset_updating.js] +[browser_890140_orphaned_placeholders.js] +[browser_890262_destroyWidget_after_add_to_panel.js] +[browser_892955_isWidgetRemovable_for_removed_widgets.js] +[browser_892956_destroyWidget_defaultPlacements.js] +[browser_909779_overflow_toolbars_new_window.js] +skip-if = os == "linux" + +[browser_901207_searchbar_in_panel.js] +[browser_913972_currentset_overflow.js] +skip-if = os == "linux" + +[browser_914138_widget_API_overflowable_toolbar.js] +skip-if = os == "linux" + +[browser_914863_disabled_help_quit_buttons.js] +[browser_918049_skipintoolbarset_dnd.js] +[browser_923857_customize_mode_event_wrapping_during_reset.js] +[browser_927717_customize_drag_empty_toolbar.js] + +# Bug 1163231 - Causes failures on Developer Edition on Windows 7. +# [browser_932928_show_notice_when_palette_empty.js] + +[browser_934113_menubar_removable.js] +# Because this test is about the menubar, it can't be run on mac +skip-if = os == "mac" + +[browser_934951_zoom_in_toolbar.js] +[browser_938980_navbar_collapsed.js] +[browser_938995_indefaultstate_nonremovable.js] +[browser_940013_registerToolbarNode_calls_registerArea.js] +[browser_940307_panel_click_closure_handling.js] +[browser_940946_removable_from_navbar_customizemode.js] +[browser_941083_invalidate_wrapper_cache_createWidget.js] +[browser_942581_unregisterArea_keeps_placements.js] +[browser_943683_migration_test.js] +[browser_944887_destroyWidget_should_destroy_in_palette.js] +[browser_945739_showInPrivateBrowsing_customize_mode.js] +[browser_947914_button_addons.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_copy.js] +subsuite = clipboard +skip-if = os == "linux" # Intermittent failures on Linux +[browser_947914_button_cut.js] +subsuite = clipboard +skip-if = os == "linux" # Intermittent failures on Linux +[browser_947914_button_find.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_history.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_newPrivateWindow.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_newWindow.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_paste.js] +subsuite = clipboard +skip-if = os == "linux" # Intermittent failures on Linux +[browser_947914_button_print.js] +skip-if = os == "linux" # Intermittent failures on Linux +[browser_947914_button_savePage.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_zoomIn.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_zoomOut.js] +skip-if = os == "linux" # Intermittent failures +[browser_947914_button_zoomReset.js] +skip-if = os == "linux" # Intermittent failures +[browser_947987_removable_default.js] +[browser_948985_non_removable_defaultArea.js] +[browser_952963_areaType_getter_no_area.js] +[browser_956602_remove_special_widget.js] +[browser_962069_drag_to_overflow_chevron.js] +[browser_962884_opt_in_disable_hyphens.js] +[browser_963639_customizing_attribute_non_customizable_toolbar.js] +[browser_967000_button_charEncoding.js] +[browser_967000_button_feeds.js] +[browser_967000_button_sync.js] +[browser_968447_bookmarks_toolbar_items_in_panel.js] +skip-if = os == "linux" # Intemittent failures - bug 979207 +[browser_968565_insert_before_hidden_items.js] +[browser_969427_recreate_destroyed_widget_after_reset.js] +[browser_969661_character_encoding_navbar_disabled.js] +[browser_970511_undo_restore_default.js] +[browser_972267_customizationchange_events.js] +[browser_973641_button_addon.js] +[browser_973932_addonbar_currentset.js] +[browser_975719_customtoolbars_behaviour.js] +[browser_976792_insertNodeInWindow.js] +skip-if = os == "linux" +[browser_978084_dragEnd_after_move.js] +[browser_980155_add_overflow_toolbar.js] +[browser_981305_separator_insertion.js] +[browser_981418-widget-onbeforecreated-handler.js] +[browser_982656_restore_defaults_builtin_widgets.js] +[browser_984455_bookmarks_items_reparenting.js] +skip-if = os == "linux" +[browser_985815_propagate_setToolbarVisibility.js] +[browser_987177_destroyWidget_xul.js] +[browser_987177_xul_wrapper_updating.js] +[browser_987185_syncButton.js] +[browser_987492_window_api.js] +[browser_987640_charEncoding.js] +[browser_988072_sidebar_events.js] +[browser_989338_saved_placements_not_resaved.js] +[browser_989751_subviewbutton_class.js] +[browser_992747_toggle_noncustomizable_toolbar.js] +[browser_993322_widget_notoolbar.js] +[browser_995164_registerArea_during_customize_mode.js] +[browser_996364_registerArea_different_properties.js] +[browser_996635_remove_non_widgets.js] +[browser_1003588_no_specials_in_panel.js] +[browser_1007336_lwthemes_in_customize_mode.js] +skip-if = os == "linux" # crashing on Linux due to bug 1271683 +[browser_1008559_anchor_undo_restore.js] +[browser_1042100_default_placements_update.js] +[browser_1058573_showToolbarsDropdown.js] +[browser_1087303_button_fullscreen.js] +tags = fullscreen +skip-if = os == "mac" +[browser_1087303_button_preferences.js] +[browser_1089591_still_customizable_after_reset.js] +[browser_1096763_seen_widgets_post_reset.js] +[browser_1161838_inserted_new_default_buttons.js] +[browser_bootstrapped_custom_toolbar.js] +[browser_customizemode_contextmenu_menubuttonstate.js] +[browser_panel_toggle.js] +[browser_switch_to_customize_mode.js] +[browser_check_tooltips_in_navbar.js] diff --git a/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js b/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js new file mode 100644 index 000000000..22fbb5c0c --- /dev/null +++ b/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function simulateItemDragAndEnd(aToDrag, aTarget) { + var ds = Components.classes["@mozilla.org/widget/dragservice;1"]. + getService(Components.interfaces.nsIDragService); + + ds.startDragSession(); + try { + var [result, dataTransfer] = EventUtils.synthesizeDragOver(aToDrag.parentNode, aTarget); + EventUtils.synthesizeDropAfterDragOver(result, dataTransfer, aTarget); + // Send dragend to move dragging item back to initial place. + EventUtils.sendDragEvent({ type: "dragend", dataTransfer: dataTransfer }, + aToDrag.parentNode); + } finally { + ds.endDragSession(true); + } +} + +add_task(function* checkNoAddingToPanel() { + let area = CustomizableUI.AREA_PANEL; + let previousPlacements = getAreaWidgetIds(area); + CustomizableUI.addWidgetToArea("separator", area); + CustomizableUI.addWidgetToArea("spring", area); + CustomizableUI.addWidgetToArea("spacer", area); + assertAreaPlacements(area, previousPlacements); + + let oldNumberOfItems = previousPlacements.length; + if (getAreaWidgetIds(area).length != oldNumberOfItems) { + CustomizableUI.reset(); + } +}); + +add_task(function* checkAddingToToolbar() { + let area = CustomizableUI.AREA_NAVBAR; + let previousPlacements = getAreaWidgetIds(area); + CustomizableUI.addWidgetToArea("separator", area); + CustomizableUI.addWidgetToArea("spring", area); + CustomizableUI.addWidgetToArea("spacer", area); + let expectedPlacements = [...previousPlacements].concat([ + /separator/, + /spring/, + /spacer/ + ]); + assertAreaPlacements(area, expectedPlacements); + + let newlyAddedElements = getAreaWidgetIds(area).slice(-3); + while (newlyAddedElements.length) { + CustomizableUI.removeWidgetFromArea(newlyAddedElements.shift()); + } + + assertAreaPlacements(area, previousPlacements); + + let oldNumberOfItems = previousPlacements.length; + if (getAreaWidgetIds(area).length != oldNumberOfItems) { + CustomizableUI.reset(); + } +}); + + +add_task(function* checkDragging() { + let startArea = CustomizableUI.AREA_NAVBAR; + let targetArea = CustomizableUI.AREA_PANEL; + let startingToolbarPlacements = getAreaWidgetIds(startArea); + let startingTargetPlacements = getAreaWidgetIds(targetArea); + + CustomizableUI.addWidgetToArea("separator", startArea); + CustomizableUI.addWidgetToArea("spring", startArea); + CustomizableUI.addWidgetToArea("spacer", startArea); + + let placementsWithSpecials = getAreaWidgetIds(startArea); + let elementsToMove = []; + for (let id of placementsWithSpecials) { + if (CustomizableUI.isSpecialWidget(id)) { + elementsToMove.push(id); + } + } + is(elementsToMove.length, 3, "Should have 3 elements to try and drag."); + + yield startCustomizing(); + for (let id of elementsToMove) { + simulateItemDragAndEnd(document.getElementById(id), PanelUI.contents); + } + + assertAreaPlacements(startArea, placementsWithSpecials); + assertAreaPlacements(targetArea, startingTargetPlacements); + + for (let id of elementsToMove) { + simulateItemDrag(document.getElementById(id), gCustomizeMode.visiblePalette); + } + + assertAreaPlacements(startArea, startingToolbarPlacements); + assertAreaPlacements(targetArea, startingTargetPlacements); + + ok(!gCustomizeMode.visiblePalette.querySelector("toolbarspring,toolbarseparator,toolbarspacer"), + "No specials should make it to the palette alive."); + yield endCustomizing(); +}); + + +add_task(function* asyncCleanup() { + yield endCustomizing(); + CustomizableUI.reset(); +}); + diff --git a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js new file mode 100644 index 000000000..db4f88e6d --- /dev/null +++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}"; +const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {}); + +add_task(function* () { + Services.prefs.clearUserPref("lightweightThemes.usedThemes"); + Services.prefs.clearUserPref("lightweightThemes.recommendedThemes"); + LightweightThemeManager.clearBuiltInThemes(); + + yield startCustomizing(); + + let themesButton = document.getElementById("customization-lwtheme-button"); + let popup = document.getElementById("customization-lwtheme-menu"); + + let popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button"); + yield popupShownPromise; + + // close current tab and re-open Customize menu to confirm correct number of Themes + yield endCustomizing(); + info("Exited customize mode"); + yield startCustomizing(); + info("Started customizing a second time"); + popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button a second time"); + yield popupShownPromise; + + let header = document.getElementById("customization-lwtheme-menu-header"); + let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended"); + + is(header.nextSibling.nextSibling, recommendedHeader, + "There should only be one theme (default) in the 'My Themes' section by default"); + is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "That theme should be the default theme"); + + let firstLWTheme = recommendedHeader.nextSibling; + let firstLWThemeId = firstLWTheme.theme.id; + let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed"); + firstLWTheme.doCommand(); + info("Clicked on first theme"); + yield themeChangedPromise; + + popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button"); + yield popupShownPromise; + + is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme"); + let installedThemeId = header.nextSibling.nextSibling.theme.id; + ok(installedThemeId.startsWith(firstLWThemeId), + "The second theme in the 'My Themes' section should be the newly installed theme: " + + "Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId); + is(header.nextSibling.nextSibling.nextSibling, recommendedHeader, + "There should be two themes in the 'My Themes' section"); + + let defaultTheme = header.nextSibling; + defaultTheme.doCommand(); + is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), "", "No lwtheme should be selected"); + + // ensure current theme isn't set to "Default" + popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button a second time"); + yield popupShownPromise; + + firstLWTheme = recommendedHeader.nextSibling; + themeChangedPromise = promiseObserverNotified("lightweight-theme-changed"); + firstLWTheme.doCommand(); + info("Clicked on first theme again"); + yield themeChangedPromise; + + // check that "Restore Defaults" button resets theme + yield gCustomizeMode.reset(); + is(LightweightThemeManager.currentTheme, null, "Current theme reset to default"); + + yield endCustomizing(); + Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]"); + Services.prefs.setCharPref("lightweightThemes.recommendedThemes", "[]"); + info("Removed all recommended themes"); + yield startCustomizing(); + popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button a second time"); + yield popupShownPromise; + header = document.getElementById("customization-lwtheme-menu-header"); + is(header.hidden, false, "Header should never be hidden"); + is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme"); + is(header.nextSibling.hidden, false, "The default theme should never be hidden"); + recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended"); + is(header.nextSibling.nextSibling, recommendedHeader, + "There should only be one theme (default) in the 'My Themes' section by default"); + let footer = document.getElementById("customization-lwtheme-menu-footer"); + is(recommendedHeader.nextSibling.id, footer.id, "There should be no recommended themes in the menu"); + is(recommendedHeader.hidden, true, "The recommendedHeader should be hidden since there are no recommended themes"); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + + Services.prefs.clearUserPref("lightweightThemes.usedThemes"); + Services.prefs.clearUserPref("lightweightThemes.recommendedThemes"); +}); diff --git a/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js new file mode 100644 index 000000000..56657914b --- /dev/null +++ b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const kAnchorAttribute = "cui-anchorid"; + +/** + * Check that anchor gets set correctly when moving an item from the panel to the toolbar + * using 'undo' + */ +add_task(function*() { + yield startCustomizing(); + let button = document.getElementById("history-panelmenu"); + is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button", + "Button (" + button.id + ") starts out with correct anchor"); + + let navbar = document.getElementById("nav-bar").customizationTarget; + simulateItemDrag(button, navbar); + is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar", + "Button (" + button.id + ") ends up in nav-bar"); + + ok(!button.hasAttribute(kAnchorAttribute), + "Button (" + button.id + ") has no anchor in toolbar"); + + let resetButton = document.getElementById("customization-reset-button"); + ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now."); + yield gCustomizeMode.reset(); + + is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button", + "Button (" + button.id + ") has anchor again"); + + let undoButton = document.getElementById("customization-undo-reset-button"); + ok(!undoButton.hasAttribute("disabled"), "Should be able to undo now."); + yield gCustomizeMode.undoReset(); + + ok(!button.hasAttribute(kAnchorAttribute), + "Button (" + button.id + ") once again has no anchor in toolbar"); + + yield gCustomizeMode.reset(); + + yield endCustomizing(); +}); + + +/** + * Check that anchor gets set correctly when moving an item from the panel to the toolbar + * using 'reset' + */ +add_task(function*() { + yield startCustomizing(); + let button = document.getElementById("bookmarks-menu-button"); + ok(!button.hasAttribute(kAnchorAttribute), + "Button (" + button.id + ") has no anchor in toolbar"); + + let panel = document.getElementById("PanelUI-contents"); + simulateItemDrag(button, panel); + is(CustomizableUI.getPlacementOfWidget(button.id).area, "PanelUI-contents", + "Button (" + button.id + ") ends up in panel"); + is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button", + "Button (" + button.id + ") has correct anchor in the panel"); + + let resetButton = document.getElementById("customization-reset-button"); + ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now."); + yield gCustomizeMode.reset(); + + ok(!button.hasAttribute(kAnchorAttribute), + "Button (" + button.id + ") once again has no anchor in toolbar"); + + yield endCustomizing(); +}); diff --git a/browser/components/customizableui/test/browser_1042100_default_placements_update.js b/browser/components/customizableui/test/browser_1042100_default_placements_update.js new file mode 100644 index 000000000..129dbd754 --- /dev/null +++ b/browser/components/customizableui/test/browser_1042100_default_placements_update.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// NB: This uses some ugly hacks to get into the CUI module from elsewhere... +// don't try this at home, kids. +function test() { + // Customize something to make sure stuff changed: + CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR); + + // Check what version we're on: + let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + + is(CustomizableUIBSPass.gFuturePlacements.size, 0, + "All future placements should be dealt with by now."); + + let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass; + CustomizableUIInternal._introduceNewBuiltinWidgets(); + is(gFuturePlacements.size, 0, + "No change to future placements initially."); + + let currentVersion = CustomizableUIBSPass.kVersion; + + + // Add our widget to the defaults: + let testWidgetNew = { + id: "test-messing-with-default-placements-new", + label: "Test messing with default placements - should be inserted", + defaultArea: CustomizableUI.AREA_NAVBAR, + introducedInVersion: currentVersion + 1, + }; + + let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew, + CustomizableUI.SOURCE_BUILTIN); + ok(normalizedWidget, "Widget should be normalizable"); + if (!normalizedWidget) { + return; + } + CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget); + + let testWidgetOld = { + id: "test-messing-with-default-placements-old", + label: "Test messing with default placements - should NOT be inserted", + defaultArea: CustomizableUI.AREA_NAVBAR, + introducedInVersion: currentVersion, + }; + + normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetOld, + CustomizableUI.SOURCE_BUILTIN); + ok(normalizedWidget, "Widget should be normalizable"); + if (!normalizedWidget) { + return; + } + CustomizableUIBSPass.gPalette.set(testWidgetOld.id, normalizedWidget); + + + // Now increase the version in the module: + CustomizableUIBSPass.kVersion++; + + let hadSavedState = !!CustomizableUIBSPass.gSavedState + if (!hadSavedState) { + CustomizableUIBSPass.gSavedState = {currentVersion: CustomizableUIBSPass.kVersion - 1}; + } + + // Then call the re-init routine so we re-add the builtin widgets + CustomizableUIInternal._introduceNewBuiltinWidgets(); + is(gFuturePlacements.size, 1, + "Should have 1 more future placement"); + let haveNavbarPlacements = gFuturePlacements.has(CustomizableUI.AREA_NAVBAR); + ok(haveNavbarPlacements, "Should have placements for nav-bar"); + if (haveNavbarPlacements) { + let placements = [...gFuturePlacements.get(CustomizableUI.AREA_NAVBAR)]; + + // Ignore widgets that are placed using the pref facility and not the + // versioned facility. They're independent of kVersion and the saved + // state's current version, so they may be present in the placements. + for (let i = 0; i < placements.length; ) { + if (placements[i] == testWidgetNew.id) { + i++; + continue; + } + let pref = "browser.toolbarbuttons.introduced." + placements[i]; + let introduced = false; + try { + introduced = Services.prefs.getBoolPref(pref); + } catch (ex) {} + if (!introduced) { + i++; + continue; + } + placements.splice(i, 1); + } + + is(placements.length, 1, "Should have 1 newly placed widget in nav-bar"); + is(placements[0], testWidgetNew.id, "Should have our test widget to be placed in nav-bar"); + } + + gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR); + CustomizableUIBSPass.kVersion--; + gPalette.delete(testWidgetNew.id); + gPalette.delete(testWidgetOld.id); + if (!hadSavedState) { + CustomizableUIBSPass.gSavedState = null; + } +} + diff --git a/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js b/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js new file mode 100644 index 000000000..42a032ff8 --- /dev/null +++ b/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check that toggleable toolbars dropdown in always shown"); + + info("Remove all possible custom toolbars"); + yield removeCustomToolbars(); + + info("Enter customization mode"); + yield startCustomizing(); + + let toolbarsToggle = document.getElementById("customization-toolbar-visibility-button"); + ok(toolbarsToggle, "The toolbars toggle dropdown exists"); + ok(!toolbarsToggle.hasAttribute("hidden"), + "The toolbars toggle dropdown is displayed"); +}); + +add_task(function* asyncCleanup() { + info("Exit customization mode"); + yield endCustomizing(); +}); diff --git a/browser/components/customizableui/test/browser_1087303_button_fullscreen.js b/browser/components/customizableui/test/browser_1087303_button_fullscreen.js new file mode 100644 index 000000000..c6b87d6ab --- /dev/null +++ b/browser/components/customizableui/test/browser_1087303_button_fullscreen.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check fullscreen button existence and functionality"); + + yield PanelUI.show(); + + let fullscreenButton = document.getElementById("fullscreen-button"); + ok(fullscreenButton, "Fullscreen button appears in Panel Menu"); + + let fullscreenPromise = promiseFullscreenChange(); + fullscreenButton.click(); + yield fullscreenPromise; + + ok(window.fullScreen, "Fullscreen mode was opened"); + + // exit full screen mode + fullscreenPromise = promiseFullscreenChange(); + window.fullScreen = !window.fullScreen; + yield fullscreenPromise; + + ok(!window.fullScreen, "Successfully exited fullscreen"); +}); + +function promiseFullscreenChange() { + let deferred = Promise.defer(); + info("Wait for fullscreen change"); + + let timeoutId = setTimeout(() => { + window.removeEventListener("fullscreen", onFullscreenChange, true); + deferred.reject("Fullscreen change did not happen within " + 20000 + "ms"); + }, 20000); + + function onFullscreenChange(event) { + clearTimeout(timeoutId); + window.removeEventListener("fullscreen", onFullscreenChange, true); + info("Fullscreen event received"); + deferred.resolve(); + } + window.addEventListener("fullscreen", onFullscreenChange, true); + return deferred.promise; +} diff --git a/browser/components/customizableui/test/browser_1087303_button_preferences.js b/browser/components/customizableui/test/browser_1087303_button_preferences.js new file mode 100644 index 000000000..b1fdb85b6 --- /dev/null +++ b/browser/components/customizableui/test/browser_1087303_button_preferences.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var newTab = null; + +add_task(function*() { + info("Check preferences button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let preferencesButton = document.getElementById("preferences-button"); + ok(preferencesButton, "Preferences button exists in Panel Menu"); + preferencesButton.click(); + + newTab = gBrowser.selectedTab; + yield waitForPageLoad(newTab); + + let openedPage = gBrowser.currentURI.spec; + is(openedPage, "about:preferences", "Preferences page was opened"); +}); + +add_task(function asyncCleanup() { + if (gBrowser.tabs.length == 1) + gBrowser.addTab("about:blank"); + + gBrowser.removeTab(gBrowser.selectedTab); + info("Tabs were restored"); +}); + +function waitForPageLoad(aTab) { + let deferred = Promise.defer(); + + let timeoutId = setTimeout(() => { + aTab.linkedBrowser.removeEventListener("load", onTabLoad, true); + deferred.reject("Page didn't load within " + 20000 + "ms"); + }, 20000); + + function onTabLoad(event) { + clearTimeout(timeoutId); + aTab.linkedBrowser.removeEventListener("load", onTabLoad, true); + info("Tab event received: " + "load"); + deferred.resolve(); + } + aTab.linkedBrowser.addEventListener("load", onTabLoad, true, true); + return deferred.promise; +} diff --git a/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js b/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js new file mode 100644 index 000000000..1f502e8e2 --- /dev/null +++ b/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js @@ -0,0 +1,24 @@ +"use strict"; + +// Dragging the elements again after a reset should work +add_task(function* () { + yield startCustomizing(); + let historyButton = document.getElementById("wrapper-history-panelmenu"); + let devButton = document.getElementById("wrapper-developer-button"); + + ok(historyButton && devButton, "Draggable elements should exist"); + simulateItemDrag(historyButton, devButton); + yield gCustomizeMode.reset(); + ok(CustomizableUI.inDefaultState, "Should be back in default state"); + + historyButton = document.getElementById("wrapper-history-panelmenu"); + devButton = document.getElementById("wrapper-developer-button"); + ok(historyButton && devButton, "Draggable elements should exist"); + simulateItemDrag(historyButton, devButton); + + yield endCustomizing(); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js b/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js new file mode 100644 index 000000000..b5a325afb --- /dev/null +++ b/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js @@ -0,0 +1,31 @@ +"use strict"; + +const BUTTONID = "test-seenwidget-post-reset"; + +add_task(function*() { + CustomizableUI.createWidget({ + id: BUTTONID, + label: "Test widget seen post reset", + defaultArea: CustomizableUI.AREA_NAVBAR + }); + + const kPrefCustomizationState = "browser.uiCustomization.state"; + let bsPass = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should be seen after createWidget is called."); + CustomizableUI.reset(); + ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should still be seen after reset."); + ok(!Services.prefs.prefHasUserValue(kPrefCustomizationState), "Pref shouldn't be set right now, because that'd break undo."); + CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR); + gCustomizeMode.removeFromArea(document.getElementById(BUTTONID)); + let hasUserValue = Services.prefs.prefHasUserValue(kPrefCustomizationState); + ok(hasUserValue, "Pref should be set right now."); + if (hasUserValue) { + let seenArray = JSON.parse(Services.prefs.getCharPref(kPrefCustomizationState)).seen; + isnot(seenArray.indexOf(BUTTONID), -1, "Widget should be in saved 'seen' list."); + } +}); + +registerCleanupFunction(function() { + CustomizableUI.destroyWidget(BUTTONID); + CustomizableUI.reset(); +}); diff --git a/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js b/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js new file mode 100644 index 000000000..42768debf --- /dev/null +++ b/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js @@ -0,0 +1,78 @@ +"use strict"; + +// NB: This uses some ugly hacks to get into the CUI module from elsewhere... +// don't try this at home, kids. +function test() { + // Customize something to make sure stuff changed: + CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR); + + let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + + is(CustomizableUIBSPass.gFuturePlacements.size, 0, + "All future placements should be dealt with by now."); + + let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass; + + // Force us to have a saved state: + CustomizableUIInternal.saveState(); + CustomizableUIInternal.loadSavedState(); + + CustomizableUIInternal._introduceNewBuiltinWidgets(); + is(gFuturePlacements.size, 0, + "No change to future placements initially."); + + // Add our widget to the defaults: + let testWidgetNew = { + id: "test-messing-with-default-placements-new-pref", + label: "Test messing with default placements - pref-based", + defaultArea: CustomizableUI.AREA_NAVBAR, + introducedInVersion: "pref", + }; + + let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew, + CustomizableUI.SOURCE_BUILTIN); + ok(normalizedWidget, "Widget should be normalizable"); + if (!normalizedWidget) { + return; + } + CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget); + + // Now adjust default placements for area: + let navbarArea = CustomizableUIBSPass.gAreas.get(CustomizableUI.AREA_NAVBAR); + let navbarPlacements = navbarArea.get("defaultPlacements"); + navbarPlacements.splice(navbarPlacements.indexOf("bookmarks-menu-button") + 1, 0, testWidgetNew.id); + + let savedPlacements = CustomizableUIBSPass.gSavedState.placements[CustomizableUI.AREA_NAVBAR]; + // Then call the re-init routine so we re-add the builtin widgets + CustomizableUIInternal._introduceNewBuiltinWidgets(); + is(gFuturePlacements.size, 1, + "Should have 1 more future placement"); + let futureNavbarPlacements = gFuturePlacements.get(CustomizableUI.AREA_NAVBAR); + ok(futureNavbarPlacements, "Should have placements for nav-bar"); + if (futureNavbarPlacements) { + ok(futureNavbarPlacements.has(testWidgetNew.id), "widget should be in future placements"); + } + CustomizableUIInternal._placeNewDefaultWidgetsInArea(CustomizableUI.AREA_NAVBAR); + + let indexInSavedPlacements = savedPlacements.indexOf(testWidgetNew.id); + info("Saved placements: " + savedPlacements.join(', ')); + isnot(indexInSavedPlacements, -1, "Widget should have been inserted"); + is(indexInSavedPlacements, savedPlacements.indexOf("bookmarks-menu-button") + 1, + "Widget should be in the right place."); + + if (futureNavbarPlacements) { + ok(!futureNavbarPlacements.has(testWidgetNew.id), "widget should be out of future placements"); + } + + if (indexInSavedPlacements != -1) { + savedPlacements.splice(indexInSavedPlacements, 1); + } + + gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR); + let indexInDefaultPlacements = navbarPlacements.indexOf(testWidgetNew.id); + if (indexInDefaultPlacements != -1) { + navbarPlacements.splice(indexInDefaultPlacements, 1); + } + gPalette.delete(testWidgetNew.id); + CustomizableUI.reset(); +} diff --git a/browser/components/customizableui/test/browser_873501_handle_specials.js b/browser/components/customizableui/test/browser_873501_handle_specials.js new file mode 100644 index 000000000..b07c8e0d7 --- /dev/null +++ b/browser/components/customizableui/test/browser_873501_handle_specials.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarName = "test-specials-toolbar"; + +registerCleanupFunction(removeCustomToolbars); + +// Add a toolbar with two springs and the downloads button. +add_task(function* addToolbarWith2SpringsAndDownloadsButton() { + // Create the toolbar with a single spring: + createToolbarWithPlacements(kToolbarName, ["spring"]); + ok(document.getElementById(kToolbarName), "Toolbar should be created."); + + // Check it's there with a generated ID: + assertAreaPlacements(kToolbarName, [/customizableui-special-spring\d+/]); + let [springId] = getAreaWidgetIds(kToolbarName); + + // Add a second spring, check if that's there and doesn't share IDs + CustomizableUI.addWidgetToArea("spring", kToolbarName); + assertAreaPlacements(kToolbarName, [springId, + /customizableui-special-spring\d+/]); + let [, spring2Id] = getAreaWidgetIds(kToolbarName); + + isnot(springId, spring2Id, "Springs shouldn't have identical IDs."); + + // Try moving the downloads button to this new toolbar, between the two springs: + CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1); + assertAreaPlacements(kToolbarName, [springId, "downloads-button", spring2Id]); + yield removeCustomToolbars(); +}); + +// Add separators around the downloads button. +add_task(function* addSeparatorsAroundDownloadsButton() { + createToolbarWithPlacements(kToolbarName, ["separator"]); + ok(document.getElementById(kToolbarName), "Toolbar should be created."); + + // Check it's there with a generated ID: + assertAreaPlacements(kToolbarName, [/customizableui-special-separator\d+/]); + let [separatorId] = getAreaWidgetIds(kToolbarName); + + CustomizableUI.addWidgetToArea("separator", kToolbarName); + assertAreaPlacements(kToolbarName, [separatorId, + /customizableui-special-separator\d+/]); + let [, separator2Id] = getAreaWidgetIds(kToolbarName); + + isnot(separatorId, separator2Id, "Separator ids shouldn't be equal."); + + CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1); + assertAreaPlacements(kToolbarName, [separatorId, "downloads-button", separator2Id]); + yield removeCustomToolbars(); +}); + +// Add spacers around the downloads button. +add_task(function* addSpacersAroundDownloadsButton() { + createToolbarWithPlacements(kToolbarName, ["spacer"]); + ok(document.getElementById(kToolbarName), "Toolbar should be created."); + + // Check it's there with a generated ID: + assertAreaPlacements(kToolbarName, [/customizableui-special-spacer\d+/]); + let [spacerId] = getAreaWidgetIds(kToolbarName); + + CustomizableUI.addWidgetToArea("spacer", kToolbarName); + assertAreaPlacements(kToolbarName, [spacerId, + /customizableui-special-spacer\d+/]); + let [, spacer2Id] = getAreaWidgetIds(kToolbarName); + + isnot(spacerId, spacer2Id, "Spacer ids shouldn't be equal."); + + CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1); + assertAreaPlacements(kToolbarName, [spacerId, "downloads-button", spacer2Id]); + yield removeCustomToolbars(); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js new file mode 100644 index 000000000..a3204c271 --- /dev/null +++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js @@ -0,0 +1,185 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kXULWidgetId = "a-test-button"; // we'll create a button with this ID. +const kAPIWidgetId = "feed-button"; +const kPanel = CustomizableUI.AREA_PANEL; +const kToolbar = CustomizableUI.AREA_NAVBAR; +const kVisiblePalette = "customization-palette"; +const kPlaceholderClass = "panel-customization-placeholder"; + +function checkWrapper(id) { + is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window."); +} + +var move = { + "drag": function(id, target) { + let targetNode = document.getElementById(target); + if (targetNode.customizationTarget) { + targetNode = targetNode.customizationTarget; + } + simulateItemDrag(document.getElementById(id), targetNode); + }, + "dragToItem": function(id, target) { + let targetNode = document.getElementById(target); + if (targetNode.customizationTarget) { + targetNode = targetNode.customizationTarget; + } + let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")"); + if (target == kPanel) { + targetNode = items[items.length - 1]; + } else { + targetNode = items[0]; + } + simulateItemDrag(document.getElementById(id), targetNode); + }, + "API": function(id, target) { + if (target == kVisiblePalette) { + return CustomizableUI.removeWidgetFromArea(id); + } + return CustomizableUI.addWidgetToArea(id, target, null); + } +}; + +function isLast(containerId, defaultPlacements, id) { + assertAreaPlacements(containerId, defaultPlacements.concat([id])); + is(document.getElementById(containerId).customizationTarget.lastChild.firstChild.id, id, + "Widget " + id + " should be in " + containerId + " in customizing window."); + is(otherWin.document.getElementById(containerId).customizationTarget.lastChild.id, id, + "Widget " + id + " should be in " + containerId + " in other window."); +} + +function getLastVisibleNodeInToolbar(containerId, win=window) { + let container = win.document.getElementById(containerId).customizationTarget; + let rv = container.lastChild; + while (rv && (rv.getAttribute('hidden') == 'true' || (rv.firstChild && rv.firstChild.getAttribute('hidden') == 'true'))) { + rv = rv.previousSibling; + } + return rv; +} + +function isLastVisibleInToolbar(containerId, defaultPlacements, id) { + let newPlacements; + for (let i = defaultPlacements.length - 1; i >= 0; i--) { + let el = document.getElementById(defaultPlacements[i]); + if (el && el.getAttribute('hidden') != 'true') { + newPlacements = [...defaultPlacements]; + newPlacements.splice(i + 1, 0, id); + break; + } + } + if (!newPlacements) { + assertAreaPlacements(containerId, defaultPlacements.concat([id])); + } else { + assertAreaPlacements(containerId, newPlacements); + } + is(getLastVisibleNodeInToolbar(containerId).firstChild.id, id, + "Widget " + id + " should be in " + containerId + " in customizing window."); + is(getLastVisibleNodeInToolbar(containerId, otherWin).id, id, + "Widget " + id + " should be in " + containerId + " in other window."); +} + +function isFirst(containerId, defaultPlacements, id) { + assertAreaPlacements(containerId, [id].concat(defaultPlacements)); + is(document.getElementById(containerId).customizationTarget.firstChild.firstChild.id, id, + "Widget " + id + " should be in " + containerId + " in customizing window."); + is(otherWin.document.getElementById(containerId).customizationTarget.firstChild.id, id, + "Widget " + id + " should be in " + containerId + " in other window."); +} + +function checkToolbar(id, method) { + // Place at start of the toolbar: + let toolbarPlacements = getAreaWidgetIds(kToolbar); + move[method](id, kToolbar); + if (method == "dragToItem") { + isFirst(kToolbar, toolbarPlacements, id); + } else if (method == "drag") { + isLastVisibleInToolbar(kToolbar, toolbarPlacements, id); + } else { + isLast(kToolbar, toolbarPlacements, id); + } + checkWrapper(id); +} + +function checkPanel(id, method) { + let panelPlacements = getAreaWidgetIds(kPanel); + move[method](id, kPanel); + let children = document.getElementById(kPanel).querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")"); + let otherChildren = otherWin.document.getElementById(kPanel).children; + let newPlacements = panelPlacements.concat([id]); + // Relative position of the new item from the end: + let position = -1; + // For the drag to item case, we drag to the last item, making the dragged item the + // penultimate item. We can't well use the first item because the panel has complicated + // rules about rearranging wide items (which, by default, the first two items are). + if (method == "dragToItem") { + newPlacements.pop(); + newPlacements.splice(panelPlacements.length - 1, 0, id); + position = -2; + } + assertAreaPlacements(kPanel, newPlacements); + is(children[children.length + position].firstChild.id, id, + "Widget " + id + " should be in " + kPanel + " in customizing window."); + is(otherChildren[otherChildren.length + position].id, id, + "Widget " + id + " should be in " + kPanel + " in other window."); + checkWrapper(id); +} + +function checkPalette(id, method) { + // Move back to palette: + move[method](id, kVisiblePalette); + ok(CustomizableUI.inDefaultState, "Should end in default state"); + let visibleChildren = gCustomizeMode.visiblePalette.children; + let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1]; + is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window."); + if (id == kXULWidgetId) { + ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window."); + } + checkWrapper(id); +} + +// This test needs a XUL button that's in the palette by default. No such +// button currently exists, so we create a simple one. +function createXULButtonForWindow(win) { + createDummyXULButton(kXULWidgetId, "test-button", win); +} + +function removeXULButtonForWindow(win) { + win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove(); +} + +var otherWin; + +// Moving widgets in two windows, one with customize mode and one without, should work. +add_task(function* MoveWidgetsInTwoWindows() { + yield startCustomizing(); + otherWin = yield openAndLoadWindow(null, true); + yield otherWin.PanelUI.ensureReady(); + // Create the XUL button to use in the test in both windows. + createXULButtonForWindow(window); + createXULButtonForWindow(otherWin); + ok(CustomizableUI.inDefaultState, "Should start in default state"); + + for (let widgetId of [kXULWidgetId, kAPIWidgetId]) { + for (let method of ["API", "drag", "dragToItem"]) { + info("Moving widget " + widgetId + " using " + method); + checkToolbar(widgetId, method); + checkPanel(widgetId, method); + checkPalette(widgetId, method); + checkPanel(widgetId, method); + checkToolbar(widgetId, method); + checkPalette(widgetId, method); + } + } + yield promiseWindowClosed(otherWin); + otherWin = null; + yield endCustomizing(); + removeXULButtonForWindow(window); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js new file mode 100644 index 000000000..ec454dc8d --- /dev/null +++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kTestWidget1 = "test-customize-mode-create-destroy1"; +const kTestWidget2 = "test-customize-mode-create-destroy2"; + +// Creating and destroying a widget should correctly wrap/unwrap stuff +add_task(function* testWrapUnwrap() { + yield startCustomizing(); + CustomizableUI.createWidget({id: kTestWidget1, label: 'Pretty label', tooltiptext: 'Pretty tooltip'}); + let elem = document.getElementById(kTestWidget1); + let wrapper = document.getElementById("wrapper-" + kTestWidget1); + ok(elem, "There should be an item"); + ok(wrapper, "There should be a wrapper"); + is(wrapper.firstChild.id, kTestWidget1, "Wrapper should have test widget"); + is(wrapper.parentNode.id, "customization-palette", "Wrapper should be in palette"); + CustomizableUI.destroyWidget(kTestWidget1); + wrapper = document.getElementById("wrapper-" + kTestWidget1); + ok(!wrapper, "There should be a wrapper"); + let item = document.getElementById(kTestWidget1); + ok(!item, "There should no longer be an item"); +}); + +// Creating and destroying a widget should correctly deal with panel placeholders +add_task(function* testPanelPlaceholders() { + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + // The value of expectedPlaceholders depends on the default palette layout. + // Bug 1229236 is for these tests to be smarter so the test doesn't need to + // change when the default placements change. + let expectedPlaceholders = 1 + (isInDevEdition() ? 1 : 0); + is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct."); + CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL}); + let elem = document.getElementById(kTestWidget2); + let wrapper = document.getElementById("wrapper-" + kTestWidget2); + ok(elem, "There should be an item"); + ok(wrapper, "There should be a wrapper"); + is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget"); + is(wrapper.parentNode, panel, "Wrapper should be in panel"); + expectedPlaceholders = isInDevEdition() ? 1 : 3; + is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct."); + CustomizableUI.destroyWidget(kTestWidget2); + wrapper = document.getElementById("wrapper-" + kTestWidget2); + ok(!wrapper, "There should be a wrapper"); + let item = document.getElementById(kTestWidget2); + ok(!item, "There should no longer be an item"); + yield endCustomizing(); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + try { + CustomizableUI.destroyWidget(kTestWidget1); + } catch (ex) {} + try { + CustomizableUI.destroyWidget(kTestWidget2); + } catch (ex) {} + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_877006_missing_view.js b/browser/components/customizableui/test/browser_877006_missing_view.js new file mode 100644 index 000000000..a1495c1fe --- /dev/null +++ b/browser/components/customizableui/test/browser_877006_missing_view.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Should be able to add broken view widget +add_task(function testAddbrokenViewWidget() { + const kWidgetId = 'test-877006-broken-widget'; + let widgetSpec = { + id: kWidgetId, + type: 'view', + viewId: 'idontexist', + /* Empty handler so we try to attach it maybe? */ + onViewShowing: function() { + } + }; + + let noError = true; + try { + CustomizableUI.createWidget(widgetSpec); + CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Should not throw an exception trying to add a broken view widget."); + + noError = true; + try { + CustomizableUI.destroyWidget(kWidgetId); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Should not throw an exception trying to remove the broken view widget."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_877178_unregisterArea.js b/browser/components/customizableui/test/browser_877178_unregisterArea.js new file mode 100644 index 000000000..28037787b --- /dev/null +++ b/browser/components/customizableui/test/browser_877178_unregisterArea.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +registerCleanupFunction(removeCustomToolbars); + +// Sanity checks +add_task(function sanityChecks() { + SimpleTest.doesThrow(() => CustomizableUI.registerArea("@foo"), + "Registering areas with an invalid ID should throw."); + + SimpleTest.doesThrow(() => CustomizableUI.registerArea([]), + "Registering areas with an invalid ID should throw."); + + SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("@foo"), + "Unregistering areas with an invalid ID should throw."); + + SimpleTest.doesThrow(() => CustomizableUI.unregisterArea([]), + "Unregistering areas with an invalid ID should throw."); + + SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("unknown"), + "Unregistering an area that's not registered should throw."); +}); + +// Check areas are loaded with their default placements. +add_task(function checkLoadedAres() { + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); +}); + +// Check registering and unregistering a new area. +add_task(function checkRegisteringAndUnregistering() { + const kToolbarId = "test-registration-toolbar"; + const kButtonId = "test-registration-button"; + createDummyXULButton(kButtonId); + createToolbarWithPlacements(kToolbarId, ["spring", kButtonId, "spring"]); + assertAreaPlacements(kToolbarId, + [/customizableui-special-spring\d+/, + kButtonId, + /customizableui-special-spring\d+/]); + ok(!CustomizableUI.inDefaultState, "With a new toolbar it is no longer in a default state."); + removeCustomToolbars(); // Will call unregisterArea for us + ok(CustomizableUI.inDefaultState, "When the toolbar is unregistered, " + + "everything will return to the default state."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_877447_skip_missing_ids.js b/browser/components/customizableui/test/browser_877447_skip_missing_ids.js new file mode 100644 index 000000000..0cba7ae4f --- /dev/null +++ b/browser/components/customizableui/test/browser_877447_skip_missing_ids.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +registerCleanupFunction(removeCustomToolbars); + +add_task(function skipMissingIDS() { + const kButtonId = "look-at-me-disappear-button"; + CustomizableUI.reset(); + ok(CustomizableUI.inDefaultState, "Should be in the default state."); + let btn = createDummyXULButton(kButtonId, "Gone!"); + CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_NAVBAR); + ok(!CustomizableUI.inDefaultState, "Should no longer be in the default state."); + is(btn.parentNode.parentNode.id, CustomizableUI.AREA_NAVBAR, "Button should be in navbar"); + btn.remove(); + is(btn.parentNode, null, "Button is no longer in the navbar"); + ok(CustomizableUI.inDefaultState, "Should be back in the default state, " + + "despite unknown button ID in placements."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_878452_drag_to_panel.js b/browser/components/customizableui/test/browser_878452_drag_to_panel.js new file mode 100644 index 000000000..8a8d82294 --- /dev/null +++ b/browser/components/customizableui/test/browser_878452_drag_to_panel.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Dragging an item from the palette to another button in the panel should work. +add_task(function*() { + yield startCustomizing(); + let btn = document.getElementById("feed-button"); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + + let lastButtonIndex = placements.length - 1; + let lastButton = placements[lastButtonIndex]; + let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]); + let lastButtonNode = document.getElementById(lastButton); + simulateItemDrag(btn, lastButtonNode); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(btn, palette); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging an item from the palette to the panel itself should also work. +add_task(function*() { + yield startCustomizing(); + let btn = document.getElementById("feed-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + + let placementsAfterAppend = placements.concat(["feed-button"]); + simulateItemDrag(btn, panel); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(btn, palette); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging an item from the palette to an empty panel should also work. +add_task(function*() { + let widgetIds = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + while (widgetIds.length) { + CustomizableUI.removeWidgetFromArea(widgetIds.shift()); + } + yield startCustomizing(); + let btn = document.getElementById("feed-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + + assertAreaPlacements(panel.id, []); + + let placementsAfterAppend = ["feed-button"]; + simulateItemDrag(btn, panel); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(btn, palette); + assertAreaPlacements(panel.id, []); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_880164_customization_context_menus.js b/browser/components/customizableui/test/browser_880164_customization_context_menus.js new file mode 100644 index 000000000..57a0db773 --- /dev/null +++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +const isOSX = (Services.appinfo.OS === "Darwin"); + +// Right-click on the home button should +// show a context menu with options to move it. +add_task(function*() { + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownPromise = popupShown(contextMenu); + let homeButton = document.getElementById("home-button"); + EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 }); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToPanel", true], + [".customize-context-removeFromToolbar", true], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; +}); + +// Right-click on an empty bit of tabstrip should +// show a context menu without options to move it, +// but with tab-specific options instead. +add_task(function*() { + // ensure there are tabs to reload/bookmark: + let extraTab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(extraTab, "http://example.com/"); + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownPromise = popupShown(contextMenu); + let tabstrip = document.getElementById("tabbrowser-tabs"); + let rect = tabstrip.getBoundingClientRect(); + EventUtils.synthesizeMouse(tabstrip, rect.width - 2, 2, {type: "contextmenu", button: 2 }); + yield shownPromise; + + let closedTabsAvailable = SessionStore.getClosedTabCount(window) == 0; + info("Closed tabs: " + closedTabsAvailable); + let expectedEntries = [ + ["#toolbar-context-reloadAllTabs", true], + ["#toolbar-context-bookmarkAllTabs", true], + ["#toolbar-context-undoCloseTab", !closedTabsAvailable], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; + gBrowser.removeTab(extraTab); +}); + +// Right-click on an empty bit of extra toolbar should +// show a context menu with moving options disabled, +// and a toggle option for the extra toolbar +add_task(function*() { + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownPromise = popupShown(contextMenu); + let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []); + toolbar.setAttribute("context", "toolbar-context-menu"); + toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu"); + EventUtils.synthesizeMouseAtCenter(toolbar, {type: "contextmenu", button: 2 }); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToPanel", false], + [".customize-context-removeFromToolbar", false], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["#toggle_880164_empty_toolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; + removeCustomToolbars(); +}); + + +// Right-click on the urlbar-container should +// show a context menu with disabled options to move it. +add_task(function*() { + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownPromise = popupShown(contextMenu); + let urlBarContainer = document.getElementById("urlbar-container"); + // Need to make sure not to click within an edit field. + EventUtils.synthesizeMouse(urlBarContainer, 100, 1, {type: "contextmenu", button: 2 }); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToPanel", false], + [".customize-context-removeFromToolbar", false], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; +}); + +// Right-click on the searchbar and moving it to the menu +// and back should move the search-container instead. +add_task(function*() { + let searchbar = document.getElementById("searchbar"); + gCustomizeMode.addToPanel(searchbar); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel"); + + let shownPanelPromise = promisePanelShown(window); + PanelUI.toggle({type: "command"}); + yield shownPanelPromise; + let hiddenPanelPromise = promisePanelHidden(window); + PanelUI.toggle({type: "command"}); + yield hiddenPanelPromise; + + gCustomizeMode.addToToolbar(searchbar); + placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar"); + gCustomizeMode.removeFromArea(searchbar); + placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement, null, "Should be in palette"); + CustomizableUI.reset(); + placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar"); +}); + +// Right-click on an item within the menu panel should +// show a context menu with options to move it. +add_task(function*() { + let shownPanelPromise = promisePanelShown(window); + PanelUI.toggle({type: "command"}); + yield shownPanelPromise; + + let contextMenu = document.getElementById("customizationPanelItemContextMenu"); + let shownContextPromise = popupShown(contextMenu); + let newWindowButton = document.getElementById("new-window-button"); + ok(newWindowButton, "new-window-button was found"); + EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownContextPromise; + + is(PanelUI.panel.state, "open", "The PanelUI should still be open."); + + let expectedEntries = [ + [".customize-context-moveToToolbar", true], + [".customize-context-removeFromPanel", true], + ["---"], + [".viewCustomizeToolbar", true] + ]; + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; + + let hiddenPromise = promisePanelHidden(window); + PanelUI.toggle({type: "command"}); + yield hiddenPromise; +}); + +// Right-click on the home button while in customization mode +// should show a context menu with options to move it. +add_task(function*() { + yield startCustomizing(); + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownPromise = popupShown(contextMenu); + let homeButton = document.getElementById("wrapper-home-button"); + EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToPanel", true], + [".customize-context-removeFromToolbar", true], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", false] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; +}); + +// Right-click on an item in the palette should +// show a context menu with options to move it. +add_task(function*() { + let contextMenu = document.getElementById("customizationPaletteItemContextMenu"); + let shownPromise = popupShown(contextMenu); + let openFileButton = document.getElementById("wrapper-open-file-button"); + EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-addToToolbar", true], + [".customize-context-addToPanel", true] + ]; + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; +}); + +// Right-click on an item in the panel while in customization mode +// should show a context menu with options to move it. +add_task(function*() { + let contextMenu = document.getElementById("customizationPanelItemContextMenu"); + let shownPromise = popupShown(contextMenu); + let newWindowButton = document.getElementById("wrapper-new-window-button"); + EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToToolbar", true], + [".customize-context-removeFromPanel", true], + ["---"], + [".viewCustomizeToolbar", false] + ]; + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; + yield endCustomizing(); +}); + +// Test the toolbarbutton panel context menu in customization mode +// without opening the panel before customization mode +add_task(function*() { + this.otherWin = yield openAndLoadWindow(null, true); + + yield new Promise(resolve => waitForFocus(resolve, this.otherWin)); + + yield startCustomizing(this.otherWin); + + let contextMenu = this.otherWin.document.getElementById("customizationPanelItemContextMenu"); + let shownPromise = popupShown(contextMenu); + let newWindowButton = this.otherWin.document.getElementById("wrapper-new-window-button"); + EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToToolbar", true], + [".customize-context-removeFromPanel", true], + ["---"], + [".viewCustomizeToolbar", false] + ]; + checkContextMenu(contextMenu, expectedEntries, this.otherWin); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; + yield endCustomizing(this.otherWin); + yield promiseWindowClosed(this.otherWin); + this.otherWin = null; + + yield new Promise(resolve => waitForFocus(resolve, window)); +}); + +// Bug 945191 - Combined buttons show wrong context menu options +// when they are in the toolbar. +add_task(function*() { + yield startCustomizing(); + let contextMenu = document.getElementById("customizationPanelItemContextMenu"); + let shownPromise = popupShown(contextMenu); + let zoomControls = document.getElementById("wrapper-zoom-controls"); + EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + // Execute the command to move the item from the panel to the toolbar. + contextMenu.childNodes[0].doCommand(); + let hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; + yield endCustomizing(); + + zoomControls = document.getElementById("zoom-controls"); + is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar"); + + contextMenu = document.getElementById("toolbar-context-menu"); + shownPromise = popupShown(contextMenu); + EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + + let expectedEntries = [ + [".customize-context-moveToPanel", true], + [".customize-context-removeFromToolbar", true], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + hiddenPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenPromise; + yield resetCustomization(); +}); + +// Bug 947586 - After customization, panel items show wrong context menu options +add_task(function*() { + yield startCustomizing(); + yield endCustomizing(); + + yield PanelUI.show(); + + let contextMenu = document.getElementById("customizationPanelItemContextMenu"); + let shownContextPromise = popupShown(contextMenu); + let newWindowButton = document.getElementById("new-window-button"); + ok(newWindowButton, "new-window-button was found"); + EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownContextPromise; + + is(PanelUI.panel.state, "open", "The PanelUI should still be open."); + + let expectedEntries = [ + [".customize-context-moveToToolbar", true], + [".customize-context-removeFromPanel", true], + ["---"], + [".viewCustomizeToolbar", true] + ]; + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; + + let hiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield hiddenPromise; +}); + + +// Bug 982027 - moving icon around removes custom context menu. +add_task(function*() { + let widgetId = "custom-context-menu-toolbarbutton"; + let expectedContext = "myfancycontext"; + let widget = createDummyXULButton(widgetId, "Test ctxt menu"); + widget.setAttribute("context", expectedContext); + CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR); + is(widget.getAttribute("context"), expectedContext, "Should have context menu when added to the toolbar."); + + yield startCustomizing(); + is(widget.getAttribute("context"), "", "Should not have own context menu in the toolbar now that we're customizing."); + is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped when in toolbar."); + + let panel = PanelUI.contents; + simulateItemDrag(widget, panel); + is(widget.getAttribute("context"), "", "Should not have own context menu when in the panel."); + is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're in the panel."); + + simulateItemDrag(widget, document.getElementById("nav-bar").customizationTarget); + is(widget.getAttribute("context"), "", "Should not have own context menu when back in toolbar because we're still customizing."); + is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're back in the toolbar."); + + yield endCustomizing(); + is(widget.getAttribute("context"), expectedContext, "Should have context menu again now that we're out of customize mode."); + CustomizableUI.removeWidgetFromArea(widgetId); + widget.remove(); + ok(CustomizableUI.inDefaultState, "Should be in default state after removing button."); +}); diff --git a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js new file mode 100644 index 000000000..9057d0557 --- /dev/null +++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js @@ -0,0 +1,497 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(5); + +// Dragging the zoom controls to be before the print button should not move any controls. +add_task(function*() { + yield startCustomizing(); + let zoomControls = document.getElementById("zoom-controls"); + let printButton = document.getElementById("print-button"); + let placementsAfterMove = ["edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "zoom-controls", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(zoomControls, printButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let newWindowButton = document.getElementById("new-window-button"); + simulateItemDrag(zoomControls, newWindowButton); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging the zoom controls to be before the save button should not move any controls. +add_task(function*() { + yield startCustomizing(); + let zoomControls = document.getElementById("zoom-controls"); + let savePageButton = document.getElementById("save-page-button"); + let placementsAfterMove = ["edit-controls", + "zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(zoomControls, savePageButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(CustomizableUI.inDefaultState, "Should be in default state."); +}); + + +// Dragging the zoom controls to be before the new-window button should not move any widgets. +add_task(function*() { + yield startCustomizing(); + let zoomControls = document.getElementById("zoom-controls"); + let newWindowButton = document.getElementById("new-window-button"); + let placementsAfterMove = ["edit-controls", + "zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(zoomControls, newWindowButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the zoom controls to be before the history-panelmenu should move the zoom-controls in to the row higher than the history-panelmenu. +add_task(function*() { + yield startCustomizing(); + let zoomControls = document.getElementById("zoom-controls"); + let historyPanelMenu = document.getElementById("history-panelmenu"); + let placementsAfterMove = ["edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "zoom-controls", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(zoomControls, historyPanelMenu); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let newWindowButton = document.getElementById("new-window-button"); + simulateItemDrag(zoomControls, newWindowButton); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging the zoom controls to be before the preferences-button should move the zoom-controls +// in to the row higher than the preferences-button. +add_task(function*() { + yield startCustomizing(); + let zoomControls = document.getElementById("zoom-controls"); + let preferencesButton = document.getElementById("preferences-button"); + let placementsAfterMove = ["edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "zoom-controls", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(zoomControls, preferencesButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let newWindowButton = document.getElementById("new-window-button"); + simulateItemDrag(zoomControls, newWindowButton); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging an item from the palette to before the zoom-controls should move it and two other buttons before the zoom controls. +add_task(function*() { + yield startCustomizing(); + let openFileButton = document.getElementById("open-file-button"); + let zoomControls = document.getElementById("zoom-controls"); + let placementsAfterInsert = ["edit-controls", + "open-file-button", + "new-window-button", + "privatebrowsing-button", + "zoom-controls", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterInsert); + simulateItemDrag(openFileButton, zoomControls); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let palette = document.getElementById("customization-palette"); + // Check that the palette items are re-wrapped correctly. + let feedWrapper = document.getElementById("wrapper-feed-button"); + let feedButton = document.getElementById("feed-button"); + is(feedButton.parentNode, feedWrapper, + "feed-button should be a child of wrapper-feed-button"); + is(feedWrapper.getAttribute("place"), "palette", + "The feed-button wrapper should have it's place set to 'palette'"); + simulateItemDrag(openFileButton, palette); + is(openFileButton.parentNode.tagName, "toolbarpaletteitem", + "The open-file-button should be wrapped by a toolbarpaletteitem"); + let newWindowButton = document.getElementById("new-window-button"); + simulateItemDrag(zoomControls, newWindowButton); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging an item from the palette to before the edit-controls +// should move it and two other buttons before the edit and zoom controls. +add_task(function*() { + yield startCustomizing(); + let openFileButton = document.getElementById("open-file-button"); + let editControls = document.getElementById("edit-controls"); + let placementsAfterInsert = ["open-file-button", + "new-window-button", + "privatebrowsing-button", + "edit-controls", + "zoom-controls", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterInsert); + simulateItemDrag(openFileButton, editControls); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + let palette = document.getElementById("customization-palette"); + // Check that the palette items are re-wrapped correctly. + let feedWrapper = document.getElementById("wrapper-feed-button"); + let feedButton = document.getElementById("feed-button"); + is(feedButton.parentNode, feedWrapper, + "feed-button should be a child of wrapper-feed-button"); + is(feedWrapper.getAttribute("place"), "palette", + "The feed-button wrapper should have it's place set to 'palette'"); + simulateItemDrag(openFileButton, palette); + is(openFileButton.parentNode.tagName, "toolbarpaletteitem", + "The open-file-button should be wrapped by a toolbarpaletteitem"); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Dragging the edit-controls to be before the zoom-controls button +// should not move any widgets. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let zoomControls = document.getElementById("zoom-controls"); + let placementsAfterMove = ["edit-controls", + "zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, zoomControls); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to be before the new-window-button should +// move the zoom-controls before the edit-controls. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let newWindowButton = document.getElementById("new-window-button"); + let placementsAfterMove = ["zoom-controls", + "edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, newWindowButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to be before the privatebrowsing-button +// should move the edit-controls in to the row higher than the +// privatebrowsing-button. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let privateBrowsingButton = document.getElementById("privatebrowsing-button"); + let placementsAfterMove = ["zoom-controls", + "edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, privateBrowsingButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to be before the save-page-button +// should move the edit-controls in to the row higher than the +// save-page-button. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let savePageButton = document.getElementById("save-page-button"); + let placementsAfterMove = ["zoom-controls", + "edit-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, savePageButton); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to the panel itself should append +// the edit controls to the bottom of the panel. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let placementsAfterMove = ["zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "edit-controls", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, panel); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to the customization-palette and +// back should work. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let palette = document.getElementById("customization-palette"); + let placementsAfterMove = ["zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "developer-button", + "sync-button", + ]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + let paletteChildElementCount = palette.childElementCount; + simulateItemDrag(editControls, palette); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + is(paletteChildElementCount + 1, palette.childElementCount, + "The palette should have a new child, congratulations!"); + is(editControls.parentNode.id, "wrapper-edit-controls", + "The edit-controls should be properly wrapped."); + is(editControls.parentNode.getAttribute("place"), "palette", + "The edit-controls should have the place of 'palette'."); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + is(paletteChildElementCount, palette.childElementCount, + "The palette child count should have returned to its prior value."); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging the edit-controls to each of the panel placeholders +// should append the edit-controls to the bottom of the panel. +add_task(function*() { + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let numPlaceholders = 2; + for (let i = 0; i < numPlaceholders; i++) { + // This test relies on there being a specific number of widgets in the + // panel. The addition of sync-button screwed this up, so we remove it + // here. We should either fix the tests to not rely on the specific layout, + // or fix bug 1007910 which would change the placeholder logic in different + // ways. Bug 1229236 is for these tests to be smarter. + CustomizableUI.removeWidgetFromArea("sync-button"); + // NB: We can't just iterate over all of the placeholders + // because each drag-drop action recreates them. + let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i]; + let placementsAfterMove = ["zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "edit-controls", + "developer-button"]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, placeholder); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(editControls, zoomControls); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); + } +}); + +// Dragging the open-file-button back on to itself should work. +add_task(function*() { + yield startCustomizing(); + let openFileButton = document.getElementById("open-file-button"); + is(openFileButton.parentNode.tagName, "toolbarpaletteitem", + "open-file-button should be wrapped by a toolbarpaletteitem"); + simulateItemDrag(openFileButton, openFileButton); + is(openFileButton.parentNode.tagName, "toolbarpaletteitem", + "open-file-button should be wrapped by a toolbarpaletteitem"); + let editControls = document.getElementById("edit-controls"); + is(editControls.parentNode.tagName, "toolbarpaletteitem", + "edit-controls should be wrapped by a toolbarpaletteitem"); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +// Dragging a small button onto the last big button should work. +add_task(function*() { + // Bug 1007910 requires there be a placeholder on the final row for this + // test to work as written. The addition of sync-button meant that's not true + // so we remove it from here. Bug 1229236 is for these tests to be smarter. + CustomizableUI.removeWidgetFromArea("sync-button"); + yield startCustomizing(); + let editControls = document.getElementById("edit-controls"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let target = panel.getElementsByClassName("panel-customization-placeholder")[0]; + let placementsAfterMove = ["zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + "edit-controls", + "developer-button"]; + removeDeveloperButtonIfDevEdition(placementsAfterMove); + simulateItemDrag(editControls, target); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + let itemToDrag = "email-link-button"; // any button in the palette by default. + let button = document.getElementById(itemToDrag); + placementsAfterMove.splice(11, 0, itemToDrag); + simulateItemDrag(button, editControls); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); + + // Put stuff back: + let palette = document.getElementById("customization-palette"); + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(button, palette); + simulateItemDrag(editControls, zoomControls); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_884402_customize_from_overflow.js b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js new file mode 100644 index 000000000..f50767c06 --- /dev/null +++ b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js @@ -0,0 +1,81 @@ +"use strict"; + +var overflowPanel = document.getElementById("widget-overflow"); + +const isOSX = (Services.appinfo.OS === "Darwin"); + +var originalWindowWidth; +registerCleanupFunction(function() { + overflowPanel.removeAttribute("animate"); + window.resizeTo(originalWindowWidth, window.outerHeight); +}); + +// Right-click on an item within the overflow panel should +// show a context menu with options to move it. +add_task(function*() { + + overflowPanel.setAttribute("animate", "false"); + + originalWindowWidth = window.outerWidth; + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + window.resizeTo(400, window.outerHeight); + + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + let chevron = document.getElementById("nav-bar-overflow-button"); + let shownPanelPromise = promisePanelElementShown(window, overflowPanel); + chevron.click(); + yield shownPanelPromise; + + let contextMenu = document.getElementById("toolbar-context-menu"); + let shownContextPromise = popupShown(contextMenu); + let homeButton = document.getElementById("home-button"); + ok(homeButton, "home-button was found"); + is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing"); + EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownContextPromise; + + is(overflowPanel.state, "open", "The widget overflow panel should still be open."); + + let expectedEntries = [ + [".customize-context-moveToPanel", true], + [".customize-context-removeFromToolbar", true], + ["---"] + ]; + if (!isOSX) { + expectedEntries.push(["#toggle_toolbar-menubar", true]); + } + expectedEntries.push( + ["#toggle_PersonalToolbar", true], + ["---"], + [".viewCustomizeToolbar", true] + ); + checkContextMenu(contextMenu, expectedEntries); + + let hiddenContextPromise = popupHidden(contextMenu); + let hiddenPromise = promisePanelElementHidden(window, overflowPanel); + let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel"); + if (moveToPanel) { + moveToPanel.click(); + } + contextMenu.hidePopup(); + yield hiddenContextPromise; + yield hiddenPromise; + + let homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button"); + ok(homeButtonPlacement, "Home button should still have a placement"); + is(homeButtonPlacement && homeButtonPlacement.area, "PanelUI-contents", "Home button should be in the panel now"); + CustomizableUI.reset(); + + // In some cases, it can take a tick for the navbar to overflow again. Wait for it: + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button"); + ok(homeButtonPlacement, "Home button should still have a placement"); + is(homeButtonPlacement && homeButtonPlacement.area, "nav-bar", "Home button should be back in the navbar now"); + + is(homeButton.getAttribute("overflowedItem"), "true", "Home button should still be overflowed"); +}); diff --git a/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js new file mode 100644 index 000000000..ea6f5a4e3 --- /dev/null +++ b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function isFullscreenSizeMode() { + let sizemode = document.documentElement.getAttribute("sizemode"); + return sizemode == "fullscreen"; +} + +// Observers should be disabled when in customization mode. +add_task(function*() { + // Open and close the panel to make sure that the + // area is generated before getting a child of the area. + let shownPanelPromise = promisePanelShown(window); + PanelUI.toggle({type: "command"}); + yield shownPanelPromise; + let hiddenPanelPromise = promisePanelHidden(window); + PanelUI.toggle({type: "command"}); + yield hiddenPanelPromise; + + let fullscreenButton = document.getElementById("fullscreen-button"); + ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.") + ok(!isFullscreenSizeMode(), "Should not be in fullscreen sizemode before we enter fullscreen."); + + BrowserFullScreen(); + yield waitForCondition(() => isFullscreenSizeMode()); + ok(fullscreenButton.checked, "Fullscreen button should be checked when in fullscreen.") + + yield startCustomizing(); + + let fullscreenButtonWrapper = document.getElementById("wrapper-fullscreen-button"); + ok(fullscreenButtonWrapper.hasAttribute("itemobserves"), "Observer should be moved to wrapper"); + fullscreenButton = document.getElementById("fullscreen-button"); + ok(!fullscreenButton.hasAttribute("observes"), "Observer should be removed from button"); + ok(!fullscreenButton.checked, "Fullscreen button should no longer be checked during customization mode"); + + yield endCustomizing(); + + BrowserFullScreen(); + fullscreenButton = document.getElementById("fullscreen-button"); + yield waitForCondition(() => !isFullscreenSizeMode()); + ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.") +}); diff --git a/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js b/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js new file mode 100644 index 000000000..e55c21862 --- /dev/null +++ b/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "some-widget"; + +function assertWidgetExists(aWindow, aExists) { + if (aExists) { + ok(aWindow.document.getElementById(kWidgetId), + "Should have found test widget in the window"); + } else { + is(aWindow.document.getElementById(kWidgetId), null, + "Should not have found test widget in the window"); + } +} + +// A widget that is created with showInPrivateBrowsing undefined should +// have that value default to true. +add_task(function() { + let wrapper = CustomizableUI.createWidget({ + id: kWidgetId + }); + ok(wrapper.showInPrivateBrowsing, + "showInPrivateBrowsing should have defaulted to true."); + CustomizableUI.destroyWidget(kWidgetId); +}); + +// Add a widget via the API with showInPrivateBrowsing set to false +// and ensure it does not appear in pre-existing or newly created +// private windows. +add_task(function*() { + let plain1 = yield openAndLoadWindow(); + let private1 = yield openAndLoadWindow({private: true}); + CustomizableUI.createWidget({ + id: kWidgetId, + removable: true, + showInPrivateBrowsing: false + }); + CustomizableUI.addWidgetToArea(kWidgetId, + CustomizableUI.AREA_NAVBAR); + assertWidgetExists(plain1, true); + assertWidgetExists(private1, false); + + // Now open up some new windows. The widget should exist in the new + // plain window, but not the new private window. + let plain2 = yield openAndLoadWindow(); + let private2 = yield openAndLoadWindow({private: true}); + assertWidgetExists(plain2, true); + assertWidgetExists(private2, false); + + // Try moving the widget around and make sure it doesn't get added + // to the private windows. We'll start by appending it to the tabstrip. + CustomizableUI.addWidgetToArea(kWidgetId, + CustomizableUI.AREA_TABSTRIP); + assertWidgetExists(plain1, true); + assertWidgetExists(plain2, true); + assertWidgetExists(private1, false); + assertWidgetExists(private2, false); + + // And then move it to the beginning of the tabstrip. + CustomizableUI.moveWidgetWithinArea(kWidgetId, 0); + assertWidgetExists(plain1, true); + assertWidgetExists(plain2, true); + assertWidgetExists(private1, false); + assertWidgetExists(private2, false); + + CustomizableUI.removeWidgetFromArea("some-widget"); + assertWidgetExists(plain1, false); + assertWidgetExists(plain2, false); + assertWidgetExists(private1, false); + assertWidgetExists(private2, false); + + yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed)); + + CustomizableUI.destroyWidget("some-widget"); +}); + +// Add a widget via the API with showInPrivateBrowsing set to true, +// and ensure that it appears in pre-existing or newly created +// private browsing windows. +add_task(function*() { + let plain1 = yield openAndLoadWindow(); + let private1 = yield openAndLoadWindow({private: true}); + + CustomizableUI.createWidget({ + id: kWidgetId, + removable: true, + showInPrivateBrowsing: true + }); + CustomizableUI.addWidgetToArea(kWidgetId, + CustomizableUI.AREA_NAVBAR); + assertWidgetExists(plain1, true); + assertWidgetExists(private1, true); + + // Now open up some new windows. The widget should exist in the new + // plain window, but not the new private window. + let plain2 = yield openAndLoadWindow(); + let private2 = yield openAndLoadWindow({private: true}); + + assertWidgetExists(plain2, true); + assertWidgetExists(private2, true); + + // Try moving the widget around and make sure it doesn't get added + // to the private windows. We'll start by appending it to the tabstrip. + CustomizableUI.addWidgetToArea(kWidgetId, + CustomizableUI.AREA_TABSTRIP); + assertWidgetExists(plain1, true); + assertWidgetExists(plain2, true); + assertWidgetExists(private1, true); + assertWidgetExists(private2, true); + + // And then move it to the beginning of the tabstrip. + CustomizableUI.moveWidgetWithinArea(kWidgetId, 0); + assertWidgetExists(plain1, true); + assertWidgetExists(plain2, true); + assertWidgetExists(private1, true); + assertWidgetExists(private2, true); + + CustomizableUI.removeWidgetFromArea("some-widget"); + assertWidgetExists(plain1, false); + assertWidgetExists(plain2, false); + assertWidgetExists(private1, false); + assertWidgetExists(private2, false); + + yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed)); + + CustomizableUI.destroyWidget("some-widget"); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js b/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js new file mode 100644 index 000000000..f46141c4f --- /dev/null +++ b/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kButtonId = "test-886323-removable-moved-node"; +const kLazyAreaId = "test-886323-lazy-area-for-removability-testing"; + +var gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR); +var gLazyArea; + +// Removable nodes shouldn't be moved by buildArea +add_task(function*() { + let dummyBtn = createDummyXULButton(kButtonId, "Dummy"); + dummyBtn.setAttribute("removable", "true"); + gNavBar.customizationTarget.appendChild(dummyBtn); + let popupSet = document.getElementById("mainPopupSet"); + gLazyArea = document.createElementNS(kNSXUL, "panel"); + gLazyArea.id = kLazyAreaId; + gLazyArea.setAttribute("hidden", "true"); + popupSet.appendChild(gLazyArea); + CustomizableUI.registerArea(kLazyAreaId, { + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: [] + }); + CustomizableUI.addWidgetToArea(kButtonId, kLazyAreaId); + assertAreaPlacements(kLazyAreaId, [kButtonId], + "Placements should have changed because widget is removable."); + let btn = document.getElementById(kButtonId); + btn.setAttribute("removable", "false"); + gLazyArea.customizationTarget = gLazyArea; + CustomizableUI.registerToolbarNode(gLazyArea, []); + assertAreaPlacements(kLazyAreaId, [], "Placements should no longer include widget."); + is(btn.parentNode.id, gNavBar.customizationTarget.id, + "Button shouldn't actually have moved as it's not removable"); + btn = document.getElementById(kButtonId); + if (btn) btn.remove(); + CustomizableUI.removeWidgetFromArea(kButtonId); + CustomizableUI.unregisterArea(kLazyAreaId); + gLazyArea.remove(); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_887438_currentset_shim.js b/browser/components/customizableui/test/browser_887438_currentset_shim.js new file mode 100644 index 000000000..a04299819 --- /dev/null +++ b/browser/components/customizableui/test/browser_887438_currentset_shim.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var navbar = document.getElementById("nav-bar"); +var navbarCT = navbar.customizationTarget; +var overflowPanelList = document.getElementById("widget-overflow-list"); + +// Reading currentset +add_task(function() { + let nodeIds = []; + for (let node of navbarCT.childNodes) { + if (node.getAttribute("skipintoolbarset") != "true") { + nodeIds.push(node.id); + } + } + for (let node of overflowPanelList.childNodes) { + if (node.getAttribute("skipintoolbarset") != "true") { + nodeIds.push(node.id); + } + } + let currentSet = navbar.currentSet; + is(currentSet.split(',').length, nodeIds.length, "Should be just as many nodes as there are."); + is(currentSet, nodeIds.join(','), "Current set and node IDs should match."); +}); + +// Insert, then remove items +add_task(function() { + let currentSet = navbar.currentSet; + let newCurrentSet = currentSet.replace('home-button', 'feed-button,sync-button,home-button'); + navbar.currentSet = newCurrentSet; + is(newCurrentSet, navbar.currentSet, "Current set should match expected current set."); + let feedBtn = document.getElementById("feed-button"); + let syncBtn = document.getElementById("sync-button"); + ok(feedBtn, "Feed button should have been added."); + ok(syncBtn, "Sync button should have been added."); + if (feedBtn && syncBtn) { + let feedParent = feedBtn.parentNode; + let syncParent = syncBtn.parentNode; + ok(feedParent == navbarCT || feedParent == overflowPanelList, + "Feed button should be in navbar or overflow"); + ok(syncParent == navbarCT || syncParent == overflowPanelList, + "Feed button should be in navbar or overflow"); + is(feedBtn.nextElementSibling, syncBtn, "Feed button should be next to sync button."); + let homeBtn = document.getElementById("home-button"); + is(syncBtn.nextElementSibling, homeBtn, "Sync button should be next to home button."); + } + navbar.currentSet = currentSet; + is(currentSet, navbar.currentSet, "Should be able to remove the added items."); +}); + +// Simultaneous insert/remove: +add_task(function() { + let currentSet = navbar.currentSet; + let newCurrentSet = currentSet.replace('home-button', 'feed-button'); + navbar.currentSet = newCurrentSet; + is(newCurrentSet, navbar.currentSet, "Current set should match expected current set."); + let feedBtn = document.getElementById("feed-button"); + ok(feedBtn, "Feed button should have been added."); + let homeBtn = document.getElementById("home-button"); + ok(!homeBtn, "Home button should have been removed."); + if (feedBtn) { + let feedParent = feedBtn.parentNode; + ok(feedParent == navbarCT || feedParent == overflowPanelList, + "Feed button should be in navbar or overflow"); + } + navbar.currentSet = currentSet; + is(currentSet, navbar.currentSet, "Should be able to return to original state."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_888817_currentset_updating.js b/browser/components/customizableui/test/browser_888817_currentset_updating.js new file mode 100644 index 000000000..6e7c4e95a --- /dev/null +++ b/browser/components/customizableui/test/browser_888817_currentset_updating.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Adding, moving and removing items should update the relevant currentset attributes +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Should be in the default state when we start"); + let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS); + setToolbarVisibility(personalbar, true); + ok(!CustomizableUI.inDefaultState, "Making the bookmarks toolbar visible takes it out of the default state"); + + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS); + let navbarCurrentset = navbar.getAttribute("currentset") || navbar.currentSet; + let personalbarCurrentset = personalbar.getAttribute("currentset") || personalbar.currentSet; + + let otherWin = yield openAndLoadWindow(); + let otherNavbar = otherWin.document.getElementById(CustomizableUI.AREA_NAVBAR); + let otherPersonalbar = otherWin.document.getElementById(CustomizableUI.AREA_BOOKMARKS); + + CustomizableUI.moveWidgetWithinArea("home-button", 0); + navbarCurrentset = "home-button," + navbarCurrentset.replace(",home-button", ""); + is(navbar.getAttribute("currentset"), navbarCurrentset, + "Should have updated currentSet after move."); + is(otherNavbar.getAttribute("currentset"), navbarCurrentset, + "Should have updated other window's currentSet after move."); + + CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_BOOKMARKS); + navbarCurrentset = navbarCurrentset.replace("home-button,", ""); + personalbarCurrentset = personalbarCurrentset + ",home-button"; + is(navbar.getAttribute("currentset"), navbarCurrentset, + "Should have updated navbar currentSet after implied remove."); + is(otherNavbar.getAttribute("currentset"), navbarCurrentset, + "Should have updated other window's navbar currentSet after implied remove."); + is(personalbar.getAttribute("currentset"), personalbarCurrentset, + "Should have updated personalbar currentSet after add."); + is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset, + "Should have updated other window's personalbar currentSet after add."); + + CustomizableUI.removeWidgetFromArea("home-button"); + personalbarCurrentset = personalbarCurrentset.replace(",home-button", ""); + is(personalbar.getAttribute("currentset"), personalbarCurrentset, + "Should have updated currentSet after remove."); + is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset, + "Should have updated other window's currentSet after remove."); + + yield promiseWindowClosed(otherWin); + // Reset in asyncCleanup will put our button back for us. +}); + +add_task(function* asyncCleanup() { + let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS); + setToolbarVisibility(personalbar, false); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js new file mode 100644 index 000000000..84b126a9b --- /dev/null +++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +// One orphaned item should have two placeholders next to it. +add_task(function*() { + yield startCustomizing(); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + } + if (!isInDevEdition()) { + ok(CustomizableUI.inDefaultState, "Should be in default state."); + } else { + ok(!CustomizableUI.inDefaultState, "Should not be in default state if on DevEdition."); + } + + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + + assertAreaPlacements(CustomizableUI.AREA_PANEL, placements); + is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting"); + + yield endCustomizing(); + yield startCustomizing(); + is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering"); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); + } + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Two orphaned items should have one placeholder next to them (case 1). +add_task(function*() { + yield startCustomizing(); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); + } + + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + + let btn = document.getElementById("open-file-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + let placementsAfterAppend = placements; + + placementsAfterAppend = placements.concat(["open-file-button"]); + simulateItemDrag(btn, panel); + + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend); + + ok(!CustomizableUI.inDefaultState, "Should not be in default state."); + + is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting"); + + yield endCustomizing(); + yield startCustomizing(); + is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering"); + + let palette = document.getElementById("customization-palette"); + simulateItemDrag(btn, palette); + + btn = document.getElementById("open-file-button"); + simulateItemDrag(btn, palette); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); + } + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// Two orphaned items should have one placeholder next to them (case 2). +add_task(function*() { + yield startCustomizing(); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); + } + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + + let btn = document.getElementById("add-ons-button"); + let btn2 = document.getElementById("developer-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let palette = document.getElementById("customization-palette"); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + + let placementsAfterAppend = placements.filter(p => p != btn.id && p != btn2.id); + simulateItemDrag(btn, palette); + simulateItemDrag(btn2, palette); + + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting"); + + yield endCustomizing(); + yield startCustomizing(); + is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering"); + + simulateItemDrag(btn, panel); + simulateItemDrag(btn2, panel); + + assertAreaPlacements(CustomizableUI.AREA_PANEL, placements); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); + } + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// A wide widget at the bottom of the panel should have three placeholders after it. +add_task(function*() { + yield startCustomizing(); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); + } + + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + + let btn = document.getElementById("edit-controls"); + let btn2 = document.getElementById("developer-button"); + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + let palette = document.getElementById("customization-palette"); + let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); + + placements.pop(); + simulateItemDrag(btn2, palette); + + let placementsAfterAppend = placements.concat([placements.shift()]); + simulateItemDrag(btn, panel); + assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state."); + is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting"); + + yield endCustomizing(); + yield startCustomizing(); + is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering"); + + simulateItemDrag(btn2, panel); + + let zoomControls = document.getElementById("zoom-controls"); + simulateItemDrag(btn, zoomControls); + + if (isInDevEdition()) { + CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); + } + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); +}); + +// The default placements should have two placeholders at the bottom (or 1 in win8). +add_task(function*() { + yield startCustomizing(); + let numPlaceholders = -1; + + if (isInDevEdition()) { + numPlaceholders = 3; + } else { + numPlaceholders = 2; + } + + let panel = document.getElementById(CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state."); + + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + + is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting"); + + yield endCustomizing(); + yield startCustomizing(); + is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering"); + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should still be in default state."); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); + +function getVisiblePlaceholderCount(aPanel) { + let visiblePlaceholders = aPanel.querySelectorAll(".panel-customization-placeholder:not([hidden=true])"); + return visiblePlaceholders.length; +} diff --git a/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js b/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js new file mode 100644 index 000000000..13f2bd7ba --- /dev/null +++ b/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kLazyAreaId = "test-890262-lazy-area"; +const kWidget1Id = "test-890262-widget1"; +const kWidget2Id = "test-890262-widget2"; + +setupArea(); + +// Destroying a widget after defaulting it to a non-legacy area should work. +add_task(function() { + CustomizableUI.createWidget({ + id: kWidget1Id, + removable: true, + defaultArea: kLazyAreaId + }); + let noError = true; + try { + CustomizableUI.destroyWidget(kWidget1Id); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Shouldn't throw an exception for a widget that was created in a not-yet-constructed area"); +}); + +// Destroying a widget after moving it to a non-legacy area should work. +add_task(function() { + CustomizableUI.createWidget({ + id: kWidget2Id, + removable: true, + defaultArea: CustomizableUI.AREA_NAVBAR + }); + + CustomizableUI.addWidgetToArea(kWidget2Id, kLazyAreaId); + let noError = true; + try { + CustomizableUI.destroyWidget(kWidget2Id); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Shouldn't throw an exception for a widget that was added to a not-yet-constructed area"); +}); + +add_task(function* asyncCleanup() { + let lazyArea = document.getElementById(kLazyAreaId); + if (lazyArea) { + lazyArea.remove(); + } + try { + CustomizableUI.unregisterArea(kLazyAreaId); + } catch (ex) {} // If we didn't register successfully for some reason + yield resetCustomization(); +}); + +function setupArea() { + let lazyArea = document.createElementNS(kNSXUL, "hbox"); + lazyArea.id = kLazyAreaId; + document.getElementById("nav-bar").appendChild(lazyArea); + CustomizableUI.registerArea(kLazyAreaId, { + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: [] + }); +} diff --git a/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js b/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js new file mode 100644 index 000000000..67ef82b82 --- /dev/null +++ b/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-892955-remove-widget"; + +// Removing a destroyed widget should work. +add_task(function*() { + let widgetSpec = { + id: kWidgetId, + defaultArea: CustomizableUI.AREA_NAVBAR + }; + + CustomizableUI.createWidget(widgetSpec); + CustomizableUI.destroyWidget(kWidgetId); + let noError = true; + try { + CustomizableUI.removeWidgetFromArea(kWidgetId); + } catch (ex) { + noError = false; + Cu.reportError(ex); + } + ok(noError, "Shouldn't throw an error removing a destroyed widget."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js b/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js new file mode 100644 index 000000000..c7047c797 --- /dev/null +++ b/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-892956-destroyWidget-defaultPlacement"; + +// destroyWidget should clean up defaultPlacements if the widget had a defaultArea +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Should be in the default state when we start"); + + let widgetSpec = { + id: kWidgetId, + defaultArea: CustomizableUI.AREA_NAVBAR + }; + CustomizableUI.createWidget(widgetSpec); + CustomizableUI.destroyWidget(kWidgetId); + ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish"); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js new file mode 100644 index 000000000..3bc449add --- /dev/null +++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +logActiveElement(); + +function* waitForSearchBarFocus() +{ + let searchbar = document.getElementById("searchbar"); + yield waitForCondition(function () { + logActiveElement(); + return document.activeElement === searchbar.textbox.inputField; + }); +} + +// Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel. +add_task(function*() { + let searchbar = document.getElementById("searchbar"); + gCustomizeMode.addToPanel(searchbar); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel"); + + let shownPanelPromise = promisePanelShown(window); + sendWebSearchKeyCommand(); + yield shownPanelPromise; + + yield waitForSearchBarFocus(); + + let hiddenPanelPromise = promisePanelHidden(window); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield hiddenPanelPromise; + CustomizableUI.reset(); +}); + +// Ctrl+K should give focus to the searchbar when the searchbar is in the menupanel and the panel is already opened. +add_task(function*() { + let searchbar = document.getElementById("searchbar"); + gCustomizeMode.addToPanel(searchbar); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel"); + + let shownPanelPromise = promisePanelShown(window); + PanelUI.toggle({type: "command"}); + yield shownPanelPromise; + + sendWebSearchKeyCommand(); + + yield waitForSearchBarFocus(); + + let hiddenPanelPromise = promisePanelHidden(window); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield hiddenPanelPromise; + CustomizableUI.reset(); +}); + +// Ctrl+K should open the overflow panel and focus the search bar if the search bar is overflowed. +add_task(function*() { + this.originalWindowWidth = window.outerWidth; + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + + window.resizeTo(360, window.outerHeight); + yield waitForCondition(() => navbar.getAttribute("overflowing") == "true"); + ok(!navbar.querySelector("#search-container"), "Search container should be overflowing"); + + let shownPanelPromise = promiseOverflowShown(window); + sendWebSearchKeyCommand(); + yield shownPanelPromise; + + let chevron = document.getElementById("nav-bar-overflow-button"); + yield waitForCondition(() => chevron.open); + + yield waitForSearchBarFocus(); + + let hiddenPanelPromise = promiseOverflowHidden(window); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield hiddenPanelPromise; + navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + window.resizeTo(this.originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar."); +}); + +// Ctrl+K should focus the search bar if it is in the navbar and not overflowing. +add_task(function*() { + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar"); + + sendWebSearchKeyCommand(); + + yield waitForSearchBarFocus(); +}); + + +function sendWebSearchKeyCommand() { + if (Services.appinfo.OS === "Darwin") + EventUtils.synthesizeKey("k", { accelKey: true }); + else + EventUtils.synthesizeKey("k", { ctrlKey: true }); +} + +function logActiveElement() { + let element = document.activeElement; + let str = ""; + while (element && element.parentNode) { + str = " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ") >" + str; + element = element.parentNode; + } + info("Active element: " + element ? str : "null"); +} diff --git a/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js b/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js new file mode 100644 index 000000000..f39d13ff4 --- /dev/null +++ b/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Resize to a small window, open a new window, check that new window handles overflow properly +add_task(function*() { + let originalWindowWidth = window.outerWidth; + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + let oldChildCount = navbar.customizationTarget.childElementCount; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children."); + let newWindow = yield openAndLoadWindow(); + let otherNavBar = newWindow.document.getElementById(CustomizableUI.AREA_NAVBAR); + yield waitForCondition(() => otherNavBar.hasAttribute("overflowing")); + ok(otherNavBar.hasAttribute("overflowing"), "Other window should have an overflowing toolbar."); + yield promiseWindowClosed(newWindow); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_913972_currentset_overflow.js b/browser/components/customizableui/test/browser_913972_currentset_overflow.js new file mode 100644 index 000000000..7d754d79b --- /dev/null +++ b/browser/components/customizableui/test/browser_913972_currentset_overflow.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + +// Resize to a small window, resize back, shouldn't affect currentSet +add_task(function*() { + let originalWindowWidth = window.outerWidth; + let oldCurrentSet = navbar.currentSet; + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + let oldChildCount = navbar.customizationTarget.childElementCount; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + is(navbar.currentSet, oldCurrentSet, "Currentset should be the same when overflowing."); + ok(CustomizableUI.inDefaultState, "Should still be in default state when overflowing."); + ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children."); + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar."); + is(navbar.currentSet, oldCurrentSet, "Currentset should still be the same now we're no longer overflowing."); + ok(CustomizableUI.inDefaultState, "Should still be in default state now we're no longer overflowing."); + + // Verify actual physical placements match those of the placement array: + let placementCounter = 0; + let placements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR); + for (let node of navbar.customizationTarget.childNodes) { + if (node.getAttribute("skipintoolbarset") == "true") { + continue; + } + is(placements[placementCounter++], node.id, "Nodes should match after overflow"); + } + is(placements.length, placementCounter, "Should have as many nodes as expected"); + is(navbar.customizationTarget.childElementCount, oldChildCount, "Number of nodes should match"); +}); + +// Enter and exit customization mode, check that currentSet works +add_task(function*() { + let oldCurrentSet = navbar.currentSet; + ok(CustomizableUI.inDefaultState, "Should start in default state."); + yield startCustomizing(); + ok(CustomizableUI.inDefaultState, "Should be in default state in customization mode."); + is(navbar.currentSet, oldCurrentSet, "Currentset should be the same in customization mode."); + yield endCustomizing(); + ok(CustomizableUI.inDefaultState, "Should be in default state after customization mode."); + is(navbar.currentSet, oldCurrentSet, "Currentset should be the same after customization mode."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js b/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js new file mode 100644 index 000000000..35ba79bec --- /dev/null +++ b/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); +var overflowList = document.getElementById(navbar.getAttribute("overflowtarget")); + +const kTestBtn1 = "test-addWidgetToArea-overflow"; +const kTestBtn2 = "test-removeWidgetFromArea-overflow"; +const kTestBtn3 = "test-createWidget-overflow"; +const kHomeBtn = "home-button"; +const kDownloadsBtn = "downloads-button"; +const kSearchBox = "search-container"; +const kStarBtn = "bookmarks-menu-button"; + +var originalWindowWidth; + +// Adding a widget should add it next to the widget it's being inserted next to. +add_task(function*() { + originalWindowWidth = window.outerWidth; + createDummyXULButton(kTestBtn1, "Test"); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar"); + let homeBtnNode = overflowList.querySelector("#" + kHomeBtn); + ok(homeBtnNode, "Home button should be overflowing"); + ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem attribute"); + + let placementOfHomeButton = CustomizableUI.getWidgetIdsInArea(navbar.id).indexOf(kHomeBtn); + CustomizableUI.addWidgetToArea(kTestBtn1, navbar.id, placementOfHomeButton); + ok(!navbar.querySelector("#" + kTestBtn1), "New button should not be in the navbar"); + let newButtonNode = overflowList.querySelector("#" + kTestBtn1); + ok(newButtonNode, "New button should be overflowing"); + ok(newButtonNode && newButtonNode.getAttribute("overflowedItem") == "true", "New button should have overflowedItem attribute"); + let nextEl = newButtonNode && newButtonNode.nextSibling; + is(nextEl && nextEl.id, kHomeBtn, "Test button should be next to home button."); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar."); + ok(navbar.querySelector("#" + kHomeBtn), "Home button should be in the navbar"); + ok(homeBtnNode && (homeBtnNode.getAttribute("overflowedItem") != "true"), "Home button should no longer have overflowedItem attribute"); + ok(!overflowList.querySelector("#" + kHomeBtn), "Home button should no longer be overflowing"); + ok(navbar.querySelector("#" + kTestBtn1), "Test button should be in the navbar"); + ok(!overflowList.querySelector("#" + kTestBtn1), "Test button should no longer be overflowing"); + ok(newButtonNode && (newButtonNode.getAttribute("overflowedItem") != "true"), "New button should no longer have overflowedItem attribute"); + let el = document.getElementById(kTestBtn1); + if (el) { + CustomizableUI.removeWidgetFromArea(kTestBtn1); + el.remove(); + } + window.resizeTo(originalWindowWidth, window.outerHeight); +}); + +// Removing a widget should remove it from the overflow list if that is where it is, and update it accordingly. +add_task(function*() { + createDummyXULButton(kTestBtn2, "Test"); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + CustomizableUI.addWidgetToArea(kTestBtn2, navbar.id); + ok(!navbar.hasAttribute("overflowing"), "Should still have a non-overflowing toolbar."); + + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar"); + ok(overflowList.querySelector("#" + kTestBtn2), "Test button should be overflowing"); + + CustomizableUI.removeWidgetFromArea(kTestBtn2); + + ok(!overflowList.querySelector("#" + kTestBtn2), "Test button should not be overflowing."); + ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar"); + ok(gNavToolbox.palette.querySelector("#" + kTestBtn2), "Test button should be in the palette"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar."); + let el = document.getElementById(kTestBtn2); + if (el) { + CustomizableUI.removeWidgetFromArea(kTestBtn2); + el.remove(); + } + window.resizeTo(originalWindowWidth, window.outerHeight); +}); + +// Constructing a widget while overflown should set the right class on it. +add_task(function*() { + originalWindowWidth = window.outerWidth; + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar"); + let homeBtnNode = overflowList.querySelector("#" + kHomeBtn); + ok(homeBtnNode, "Home button should be overflowing"); + ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem class"); + + let testBtnSpec = {id: kTestBtn3, label: "Overflowable widget test", defaultArea: "nav-bar"}; + CustomizableUI.createWidget(testBtnSpec); + let testNode = overflowList.querySelector("#" + kTestBtn3); + ok(testNode, "Test button should be overflowing"); + ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class"); + + CustomizableUI.destroyWidget(kTestBtn3); + testNode = document.getElementById(kTestBtn3); + ok(!testNode, "Test button should be gone"); + + CustomizableUI.createWidget(testBtnSpec); + testNode = overflowList.querySelector("#" + kTestBtn3); + ok(testNode, "Test button should be overflowing"); + ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class"); + + CustomizableUI.removeWidgetFromArea(kTestBtn3); + testNode = document.getElementById(kTestBtn3); + ok(!testNode, "Test button should be gone"); + CustomizableUI.destroyWidget(kTestBtn3); + window.resizeTo(originalWindowWidth, window.outerHeight); +}); + +add_task(function* asyncCleanup() { + window.resizeTo(originalWindowWidth, window.outerHeight); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js b/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js new file mode 100644 index 000000000..b5757eabb --- /dev/null +++ b/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js @@ -0,0 +1,16 @@ +/* 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/. */ + +// Entering then exiting customization mode should reenable the Help and Exit buttons. +add_task(function*() { + yield startCustomizing(); + let helpButton = document.getElementById("PanelUI-help"); + let quitButton = document.getElementById("PanelUI-quit"); + ok(helpButton.getAttribute("disabled") == "true", "Help button should be disabled while in customization mode."); + ok(quitButton.getAttribute("disabled") == "true", "Quit button should be disabled while in customization mode."); + yield endCustomizing(); + + ok(!helpButton.hasAttribute("disabled"), "Help button should not be disabled."); + ok(!quitButton.hasAttribute("disabled"), "Quit button should not be disabled."); +}); diff --git a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js new file mode 100644 index 000000000..dffe388dc --- /dev/null +++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var navbar; +var skippedItem; + +// Attempting to drag a skipintoolbarset item should work. +add_task(function*() { + navbar = document.getElementById("nav-bar"); + skippedItem = document.createElement("toolbarbutton"); + skippedItem.id = "test-skipintoolbarset-item"; + skippedItem.setAttribute("label", "Test"); + skippedItem.setAttribute("skipintoolbarset", "true"); + skippedItem.setAttribute("removable", "true"); + navbar.customizationTarget.appendChild(skippedItem); + let downloadsButton = document.getElementById("downloads-button"); + yield startCustomizing(); + ok(CustomizableUI.inDefaultState, "Should still be in default state"); + simulateItemDrag(skippedItem, downloadsButton); + ok(CustomizableUI.inDefaultState, "Should still be in default state"); + let skippedItemWrapper = skippedItem.parentNode; + is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id, + downloadsButton.parentNode.id, "Should be next to downloads button"); + simulateItemDrag(downloadsButton, skippedItem); + let downloadWrapper = downloadsButton.parentNode; + is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id, + skippedItem.parentNode.id, "Should be next to skipintoolbarset item"); + ok(CustomizableUI.inDefaultState, "Should still be in default state"); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + skippedItem.remove(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js new file mode 100644 index 000000000..87aca51eb --- /dev/null +++ b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Customize mode reset button should revert correctly +add_task(function*() { + yield startCustomizing(); + let devButton = document.getElementById("developer-button"); + let downloadsButton = document.getElementById("downloads-button"); + let searchBox = document.getElementById("search-container"); + let palette = document.getElementById("customization-palette"); + ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist"); + simulateItemDrag(devButton, downloadsButton); + simulateItemDrag(searchBox, palette); + yield gCustomizeMode.reset(); + ok(CustomizableUI.inDefaultState, "Should be back in default state"); + yield endCustomizing(); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js new file mode 100644 index 000000000..d79f6e364 --- /dev/null +++ b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kTestToolbarId = "test-empty-drag"; + +// Attempting to drag an item to an empty container should work. +add_task(function*() { + yield createToolbarWithPlacements(kTestToolbarId, []); + yield startCustomizing(); + let downloadButton = document.getElementById("downloads-button"); + let customToolbar = document.getElementById(kTestToolbarId); + simulateItemDrag(downloadButton, customToolbar); + assertAreaPlacements(kTestToolbarId, ["downloads-button"]); + ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar, + "Button should really be in toolbar"); + yield endCustomizing(); + removeCustomToolbars(); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js b/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js new file mode 100644 index 000000000..3cbf6be42 --- /dev/null +++ b/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// There should be an advert to get more addons when the palette is empty. +add_task(function*() { + yield startCustomizing(); + let visiblePalette = document.getElementById("customization-palette"); + let emptyPaletteNotice = document.getElementById("customization-empty"); + is(emptyPaletteNotice.hidden, true, "The empty palette notice should not be shown when there are items in the palette."); + + while (visiblePalette.childElementCount) { + gCustomizeMode.addToToolbar(visiblePalette.children[0]); + } + is(visiblePalette.childElementCount, 0, "There shouldn't be any items remaining in the visible palette."); + is(emptyPaletteNotice.hidden, false, "The empty palette notice should be shown when there are no items in the palette."); + + yield endCustomizing(); + yield startCustomizing(); + visiblePalette = document.getElementById("customization-palette"); + emptyPaletteNotice = document.getElementById("customization-empty"); + is(emptyPaletteNotice.hidden, false, + "The empty palette notice should be shown when there are no items in the palette and cust. mode is re-entered."); + + gCustomizeMode.removeFromArea(document.getElementById("wrapper-home-button")); + is(emptyPaletteNotice.hidden, true, + "The empty palette notice should not be shown when there is at least one item in the palette."); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_934113_menubar_removable.js b/browser/components/customizableui/test/browser_934113_menubar_removable.js new file mode 100644 index 000000000..1d788bced --- /dev/null +++ b/browser/components/customizableui/test/browser_934113_menubar_removable.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Attempting to drag the menubar to the navbar shouldn't work. +add_task(function*() { + yield startCustomizing(); + let menuItems = document.getElementById("menubar-items"); + let navbar = document.getElementById("nav-bar"); + let menubar = document.getElementById("toolbar-menubar"); + // Force the menu to be shown. + const kAutohide = menubar.getAttribute("autohide"); + menubar.setAttribute("autohide", "false"); + simulateItemDrag(menuItems, navbar.customizationTarget); + + is(getAreaWidgetIds("nav-bar").indexOf("menubar-items"), -1, "Menu bar shouldn't be in the navbar."); + ok(!navbar.querySelector("#menubar-items"), "Shouldn't find menubar items in the navbar."); + ok(menubar.querySelector("#menubar-items"), "Should find menubar items in the menubar."); + isnot(getAreaWidgetIds("toolbar-menubar").indexOf("menubar-items"), -1, + "Menubar items shouldn't be missing from the navbar."); + menubar.setAttribute("autohide", kAutohide); + yield endCustomizing(); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js new file mode 100644 index 000000000..dcc183051 --- /dev/null +++ b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kTimeoutInMS = 20000; + +// Bug 934951 - Zoom controls percentage label doesn't update when it's in the toolbar and you navigate. +add_task(function*() { + CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR); + let tab1 = gBrowser.addTab("about:mozilla"); + yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser); + let tab2 = gBrowser.addTab("about:robots"); + yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + gBrowser.selectedTab = tab1; + let zoomResetButton = document.getElementById("zoom-reset-button"); + + registerCleanupFunction(() => { + info("Cleaning up."); + CustomizableUI.reset(); + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab1); + }); + + is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla"); + let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange"); + FullZoom.enlarge(); + yield zoomChangePromise; + is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla"); + + let tabSelectPromise = promiseTabSelect(); + gBrowser.selectedTab = tab2; + yield tabSelectPromise; + is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots"); + + gBrowser.selectedTab = tab1; + let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset"); + FullZoom.reset(); + yield zoomResetPromise; + is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla"); + + // Test zoom label updates while navigating pages in the same tab. + FullZoom.enlarge(); + yield zoomChangePromise; + is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla"); + let attributeChangePromise = promiseAttributeMutation(zoomResetButton, "label", (v) => { + return parseInt(v, 10) == 100; + }); + yield promiseTabLoadEvent(tab1, "about:home"); + yield attributeChangePromise; + is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:home"); + yield promiseTabHistoryNavigation(-1, function() { + return parseInt(zoomResetButton.label, 10) == 110; + }); + is(parseInt(zoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla"); + FullZoom.reset(); +}); + +function promiseObserverNotification(aObserver) { + let deferred = Promise.defer(); + function notificationCallback(e) { + Services.obs.removeObserver(notificationCallback, aObserver, false); + clearTimeout(timeoutId); + deferred.resolve(); + } + let timeoutId = setTimeout(() => { + Services.obs.removeObserver(notificationCallback, aObserver, false); + deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds."); + }, kTimeoutInMS); + Services.obs.addObserver(notificationCallback, aObserver, false); + return deferred.promise; +} + +function promiseTabSelect() { + let deferred = Promise.defer(); + let container = window.gBrowser.tabContainer; + let timeoutId = setTimeout(() => { + container.removeEventListener("TabSelect", callback); + deferred.reject("TabSelect did not happen within 20 seconds"); + }, kTimeoutInMS); + function callback(e) { + container.removeEventListener("TabSelect", callback); + clearTimeout(timeoutId); + executeSoon(deferred.resolve); + } + container.addEventListener("TabSelect", callback); + return deferred.promise; +} diff --git a/browser/components/customizableui/test/browser_938980_navbar_collapsed.js b/browser/components/customizableui/test/browser_938980_navbar_collapsed.js new file mode 100644 index 000000000..fc7fa1a0a --- /dev/null +++ b/browser/components/customizableui/test/browser_938980_navbar_collapsed.js @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +var bookmarksToolbar = document.getElementById("PersonalToolbar"); +var navbar = document.getElementById("nav-bar"); +var tabsToolbar = document.getElementById("TabsToolbar"); + +// Customization reset should restore visibility to default-visible toolbars. +add_task(function*() { + is(navbar.collapsed, false, "Test should start with navbar visible"); + setToolbarVisibility(navbar, false); + is(navbar.collapsed, true, "navbar should be hidden now"); + + yield resetCustomization(); + + is(navbar.collapsed, false, "Customization reset should restore visibility to the navbar"); +}); + +// Customization reset should restore collapsed-state to default-collapsed toolbars. +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Everything should be in its default state"); + + is(bookmarksToolbar.collapsed, true, "Test should start with bookmarks toolbar collapsed"); + ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed"); + ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed"); + is(navbar.collapsed, false, "The nav-bar should be shown by default"); + + setToolbarVisibility(bookmarksToolbar, true); + setToolbarVisibility(navbar, false); + ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now"); + ok(navbar.collapsed, "navbar should be collapsed"); + is(CustomizableUI.inDefaultState, false, "Should no longer be in default state"); + + yield startCustomizing(); + yield gCustomizeMode.reset(); + yield endCustomizing(); + + is(bookmarksToolbar.collapsed, true, "Customization reset should restore collapsed-state to the bookmarks toolbar"); + ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed"); + ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset"); + ok(CustomizableUI.inDefaultState, "Everything should be back to default state"); +}); + +// Check that the menubar will be collapsed by resetting, if the platform supports it. +add_task(function*() { + let menubar = document.getElementById("toolbar-menubar"); + const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id); + if (!canMenubarCollapse) { + return; + } + ok(CustomizableUI.inDefaultState, "Everything should be in its default state"); + + is(menubar.getBoundingClientRect().height, 0, "menubar should be hidden by default"); + setToolbarVisibility(menubar, true); + isnot(menubar.getBoundingClientRect().height, 0, "menubar should be visible now"); + + yield startCustomizing(); + yield gCustomizeMode.reset(); + + is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset in customization mode"); + is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset in customization mode"); + + yield endCustomizing(); + + is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset"); + is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset"); +}); + +// Customization reset should restore collapsed-state to default-collapsed toolbars. +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Everything should be in its default state"); + ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed"); + ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed"); + + setToolbarVisibility(bookmarksToolbar, true); + ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now"); + is(CustomizableUI.inDefaultState, false, "Should no longer be in default state"); + + yield startCustomizing(); + + ok(!bookmarksToolbar.collapsed, "The bookmarksToolbar should be visible before reset"); + ok(!navbar.collapsed, "The navbar should be visible before reset"); + ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed"); + + yield gCustomizeMode.reset(); + + ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset"); + ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed"); + ok(!navbar.collapsed, "The navbar should still be visible after reset"); + ok(CustomizableUI.inDefaultState, "Everything should be back to default state"); + yield endCustomizing(); +}); + +// Check that the menubar will be collapsed by resetting, if the platform supports it. +add_task(function*() { + let menubar = document.getElementById("toolbar-menubar"); + const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id); + if (!canMenubarCollapse) { + return; + } + ok(CustomizableUI.inDefaultState, "Everything should be in its default state"); + yield startCustomizing(); + let resetButton = document.getElementById("customization-reset-button"); + is(resetButton.disabled, true, "The reset button should be disabled when in default state"); + + setToolbarVisibility(menubar, true); + is(resetButton.disabled, false, "The reset button should be enabled when not in default state") + ok(!CustomizableUI.inDefaultState, "No longer in default state when the menubar is shown"); + + yield gCustomizeMode.reset(); + + is(resetButton.disabled, true, "The reset button should be disabled when in default state"); + ok(CustomizableUI.inDefaultState, "Everything should be in its default state"); + + yield endCustomizing(); +}); diff --git a/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js new file mode 100644 index 000000000..1f06c1aac --- /dev/null +++ b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-non-removable-widget"; + +// Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState +add_task(function() { + ok(CustomizableUI.inDefaultState, "Should start in default state"); + + let button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling"); + CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR); + button.setAttribute("removable", "false"); + ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition"); + button.remove(); + + button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling"); + CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL); + button.setAttribute("removable", "false"); + ok(CustomizableUI.inDefaultState, "Should still be in default state after panel addition"); + button.remove(); + ok(CustomizableUI.inDefaultState, "Should be in default state after destroying both widgets"); +}); diff --git a/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js new file mode 100644 index 000000000..c554bffab --- /dev/null +++ b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarId = "test-registerToolbarNode-toolbar"; +const kButtonId = "test-registerToolbarNode-button"; +registerCleanupFunction(cleanup); + +// Registering a toolbar with defaultset attribute should work +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); + let btn = createDummyXULButton(kButtonId); + let toolbar = document.createElement("toolbar"); + toolbar.id = kToolbarId; + toolbar.setAttribute("customizable", true); + toolbar.setAttribute("defaultset", kButtonId); + gNavToolbox.appendChild(toolbar); + ok(CustomizableUI.areas.indexOf(kToolbarId) != -1, + "Toolbar should have been registered automatically."); + is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR, + "Area should be registered as toolbar"); + assertAreaPlacements(kToolbarId, [kButtonId]); + ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible."); + CustomizableUI.unregisterArea(kToolbarId, true); + toolbar.remove(); + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); + btn.remove(); +}); + +// Registering a toolbar without a defaultset attribute should +// wait for the registerArea call +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); + let btn = createDummyXULButton(kButtonId); + let toolbar = document.createElement("toolbar"); + toolbar.id = kToolbarId; + toolbar.setAttribute("customizable", true); + gNavToolbox.appendChild(toolbar); + ok(CustomizableUI.areas.indexOf(kToolbarId) == -1, + "Toolbar should not yet have been registered automatically."); + CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]}); + ok(CustomizableUI.areas.indexOf(kToolbarId) != -1, + "Toolbar should have been registered now."); + is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR, + "Area should be registered as toolbar"); + assertAreaPlacements(kToolbarId, [kButtonId]); + ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible."); + CustomizableUI.unregisterArea(kToolbarId, true); + toolbar.remove(); + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); + btn.remove(); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); + +function cleanup() { + let toolbar = document.getElementById(kToolbarId); + if (toolbar) { + toolbar.remove(); + } + let btn = document.getElementById(kButtonId) || + gNavToolbox.querySelector("#" + kButtonId); + if (btn) { + btn.remove(); + } +} diff --git a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js new file mode 100644 index 000000000..944879a1b --- /dev/null +++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js @@ -0,0 +1,136 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var button, menuButton; +/* Clicking a button should close the panel */ +add_task(function*() { + button = document.createElement("toolbarbutton"); + button.id = "browser_940307_button"; + button.setAttribute("label", "Button"); + PanelUI.contents.appendChild(button); + yield PanelUI.show(); + let hiddenAgain = promisePanelHidden(window); + EventUtils.synthesizeMouseAtCenter(button, {}); + yield hiddenAgain; + button.remove(); +}); + +/* Clicking a menu button should close the panel, opening the popup shouldn't. */ +add_task(function*() { + menuButton = document.createElement("toolbarbutton"); + menuButton.setAttribute("type", "menu-button"); + menuButton.id = "browser_940307_menubutton"; + menuButton.setAttribute("label", "Menu button"); + + let menuPopup = document.createElement("menupopup"); + menuPopup.id = "browser_940307_menupopup"; + + let menuItem = document.createElement("menuitem"); + menuItem.setAttribute("label", "Menu item"); + menuItem.id = "browser_940307_menuitem"; + + menuPopup.appendChild(menuItem); + menuButton.appendChild(menuPopup); + PanelUI.contents.appendChild(menuButton); + + yield PanelUI.show(); + let hiddenAgain = promisePanelHidden(window); + let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button"); + EventUtils.synthesizeMouseAtCenter(innerButton, {}); + yield hiddenAgain; + + // Now click the dropmarker to show the menu + yield PanelUI.show(); + hiddenAgain = promisePanelHidden(window); + let menuShown = promisePanelElementShown(window, menuPopup); + let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button"); + EventUtils.synthesizeMouseAtCenter(dropmarker, {}); + yield menuShown; + // Panel should stay open: + ok(isPanelUIOpen(), "Panel should still be open"); + let menuHidden = promisePanelElementHidden(window, menuPopup); + // Then click the menu item to close all the things + EventUtils.synthesizeMouseAtCenter(menuItem, {}); + yield menuHidden; + yield hiddenAgain; + menuButton.remove(); +}); + +add_task(function*() { + let searchbar = document.getElementById("searchbar"); + gCustomizeMode.addToPanel(searchbar); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel"); + yield PanelUI.show(); + yield waitForCondition(() => "value" in searchbar && searchbar.value === ""); + + // Focusing a non-empty searchbox will cause us to open the + // autocomplete panel and search for suggestions, which would + // trigger network requests. Temporarily disable suggestions. + yield SpecialPowers.pushPrefEnv({set: [["browser.search.suggest.enabled", false]]}); + + searchbar.value = "foo"; + searchbar.focus(); + // Reaching into this context menu is pretty evil, but hey... it's a test. + let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box"); + let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu"); + let contextMenuShown = promisePanelElementShown(window, contextmenu); + EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2}); + yield contextMenuShown; + + ok(isPanelUIOpen(), "Panel should still be open"); + + let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']"); + let contextMenuHidden = promisePanelElementHidden(window, contextmenu); + EventUtils.synthesizeMouseAtCenter(selectAll, {}); + yield contextMenuHidden; + + // Hide the suggestion panel. + searchbar.textbox.popup.hidePopup(); + + ok(isPanelUIOpen(), "Panel should still be open"); + + let hiddenPanelPromise = promisePanelHidden(window); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield hiddenPanelPromise; + ok(!isPanelUIOpen(), "Panel should no longer be open"); + + // We focused the search bar earlier - ensure we don't keep doing that. + gURLBar.select(); + + CustomizableUI.reset(); +}); + +add_task(function*() { + button = document.createElement("toolbarbutton"); + button.id = "browser_946166_button_disabled"; + button.setAttribute("disabled", "true"); + button.setAttribute("label", "Button"); + PanelUI.contents.appendChild(button); + yield PanelUI.show(); + EventUtils.synthesizeMouseAtCenter(button, {}); + is(PanelUI.panel.state, "open", "Popup stays open"); + button.removeAttribute("disabled"); + let hiddenAgain = promisePanelHidden(window); + EventUtils.synthesizeMouseAtCenter(button, {}); + yield hiddenAgain; + button.remove(); +}); + +registerCleanupFunction(function() { + if (button && button.parentNode) { + button.remove(); + } + if (menuButton && menuButton.parentNode) { + menuButton.remove(); + } + // Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should + // definitely close it here and hope it won't interfere with other tests. + // Of course, all the tests are meant to do this themselves, but if they fail... + if (isPanelUIOpen()) { + PanelUI.hide(); + } +}); diff --git a/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js b/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js new file mode 100644 index 000000000..c81b004c1 --- /dev/null +++ b/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kTestBtnId = "test-removable-navbar-customize-mode"; + +// Items without the removable attribute in the navbar should be considered non-removable +add_task(function*() { + let btn = createDummyXULButton(kTestBtnId, "Test removable in navbar in customize mode"); + document.getElementById("nav-bar").customizationTarget.appendChild(btn); + yield startCustomizing(); + ok(!CustomizableUI.isWidgetRemovable(kTestBtnId), "Widget should not be considered removable"); + yield endCustomizing(); + document.getElementById(kTestBtnId).remove(); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js b/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js new file mode 100644 index 000000000..1d7f86fd2 --- /dev/null +++ b/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// See https://bugzilla.mozilla.org/show_bug.cgi?id=941083 + +const kWidgetId = "test-invalidate-wrapper-cache"; + +// Check createWidget invalidates the widget cache +add_task(function() { + let groupWrapper = CustomizableUI.getWidget(kWidgetId); + ok(groupWrapper, "Should get group wrapper."); + let singleWrapper = groupWrapper.forWindow(window); + ok(singleWrapper, "Should get single wrapper."); + + CustomizableUI.createWidget({id: kWidgetId, label: "Test invalidating widgets caching"}); + + let newGroupWrapper = CustomizableUI.getWidget(kWidgetId); + ok(newGroupWrapper, "Should get a group wrapper again."); + isnot(newGroupWrapper, groupWrapper, "Wrappers shouldn't be the same."); + isnot(newGroupWrapper.provider, groupWrapper.provider, "Wrapper providers shouldn't be the same."); + + let newSingleWrapper = newGroupWrapper.forWindow(window); + isnot(newSingleWrapper, singleWrapper, "Single wrappers shouldn't be the same."); + isnot(newSingleWrapper.provider, singleWrapper.provider, "Single wrapper providers shouldn't be the same."); + + CustomizableUI.destroyWidget(kWidgetId); + ok(!CustomizableUI.getWidget(kWidgetId), "Shouldn't get a wrapper after destroying the widget."); +}); diff --git a/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js b/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js new file mode 100644 index 000000000..61adac982 --- /dev/null +++ b/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarName = "test-unregisterArea-placements-toolbar"; +const kTestWidgetPfx = "test-widget-for-unregisterArea-placements-"; +const kTestWidgetCount = 3; +registerCleanupFunction(removeCustomToolbars); + +// unregisterArea should keep placements by default and restore them when re-adding the area +add_task(function*() { + let widgetIds = []; + for (let i = 0; i < kTestWidgetCount; i++) { + let id = kTestWidgetPfx + i; + widgetIds.push(id); + let spec = {id: id, type: 'button', removable: true, label: "unregisterArea test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + } + for (let i = kTestWidgetCount; i < kTestWidgetCount * 2; i++) { + let id = kTestWidgetPfx + i; + widgetIds.push(id); + createDummyXULButton(id, "unregisterArea XUL test " + i); + } + let toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds); + checkAbstractAndRealPlacements(toolbarNode, widgetIds); + + // Now move one of them: + CustomizableUI.moveWidgetWithinArea(kTestWidgetPfx + kTestWidgetCount, 0); + // Clone the array so we know this is the modified one: + let modifiedWidgetIds = [...widgetIds]; + let movedWidget = modifiedWidgetIds.splice(kTestWidgetCount, 1)[0]; + modifiedWidgetIds.unshift(movedWidget); + + // Check it: + checkAbstractAndRealPlacements(toolbarNode, modifiedWidgetIds); + + // Then unregister + CustomizableUI.unregisterArea(kToolbarName); + + // Check we tell the outside world no dangerous things: + checkWidgetFates(widgetIds); + // Only then remove the real node + toolbarNode.remove(); + + // Now move one of the items to the palette, and another to the navbar: + let lastWidget = modifiedWidgetIds.pop(); + CustomizableUI.removeWidgetFromArea(lastWidget); + lastWidget = modifiedWidgetIds.pop(); + CustomizableUI.addWidgetToArea(lastWidget, CustomizableUI.AREA_NAVBAR); + + // Recreate ourselves with the default placements being the same: + toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds); + // Then check that after doing this, our actual placements match + // the modified list, not the default one. + checkAbstractAndRealPlacements(toolbarNode, modifiedWidgetIds); + + // Now remove completely: + CustomizableUI.unregisterArea(kToolbarName, true); + checkWidgetFates(modifiedWidgetIds); + toolbarNode.remove(); + + // One more time: + // Recreate ourselves with the default placements being the same: + toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds); + // Should now be back to default: + checkAbstractAndRealPlacements(toolbarNode, widgetIds); + CustomizableUI.unregisterArea(kToolbarName, true); + checkWidgetFates(widgetIds); + toolbarNode.remove(); + + // XXXgijs: ensure cleanup function doesn't barf: + gAddedToolbars.delete(kToolbarName); + + // Remove all the XUL widgets, destroy the others: + for (let widget of widgetIds) { + let widgetWrapper = CustomizableUI.getWidget(widget); + if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL) { + gNavToolbox.palette.querySelector("#" + widget).remove(); + } else { + CustomizableUI.destroyWidget(widget); + } + } +}); + +function checkAbstractAndRealPlacements(aNode, aExpectedPlacements) { + assertAreaPlacements(kToolbarName, aExpectedPlacements); + let physicalWidgetIds = Array.from(aNode.childNodes, (node) => node.id); + placementArraysEqual(aNode.id, physicalWidgetIds, aExpectedPlacements); +} + +function checkWidgetFates(aWidgetIds) { + for (let widget of aWidgetIds) { + ok(!CustomizableUI.getPlacementOfWidget(widget), "Widget should be in palette"); + ok(!document.getElementById(widget), "Widget should not be in the DOM"); + let widgetInPalette = !!gNavToolbox.palette.querySelector("#" + widget); + let widgetProvider = CustomizableUI.getWidget(widget).provider; + let widgetIsXULWidget = widgetProvider == CustomizableUI.PROVIDER_XUL; + is(widgetInPalette, widgetIsXULWidget, "Just XUL Widgets should be in the palette"); + } +} + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_943683_migration_test.js b/browser/components/customizableui/test/browser_943683_migration_test.js new file mode 100644 index 000000000..fe30df9e3 --- /dev/null +++ b/browser/components/customizableui/test/browser_943683_migration_test.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-addonbar-migration"; +const kWidgetId2 = "test-addonbar-migration2"; + +var addonbar = document.getElementById(CustomizableUI.AREA_ADDONBAR); +var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + +var btn; +var btn2; + +// Check we migrate normal stuff to the navbar +add_task(function*() { + btn = createDummyXULButton(kWidgetId, "Test"); + btn2 = createDummyXULButton(kWidgetId2, "Test2"); + addonbar.insertItem(btn.id); + ok(btn.parentNode == navbar.customizationTarget, "Button should end up in navbar"); + let migrationArray = addonbar.getMigratedItems(); + is(migrationArray.length, 1, "Should have migrated 1 item"); + is(migrationArray[0], kWidgetId, "Should have migrated our 1 item"); + + addonbar.currentSet = addonbar.currentSet + "," + kWidgetId2; + ok(btn2.parentNode == navbar.customizationTarget, "Second button should end up in the navbar"); + migrationArray = addonbar.getMigratedItems(); + is(migrationArray.length, 2, "Should have migrated 2 items"); + isnot(migrationArray.indexOf(kWidgetId2), -1, "Should have migrated our second item"); + + let otherWindow = yield openAndLoadWindow(undefined, true); + try { + let addonBar = otherWindow.document.getElementById("addon-bar"); + let otherMigrationArray = addonBar.getMigratedItems(); + is(migrationArray.length, otherMigrationArray.length, + "Other window should have the same number of migrated items."); + if (migrationArray.length == otherMigrationArray.length) { + for (let widget of migrationArray) { + isnot(otherMigrationArray.indexOf(widget), -1, + "Migrated widget " + widget + " should also be listed as migrated in the other window."); + } + } + } finally { + yield promiseWindowClosed(otherWindow); + } + btn.remove(); + btn2.remove(); + CustomizableUI.reset(); +}); diff --git a/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js b/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js new file mode 100644 index 000000000..a724b0c7f --- /dev/null +++ b/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-destroy-in-palette"; + +// Check destroyWidget destroys the node if it's in the palette +add_task(function*() { + CustomizableUI.createWidget({id: kWidgetId, label: "Test destroying widgets in palette."}); + yield startCustomizing(); + yield endCustomizing(); + ok(gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget still exists in palette."); + CustomizableUI.destroyWidget(kWidgetId); + ok(!gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget no longer exists in palette."); +}); diff --git a/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js b/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js new file mode 100644 index 000000000..6b8acbee0 --- /dev/null +++ b/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kWidgetId = "test-private-browsing-customize-mode-widget"; + +// Add a widget via the API with showInPrivateBrowsing set to false +// and ensure it does not appear in the list of unused widgets in private +// windows. +add_task(function* testPrivateBrowsingCustomizeModeWidget() { + CustomizableUI.createWidget({ + id: kWidgetId, + showInPrivateBrowsing: false + }); + + let normalWidgetArray = CustomizableUI.getUnusedWidgets(gNavToolbox.palette); + normalWidgetArray = normalWidgetArray.map((w) => w.id); + ok(normalWidgetArray.indexOf(kWidgetId) > -1, + "Widget should appear as unused in non-private window"); + + let privateWindow = yield openAndLoadWindow({private: true}); + let privateWidgetArray = CustomizableUI.getUnusedWidgets(privateWindow.gNavToolbox.palette); + privateWidgetArray = privateWidgetArray.map((w) => w.id); + is(privateWidgetArray.indexOf(kWidgetId), -1, + "Widget should not appear as unused in private window"); + yield promiseWindowClosed(privateWindow); + + CustomizableUI.destroyWidget(kWidgetId); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_addons.js b/browser/components/customizableui/test/browser_947914_button_addons.js new file mode 100644 index 000000000..b942ee771 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_addons.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialLocation = gBrowser.currentURI.spec; +var newTab = null; + +add_task(function*() { + info("Check addons button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let addonsButton = document.getElementById("add-ons-button"); + ok(addonsButton, "Add-ons button exists in Panel Menu"); + addonsButton.click(); + + newTab = gBrowser.selectedTab; + yield waitForCondition(() => gBrowser.currentURI && + gBrowser.currentURI.spec == "about:addons"); + + let addonsPage = gBrowser.selectedBrowser.contentWindow.document. + getElementById("addons-page"); + ok(addonsPage, "Add-ons page was opened"); +}); + +add_task(function* asyncCleanup() { + gBrowser.addTab(initialLocation); + gBrowser.removeTab(gBrowser.selectedTab); + info("Tabs were restored"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_copy.js b/browser/components/customizableui/test/browser_947914_button_copy.js new file mode 100644 index 000000000..c778c956f --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_copy.js @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialLocation = gBrowser.currentURI.spec; +var globalClipboard; + +add_task(function*() { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() { + info("Check copy button existence and functionality"); + + let testText = "copy text test"; + + gURLBar.focus(); + info("The URL bar was focused"); + yield PanelUI.show(); + info("Menu panel was opened"); + + let copyButton = document.getElementById("copy-button"); + ok(copyButton, "Copy button exists in Panel Menu"); + ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled"); + + // copy text from URL bar + gURLBar.value = testText; + gURLBar.focus(); + gURLBar.select(); + yield PanelUI.show(); + info("Menu panel was opened"); + + ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting"); + + copyButton.click(); + is(gURLBar.value, testText, "Selected text is unaltered when clicking copy"); + + // check that the text was added to the clipboard + let clipboard = Services.clipboard; + let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + globalClipboard = clipboard.kGlobalClipboard; + + transferable.init(null); + transferable.addDataFlavor("text/unicode"); + clipboard.getData(transferable, globalClipboard); + let str = {}, strLength = {}; + transferable.getTransferData("text/unicode", str, strLength); + let clipboardValue = ""; + + if (str.value) { + str.value.QueryInterface(Ci.nsISupportsString); + clipboardValue = str.value.data; + } + is(clipboardValue, testText, "Data was copied to the clipboard."); + }); +}); + +registerCleanupFunction(function cleanup() { + Services.clipboard.emptyClipboard(globalClipboard); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_cut.js b/browser/components/customizableui/test/browser_947914_button_cut.js new file mode 100644 index 000000000..e6e614368 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_cut.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialLocation = gBrowser.currentURI.spec; +var globalClipboard; + +add_task(function*() { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() { + info("Check cut button existence and functionality"); + + let testText = "cut text test"; + + gURLBar.focus(); + yield PanelUI.show(); + info("Menu panel was opened"); + + let cutButton = document.getElementById("cut-button"); + ok(cutButton, "Cut button exists in Panel Menu"); + ok(cutButton.hasAttribute("disabled"), "Cut button is disabled"); + + // cut text from URL bar + gURLBar.value = testText; + gURLBar.focus(); + gURLBar.select(); + yield PanelUI.show(); + info("Menu panel was opened"); + + ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting"); + cutButton.click(); + is(gURLBar.value, "", "Selected text is removed from source when clicking on cut"); + + // check that the text was added to the clipboard + let clipboard = Services.clipboard; + let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + globalClipboard = clipboard.kGlobalClipboard; + + transferable.init(null); + transferable.addDataFlavor("text/unicode"); + clipboard.getData(transferable, globalClipboard); + let str = {}, strLength = {}; + transferable.getTransferData("text/unicode", str, strLength); + let clipboardValue = ""; + + if (str.value) { + str.value.QueryInterface(Ci.nsISupportsString); + clipboardValue = str.value.data; + } + is(clipboardValue, testText, "Data was copied to the clipboard."); + }); +}); + +registerCleanupFunction(function cleanup() { + Services.clipboard.emptyClipboard(globalClipboard); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_find.js b/browser/components/customizableui/test/browser_947914_button_find.js new file mode 100644 index 000000000..cf3b79e34 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_find.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check find button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let findButton = document.getElementById("find-button"); + ok(findButton, "Find button exists in Panel Menu"); + + findButton.click(); + ok(!gFindBar.hasAttribute("hidden"), "Findbar opened successfully"); + + // close find bar + gFindBar.close(); + info("Findbar was closed"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_history.js b/browser/components/customizableui/test/browser_947914_button_history.js new file mode 100644 index 000000000..64080fcc3 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_history.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check history button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let historyButton = document.getElementById("history-panelmenu"); + ok(historyButton, "History button appears in Panel Menu"); + + historyButton.click(); + let historyPanel = document.getElementById("PanelUI-history"); + ok(historyPanel.getAttribute("current"), "History Panel is in view"); + + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise + info("Menu panel was closed"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js b/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js new file mode 100644 index 000000000..c2006bef0 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check private browsing button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let windowWasHandled = false; + let privateWindow = null; + + let observerWindowOpened = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + privateWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow); + privateWindow.addEventListener("load", function newWindowHandler() { + privateWindow.removeEventListener("load", newWindowHandler, false); + is(privateWindow.location.href, "chrome://browser/content/browser.xul", + "A new browser window was opened"); + ok(PrivateBrowsingUtils.isWindowPrivate(privateWindow), "Window is private"); + windowWasHandled = true; + }, false); + } + } + } + + Services.ww.registerNotification(observerWindowOpened); + + let privateBrowsingButton = document.getElementById("privatebrowsing-button"); + ok(privateBrowsingButton, "Private browsing button exists in Panel Menu"); + privateBrowsingButton.click(); + + try { + yield waitForCondition(() => windowWasHandled); + yield promiseWindowClosed(privateWindow); + info("The new private window was closed"); + } + catch (e) { + ok(false, "The new private browser window was not properly handled"); + } + finally { + Services.ww.unregisterNotification(observerWindowOpened); + } +}); diff --git a/browser/components/customizableui/test/browser_947914_button_newWindow.js b/browser/components/customizableui/test/browser_947914_button_newWindow.js new file mode 100644 index 000000000..47162ee86 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_newWindow.js @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check new window button existence and functionality"); + yield PanelUI.show(); + info("Menu panel was opened"); + + let windowWasHandled = false; + let newWindow = null; + + let observerWindowOpened = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + newWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow); + newWindow.addEventListener("load", function newWindowHandler() { + newWindow.removeEventListener("load", newWindowHandler, false); + is(newWindow.location.href, "chrome://browser/content/browser.xul", + "A new browser window was opened"); + ok(!PrivateBrowsingUtils.isWindowPrivate(newWindow), "Window is not private"); + windowWasHandled = true; + }, false); + } + } + } + + Services.ww.registerNotification(observerWindowOpened); + + let newWindowButton = document.getElementById("new-window-button"); + ok(newWindowButton, "New Window button exists in Panel Menu"); + newWindowButton.click(); + + try { + yield waitForCondition(() => windowWasHandled); + yield promiseWindowClosed(newWindow); + info("The new window was closed"); + } + catch (e) { + ok(false, "The new browser window was not properly handled"); + } + finally { + Services.ww.unregisterNotification(observerWindowOpened); + } +}); diff --git a/browser/components/customizableui/test/browser_947914_button_paste.js b/browser/components/customizableui/test/browser_947914_button_paste.js new file mode 100644 index 000000000..fc83ead56 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_paste.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialLocation = gBrowser.currentURI.spec; +var globalClipboard; + +add_task(function*() { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() { + info("Check paste button existence and functionality"); + + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); + globalClipboard = Services.clipboard.kGlobalClipboard; + + yield PanelUI.show(); + info("Menu panel was opened"); + + let pasteButton = document.getElementById("paste-button"); + ok(pasteButton, "Paste button exists in Panel Menu"); + + // add text to clipboard + let text = "Sample text for testing"; + clipboard.copyString(text); + + // test paste button by pasting text to URL bar + gURLBar.focus(); + yield PanelUI.show(); + info("Menu panel was opened"); + + ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled"); + pasteButton.click(); + + is(gURLBar.value, text, "Text pasted successfully"); + }); +}); + +registerCleanupFunction(function cleanup() { + Services.clipboard.emptyClipboard(globalClipboard); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_print.js b/browser/components/customizableui/test/browser_947914_button_print.js new file mode 100644 index 000000000..af7abcaeb --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_print.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const isOSX = (Services.appinfo.OS === "Darwin"); + +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "http://example.com/", + }, function* () { + info("Check print button existence and functionality"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + yield waitForCondition(() => document.getElementById("print-button") != null); + + let printButton = document.getElementById("print-button"); + ok(printButton, "Print button exists in Panel Menu"); + + if (isOSX) { + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + info("Menu panel was closed"); + } + else { + printButton.click(); + yield waitForCondition(() => gInPrintPreviewMode); + + ok(gInPrintPreviewMode, "Entered print preview mode"); + + // close print preview + if (gInPrintPreviewMode) { + PrintUtils.exitPrintPreview(); + yield waitForCondition(() => !window.gInPrintPreviewMode); + info("Exited print preview") + } + } + }); +}); + diff --git a/browser/components/customizableui/test/browser_947914_button_savePage.js b/browser/components/customizableui/test/browser_947914_button_savePage.js new file mode 100644 index 000000000..543ff3ca6 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_savePage.js @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + info("Check save page button existence"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let savePageButton = document.getElementById("save-page-button"); + ok(savePageButton, "Save Page button exists in Panel Menu"); + + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + info("Menu panel was closed"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_zoomIn.js b/browser/components/customizableui/test/browser_947914_button_zoomIn.js new file mode 100644 index 000000000..4463d87d6 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_zoomIn.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialPageZoom = ZoomManager.zoom; + +add_task(function*() { + info("Check zoom in button existence and functionality"); + + is(initialPageZoom, 1, "Initial zoom factor should be 1"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let zoomInButton = document.getElementById("zoom-in-button"); + ok(zoomInButton, "Zoom in button exists in Panel Menu"); + + zoomInButton.click(); + let pageZoomLevel = parseInt(ZoomManager.zoom * 100); + let zoomResetButton = document.getElementById("zoom-reset-button"); + let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10); + ok(pageZoomLevel > 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed in correctly"); + + // close the Panel + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + info("Menu panel was closed"); +}); + +add_task(function* asyncCleanup() { + // reset zoom level + ZoomManager.zoom = initialPageZoom; + info("Zoom level was restored"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_zoomOut.js b/browser/components/customizableui/test/browser_947914_button_zoomOut.js new file mode 100644 index 000000000..f9f51ac9a --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_zoomOut.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialPageZoom = ZoomManager.zoom; + +add_task(function*() { + info("Check zoom out button existence and functionality"); + + is(initialPageZoom, 1, "Initial zoom factor should be 1"); + + yield PanelUI.show(); + info("Menu panel was opened"); + + let zoomOutButton = document.getElementById("zoom-out-button"); + ok(zoomOutButton, "Zoom out button exists in Panel Menu"); + + zoomOutButton.click(); + let pageZoomLevel = Math.round(ZoomManager.zoom * 100); + + let zoomResetButton = document.getElementById("zoom-reset-button"); + let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10); + ok(pageZoomLevel < 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed out correctly"); + + // close the panel + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + info("Menu panel was closed"); +}); + +add_task(function* asyncCleanup() { + // reset zoom level + ZoomManager.zoom = initialPageZoom; + info("Zoom level was restored"); +}); diff --git a/browser/components/customizableui/test/browser_947914_button_zoomReset.js b/browser/components/customizableui/test/browser_947914_button_zoomReset.js new file mode 100644 index 000000000..372097665 --- /dev/null +++ b/browser/components/customizableui/test/browser_947914_button_zoomReset.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var initialPageZoom = ZoomManager.zoom; + +add_task(function*() { + info("Check zoom reset button existence and functionality"); + + is(initialPageZoom, 1, "Page zoom reset correctly"); + ZoomManager.zoom = 0.5; + yield PanelUI.show(); + info("Menu panel was opened"); + + let zoomResetButton = document.getElementById("zoom-reset-button"); + ok(zoomResetButton, "Zoom reset button exists in Panel Menu"); + + zoomResetButton.click(); + yield new Promise(SimpleTest.executeSoon); + + let pageZoomLevel = Math.floor(ZoomManager.zoom * 100); + let expectedZoomLevel = 100; + let buttonZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10); + is(pageZoomLevel, expectedZoomLevel, "Page zoom reset correctly"); + is(pageZoomLevel, buttonZoomLevel, "Button displays the correct zoom level"); + + // close the panel + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + info("Menu panel was closed"); +}); + +add_task(function* asyncCleanup() { + // reset zoom level + ZoomManager.zoom = initialPageZoom; + info("Zoom level was restored"); +}); diff --git a/browser/components/customizableui/test/browser_947987_removable_default.js b/browser/components/customizableui/test/browser_947987_removable_default.js new file mode 100644 index 000000000..98325ec2a --- /dev/null +++ b/browser/components/customizableui/test/browser_947987_removable_default.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var kWidgetId = "test-removable-widget-default"; +const kNavBar = CustomizableUI.AREA_NAVBAR; +var widgetCounter = 0; + +registerCleanupFunction(removeCustomToolbars); + +// Sanity checks +add_task(function() { + let brokenSpec = {id: kWidgetId + (widgetCounter++), removable: false}; + SimpleTest.doesThrow(() => CustomizableUI.createWidget(brokenSpec), + "Creating non-removable widget without defaultArea should throw."); + + // Widget without removable set should be removable: + let wrapper = CustomizableUI.createWidget({id: kWidgetId + (widgetCounter++)}); + ok(CustomizableUI.isWidgetRemovable(wrapper.id), "Should be removable by default."); + CustomizableUI.destroyWidget(wrapper.id); +}); + +// Test non-removable widget with defaultArea +add_task(function*() { + // Non-removable widget with defaultArea should work: + let spec = {id: kWidgetId + (widgetCounter++), removable: false, + defaultArea: kNavBar}; + let widgetWrapper; + try { + widgetWrapper = CustomizableUI.createWidget(spec); + } catch (ex) { + ok(false, "Creating a non-removable widget with a default area should not throw."); + return; + } + + let placement = CustomizableUI.getPlacementOfWidget(spec.id); + ok(placement, "Widget should be placed."); + is(placement.area, kNavBar, "Widget should be in navbar"); + let singleWrapper = widgetWrapper.forWindow(window); + ok(singleWrapper, "Widget should exist in window."); + ok(singleWrapper.node, "Widget node should exist in window."); + let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, window); + is(singleWrapper.node.parentNode, expectedParent, "Widget should be in navbar."); + + let otherWin = yield openAndLoadWindow(true); + placement = CustomizableUI.getPlacementOfWidget(spec.id); + ok(placement, "Widget should be placed."); + is(placement && placement.area, kNavBar, "Widget should be in navbar"); + + singleWrapper = widgetWrapper.forWindow(otherWin); + ok(singleWrapper, "Widget should exist in other window."); + if (singleWrapper) { + ok(singleWrapper.node, "Widget node should exist in other window."); + if (singleWrapper.node) { + let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, otherWin); + is(singleWrapper.node.parentNode, expectedParent, + "Widget should be in navbar in other window."); + } + } + CustomizableUI.destroyWidget(spec.id); + yield promiseWindowClosed(otherWin); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js b/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js new file mode 100644 index 000000000..456c9ed02 --- /dev/null +++ b/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js @@ -0,0 +1,32 @@ +/* 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/. */ + +const kWidgetId = "test-destroy-non-removable-defaultArea"; + +add_task(function() { + let spec = {id: kWidgetId, label: "Test non-removable defaultArea re-adding.", + removable: false, defaultArea: CustomizableUI.AREA_NAVBAR}; + CustomizableUI.createWidget(spec); + let placement = CustomizableUI.getPlacementOfWidget(kWidgetId); + ok(placement, "Should have placed the widget."); + is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar"); + CustomizableUI.destroyWidget(kWidgetId); + CustomizableUI.removeWidgetFromArea(kWidgetId); + + CustomizableUI.createWidget(spec); + ok(placement, "Should have placed the widget."); + is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar"); + CustomizableUI.destroyWidget(kWidgetId); + CustomizableUI.removeWidgetFromArea(kWidgetId); + + const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd"; + Services.prefs.setBoolPref(kPrefCustomizationAutoAdd, false); + CustomizableUI.createWidget(spec); + ok(placement, "Should have placed the widget."); + is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar"); + CustomizableUI.destroyWidget(kWidgetId); + CustomizableUI.removeWidgetFromArea(kWidgetId); + Services.prefs.clearUserPref(kPrefCustomizationAutoAdd); +}); + diff --git a/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js b/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js new file mode 100644 index 000000000..fc05a99fd --- /dev/null +++ b/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarName = "test-unregisterArea-areaType"; +const kUnregisterAreaTestWidget = "test-widget-for-unregisterArea-areaType"; +const kTestWidget = "test-widget-no-area-areaType"; +registerCleanupFunction(removeCustomToolbars); + +function checkAreaType(widget) { + try { + is(widget.areaType, null, "areaType should be null"); + } catch (ex) { + info("Fetching areaType threw: " + ex); + ok(false, "areaType getter shouldn't throw."); + } +} + +// widget wrappers in unregisterArea'd areas and nowhere shouldn't throw when checking areaTypes. +add_task(function*() { + // Using the ID before it's been created will imply a XUL wrapper; we'll test + // an API-based wrapper below + let toolbarNode = createToolbarWithPlacements(kToolbarName, [kUnregisterAreaTestWidget]); + CustomizableUI.unregisterArea(kToolbarName); + toolbarNode.remove(); + + let w = CustomizableUI.getWidget(kUnregisterAreaTestWidget); + checkAreaType(w); + + w = CustomizableUI.getWidget(kTestWidget); + checkAreaType(w); + + let spec = {id: kUnregisterAreaTestWidget, type: 'button', removable: true, + label: "areaType test", tooltiptext: "areaType test"}; + CustomizableUI.createWidget(spec); + toolbarNode = createToolbarWithPlacements(kToolbarName, [kUnregisterAreaTestWidget]); + CustomizableUI.unregisterArea(kToolbarName); + toolbarNode.remove(); + w = CustomizableUI.getWidget(spec.id); + checkAreaType(w); + CustomizableUI.removeWidgetFromArea(kUnregisterAreaTestWidget); + checkAreaType(w); + // XXXgijs: ensure cleanup function doesn't barf: + gAddedToolbars.delete(kToolbarName); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); + diff --git a/browser/components/customizableui/test/browser_956602_remove_special_widget.js b/browser/components/customizableui/test/browser_956602_remove_special_widget.js new file mode 100644 index 000000000..f87b2e4c8 --- /dev/null +++ b/browser/components/customizableui/test/browser_956602_remove_special_widget.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + + +// Adding a separator and then dragging it out of the navbar shouldn't throw +add_task(function*() { + try { + let navbar = document.getElementById("nav-bar"); + let separatorSelector = "toolbarseparator[id^=customizableui-special-separator]"; + ok(!navbar.querySelector(separatorSelector), "Shouldn't be a separator in the navbar"); + CustomizableUI.addWidgetToArea('separator', 'nav-bar'); + yield startCustomizing(); + let separator = navbar.querySelector(separatorSelector); + ok(separator, "There should be a separator in the navbar now."); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(separator, palette); + ok(!palette.querySelector(separatorSelector), "No separator in the palette."); + } catch (ex) { + Cu.reportError(ex); + ok(false, "Shouldn't throw an exception moving an item to the navbar."); + } finally { + yield endCustomizing(); + } +}); + +add_task(function* asyncCleanup() { + resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js b/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js new file mode 100644 index 000000000..7c4f6cfa4 --- /dev/null +++ b/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var originalWindowWidth; + +// Drag to overflow chevron should open the overflow panel. +add_task(function*() { + originalWindowWidth = window.outerWidth; + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + let widgetOverflowPanel = document.getElementById("widget-overflow"); + let panelShownPromise = promisePanelElementShown(window, widgetOverflowPanel); + let identityBox = document.getElementById("identity-box"); + let overflowChevron = document.getElementById("nav-bar-overflow-button"); + + // Listen for hiding immediately so we don't miss the event because of the + // async-ness of the 'shown' yield... + let panelHiddenPromise = promisePanelElementHidden(window, widgetOverflowPanel); + + var ds = Components.classes["@mozilla.org/widget/dragservice;1"]. + getService(Components.interfaces.nsIDragService); + + ds.startDragSession(); + try { + var [result, dataTransfer] = EventUtils.synthesizeDragOver(identityBox, overflowChevron); + + // Wait for showing panel before ending drag session. + yield panelShownPromise; + + EventUtils.synthesizeDropAfterDragOver(result, dataTransfer, overflowChevron); + } finally { + ds.endDragSession(true); + } + + info("Overflow panel is shown."); + + widgetOverflowPanel.hidePopup(); + yield panelHiddenPromise; +}); + +add_task(function*() { + window.resizeTo(originalWindowWidth, window.outerHeight); + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); + ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar."); +}); diff --git a/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js new file mode 100644 index 000000000..cf2603999 --- /dev/null +++ b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + const kNormalLabel = "Character Encoding"; + CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR); + let characterEncoding = document.getElementById("characterencoding-button"); + const kOriginalLabel = characterEncoding.getAttribute("label"); + characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel); + CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_PANEL); + + yield PanelUI.show(); + + is(characterEncoding.getAttribute("auto-hyphens"), "off", + "Hyphens should be disabled if the ­ character is present in the label"); + let multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text"); + let multilineTextCS = getComputedStyle(multilineText); + is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the ­ character is present.") + + let hiddenPanelPromise = promisePanelHidden(window); + PanelUI.toggle(); + yield hiddenPanelPromise; + + characterEncoding.setAttribute("label", kNormalLabel); + + yield PanelUI.show(); + + isnot(characterEncoding.getAttribute("auto-hyphens"), "off", + "Hyphens should not be disabled if the ­ character is not present in the label"); + multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text"); + multilineTextCS = getComputedStyle(multilineText); + is(multilineTextCS.MozHyphens, "auto", "-moz-hyphens should be set to auto by default.") + + hiddenPanelPromise = promisePanelHidden(window); + PanelUI.toggle(); + yield hiddenPanelPromise; + + characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel); + CustomizableUI.removeWidgetFromArea("characterencoding-button"); + yield startCustomizing(); + + isnot(characterEncoding.getAttribute("auto-hyphens"), "off", + "Hyphens should not be disabled when the widget is in the palette"); + + gCustomizeMode.addToPanel(characterEncoding); + is(characterEncoding.getAttribute("auto-hyphens"), "off", + "Hyphens should be disabled if the ­ character is present in the label in customization mode"); + multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text"); + multilineTextCS = getComputedStyle(multilineText); + is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the ­ character is present in customization mode.") + + yield endCustomizing(); + + CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR); + ok(!characterEncoding.hasAttribute("auto-hyphens"), + "Removing the widget from the panel should remove the auto-hyphens attribute"); + + characterEncoding.setAttribute("label", kOriginalLabel); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js new file mode 100644 index 000000000..e5710c50a --- /dev/null +++ b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbar = "test-toolbar-963639-non-customizable-customizing-attribute"; + +add_task(function*() { + info("Test for Bug 963639 - CustomizeMode _onToolbarVisibilityChange sets @customizing on non-customizable toolbars"); + + let toolbar = document.createElement("toolbar"); + toolbar.id = kToolbar; + gNavToolbox.appendChild(toolbar); + + let testToolbar = document.getElementById(kToolbar) + ok(testToolbar, "Toolbar was created."); + is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 1, + "Toolbar was added to the navigator toolbox"); + + toolbar.setAttribute("toolbarname", "NonCustomizableToolbarCustomizingAttribute"); + toolbar.setAttribute("collapsed", "true"); + + yield startCustomizing(); + window.setToolbarVisibility(toolbar, "true"); + isnot(toolbar.getAttribute("customizing"), "true", + "Toolbar doesn't have the customizing attribute"); + + yield endCustomizing(); + gNavToolbox.removeChild(toolbar); + + is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 0, + "Toolbar was removed from the navigator toolbox"); +}); diff --git a/browser/components/customizableui/test/browser_967000_button_charEncoding.js b/browser/components/customizableui/test/browser_967000_button_charEncoding.js new file mode 100644 index 000000000..0688ebbd6 --- /dev/null +++ b/browser/components/customizableui/test/browser_967000_button_charEncoding.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html"; + +add_task(function*() { + info("Check Character Encoding button functionality"); + + // add the Character Encoding button to the panel + CustomizableUI.addWidgetToArea("characterencoding-button", + CustomizableUI.AREA_PANEL); + + // check the button's functionality + yield PanelUI.show(); + + let charEncodingButton = document.getElementById("characterencoding-button"); + ok(charEncodingButton, "The Character Encoding button was added to the Panel Menu"); + is(charEncodingButton.getAttribute("disabled"), "true", + "The Character encoding button is initially disabled"); + + let panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + + let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE, true, true); + + yield PanelUI.show(); + ok(!charEncodingButton.hasAttribute("disabled"), "The Character encoding button gets enabled"); + let characterEncodingView = document.getElementById("PanelUI-characterEncodingView"); + let subviewShownPromise = subviewShown(characterEncodingView); + charEncodingButton.click(); + yield subviewShownPromise; + + ok(characterEncodingView.hasAttribute("current"), "The Character encoding panel is displayed"); + + let pinnedEncodings = document.getElementById("PanelUI-characterEncodingView-pinned"); + let charsetsList = document.getElementById("PanelUI-characterEncodingView-charsets"); + ok(pinnedEncodings, "Pinned charsets are available"); + ok(charsetsList, "Charsets list is available"); + + let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']"); + is(checkedButtons.length, 2, "There should be 2 checked items (1 charset, 1 detector)."); + is(checkedButtons[0].getAttribute("label"), "Unicode", "The unicode encoding is correctly selected"); + is(characterEncodingView.querySelectorAll("#PanelUI-characterEncodingView-autodetect toolbarbutton[checked='true']").length, + 1, + "There should be 1 checked detector."); + + panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + + yield BrowserTestUtils.removeTab(newTab); +}); + +add_task(function* asyncCleanup() { + // reset the panel to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The UI is in default state again."); +}); diff --git a/browser/components/customizableui/test/browser_967000_button_feeds.js b/browser/components/customizableui/test/browser_967000_button_feeds.js new file mode 100644 index 000000000..8f391941a --- /dev/null +++ b/browser/components/customizableui/test/browser_967000_button_feeds.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html"; +const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml" + +var newTab = null; +var initialLocation = gBrowser.currentURI.spec; + +add_task(function*() { + info("Check Subscribe button functionality"); + + // add the Subscribe button to the panel + CustomizableUI.addWidgetToArea("feed-button", + CustomizableUI.AREA_PANEL); + + // check the button's functionality + yield PanelUI.show(); + + let feedButton = document.getElementById("feed-button"); + ok(feedButton, "The Subscribe button was added to the Panel Menu"); + is(feedButton.getAttribute("disabled"), "true", "The Subscribe button is initially disabled"); + + let panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + + newTab = gBrowser.selectedTab; + yield promiseTabLoadEvent(newTab, TEST_PAGE); + + yield PanelUI.show(); + + yield waitForCondition(() => !feedButton.hasAttribute("disabled")); + ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled"); + + feedButton.click(); + yield promiseTabLoadEvent(newTab, TEST_FEED); + + is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened"); + ok(!isPanelUIOpen(), "Panel is closed"); + + if (isPanelUIOpen()) { + panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + } +}); + +add_task(function* asyncCleanup() { + // reset the panel UI to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The UI is in default state again."); + + // restore the initial location + gBrowser.addTab(initialLocation); + gBrowser.removeTab(newTab); +}); diff --git a/browser/components/customizableui/test/browser_967000_button_sync.js b/browser/components/customizableui/test/browser_967000_button_sync.js new file mode 100644 index 000000000..15a3235e0 --- /dev/null +++ b/browser/components/customizableui/test/browser_967000_button_sync.js @@ -0,0 +1,335 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +let {SyncedTabs} = Cu.import("resource://services-sync/SyncedTabs.jsm", {}); + +XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm"); + +// These are available on the widget implementation, but it seems impossible +// to grab that impl at runtime. +const DECKINDEX_TABS = 0; +const DECKINDEX_TABSDISABLED = 1; +const DECKINDEX_FETCHING = 2; +const DECKINDEX_NOCLIENTS = 3; + +var initialLocation = gBrowser.currentURI.spec; +var newTab = null; + +// A helper to notify there are new tabs. Returns a promise that is resolved +// once the UI has been updated. +function updateTabsPanel() { + let promiseTabsUpdated = promiseObserverNotified("synced-tabs-menu:test:tabs-updated"); + Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED, null); + return promiseTabsUpdated; +} + +// This is the mock we use for SyncedTabs.jsm - tests may override various +// functions. +let mockedInternal = { + get isConfiguredToSyncTabs() { return true; }, + getTabClients() { return []; }, + syncTabs() {}, + hasSyncedThisSession: false, +}; + + +add_task(function* setup() { + let oldInternal = SyncedTabs._internal; + SyncedTabs._internal = mockedInternal; + + registerCleanupFunction(() => { + SyncedTabs._internal = oldInternal; + }); +}); + +// The test expects the about:preferences#sync page to open in the current tab +function* openPrefsFromMenuPanel(expectedPanelId, entryPoint) { + info("Check Sync button functionality"); + Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", "http://example.com/"); + + // add the Sync button to the panel + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + + // check the button's functionality + yield PanelUI.show(); + + if (entryPoint == "uitour") { + UITour.tourBrowsersByWindow.set(window, new Set()); + UITour.tourBrowsersByWindow.get(window).add(gBrowser.selectedBrowser); + } + + let syncButton = document.getElementById("sync-button"); + ok(syncButton, "The Sync button was added to the Panel Menu"); + + syncButton.click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + // Sync is not configured - verify that state is reflected. + let subpanel = document.getElementById(expectedPanelId) + ok(!subpanel.hidden, "sync setup element is visible"); + + // Find and click the "setup" button. + let setupButton = subpanel.querySelector(".PanelUI-remotetabs-prefs-button"); + setupButton.click(); + + let deferred = Promise.defer(); + let handler = (e) => { + if (e.originalTarget != gBrowser.selectedBrowser.contentDocument || + e.target.location.href == "about:blank") { + info("Skipping spurious 'load' event for " + e.target.location.href); + return; + } + gBrowser.selectedBrowser.removeEventListener("load", handler, true); + deferred.resolve(); + } + gBrowser.selectedBrowser.addEventListener("load", handler, true); + + yield deferred.promise; + newTab = gBrowser.selectedTab; + + is(gBrowser.currentURI.spec, "about:preferences?entrypoint=" + entryPoint + "#sync", + "Firefox Sync preference page opened with `menupanel` entrypoint"); + ok(!isPanelUIOpen(), "The panel closed"); + + if (isPanelUIOpen()) { + let panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + } +} + +function* asyncCleanup() { + Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); + // reset the panel UI to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The panel UI is in default state again."); + + // restore the tabs + gBrowser.addTab(initialLocation); + gBrowser.removeTab(newTab); + UITour.tourBrowsersByWindow.delete(window); +} + +// When Sync is not setup. +add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "synced-tabs")); +add_task(asyncCleanup); + +// When Sync is configured in a "needs reauthentication" state. +add_task(function* () { + // configure our broadcasters so we are in the right state. + document.getElementById("sync-reauth-state").hidden = false; + document.getElementById("sync-setup-state").hidden = true; + document.getElementById("sync-syncnow-state").hidden = true; + yield openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "synced-tabs") +}); + +// Test the mobile promo links +add_task(function* () { + // change the preferences for the mobile links. + Services.prefs.setCharPref("identity.mobilepromo.android", "http://example.com/?os=android&tail="); + Services.prefs.setCharPref("identity.mobilepromo.ios", "http://example.com/?os=ios&tail="); + + mockedInternal.getTabClients = () => []; + mockedInternal.syncTabs = () => Promise.resolve(); + + document.getElementById("sync-reauth-state").hidden = true; + document.getElementById("sync-setup-state").hidden = true; + document.getElementById("sync-syncnow-state").hidden = false; + + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + + let syncPanel = document.getElementById("PanelUI-remotetabs"); + let links = syncPanel.querySelectorAll(".remotetabs-promo-link"); + + is(links.length, 2, "found 2 links as expected"); + + // test each link and left and middle mouse buttons + for (let link of links) { + for (let button = 0; button < 2; button++) { + yield PanelUI.show(); + EventUtils.sendMouseEvent({ type: "click", button }, link, window); + // the panel should have been closed. + ok(!isPanelUIOpen(), "click closed the panel"); + // should be a new tab - wait for the load. + is(gBrowser.tabs.length, 2, "there's a new tab"); + yield new Promise(resolve => { + if (gBrowser.selectedBrowser.currentURI.spec == "about:blank") { + gBrowser.selectedBrowser.addEventListener("load", function listener(e) { + gBrowser.selectedBrowser.removeEventListener("load", listener, true); + resolve(); + }, true); + return; + } + // the new tab has already transitioned away from about:blank so we + // are good to go. + resolve(); + }); + + let os = link.getAttribute("mobile-promo-os"); + let expectedUrl = `http://example.com/?os=${os}&tail=synced-tabs`; + is(gBrowser.selectedBrowser.currentURI.spec, expectedUrl, "correct URL"); + gBrowser.removeTab(gBrowser.selectedTab); + } + } + + // test each link and right mouse button - should be a noop. + yield PanelUI.show(); + for (let link of links) { + EventUtils.sendMouseEvent({ type: "click", button: 2 }, link, window); + // the panel should still be open + ok(isPanelUIOpen(), "panel remains open after right-click"); + is(gBrowser.tabs.length, 1, "no new tab was opened"); + } + PanelUI.hide(); + + Services.prefs.clearUserPref("identity.mobilepromo.android"); + Services.prefs.clearUserPref("identity.mobilepromo.ios"); +}); + +// Test the "Sync Now" button +add_task(function* () { + mockedInternal.getTabClients = () => []; + mockedInternal.syncTabs = () => { + return Promise.resolve(); + } + + // configure our broadcasters so we are in the right state. + document.getElementById("sync-reauth-state").hidden = true; + document.getElementById("sync-setup-state").hidden = true; + document.getElementById("sync-syncnow-state").hidden = false; + + // add the Sync button to the panel + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + yield PanelUI.show(); + document.getElementById("sync-button").click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + let subpanel = document.getElementById("PanelUI-remotetabs-main") + ok(!subpanel.hidden, "main pane is visible"); + let deck = document.getElementById("PanelUI-remotetabs-deck"); + + // The widget is still fetching tabs, as we've neutered everything that + // provides them + is(deck.selectedIndex, DECKINDEX_FETCHING, "first deck entry is visible"); + + let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow"); + + let didSync = false; + let oldDoSync = gSyncUI.doSync; + gSyncUI.doSync = function() { + didSync = true; + mockedInternal.hasSyncedThisSession = true; + gSyncUI.doSync = oldDoSync; + } + syncNowButton.click(); + ok(didSync, "clicking the button called the correct function"); + + // Tell the widget there are tabs available, but with zero clients. + mockedInternal.getTabClients = () => { + return Promise.resolve([]); + } + yield updateTabsPanel(); + // The UI should be showing the "no clients" pane. + is(deck.selectedIndex, DECKINDEX_NOCLIENTS, "no-clients deck entry is visible"); + + // Tell the widget there are tabs available - we have 3 clients, one with no + // tabs. + mockedInternal.getTabClients = () => { + return Promise.resolve([ + { + id: "guid_mobile", + type: "client", + name: "My Phone", + tabs: [], + }, + { + id: "guid_desktop", + type: "client", + name: "My Desktop", + tabs: [ + { + title: "http://example.com/10", + lastUsed: 10, // the most recent + }, + { + title: "http://example.com/1", + lastUsed: 1, // the least recent. + }, + { + title: "http://example.com/5", + lastUsed: 5, + }, + ], + }, + { + id: "guid_second_desktop", + name: "My Other Desktop", + tabs: [ + { + title: "http://example.com/6", + lastUsed: 6, + } + ], + }, + ]); + }; + yield updateTabsPanel(); + + // The UI should be showing tabs! + is(deck.selectedIndex, DECKINDEX_TABS, "no-clients deck entry is visible"); + let tabList = document.getElementById("PanelUI-remotetabs-tabslist"); + let node = tabList.firstChild; + // First entry should be the client with the most-recent tab. + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Desktop", "correct client"); + // Next entry is the most-recent tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/10"); + + // Next entry is the next-most-recent tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/5"); + + // Next entry is the least-recent tab from the first client. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/1"); + + // Next is a menuseparator between the clients. + node = node.nextSibling; + is(node.nodeName, "menuseparator"); + + // Next is the client with 1 tab. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Other Desktop", "correct client"); + // Its single tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/6"); + + // Next is a menuseparator between the clients. + node = node.nextSibling; + is(node.nodeName, "menuseparator"); + + // Next is the client with no tab. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Phone", "correct client"); + // There is a single node saying there's no tabs for the client. + node = node.nextSibling; + is(node.nodeName, "label", "node is a label"); + is(node.getAttribute("itemtype"), "", "node is neither a tab nor a client"); + + node = node.nextSibling; + is(node, null, "no more entries"); +}); diff --git a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js new file mode 100644 index 000000000..88c30bf81 --- /dev/null +++ b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Bug 968447 - The Bookmarks Toolbar Items doesn't appear as a +// normal menu panel button in new windows. +add_task(function*() { + const buttonId = "bookmarks-toolbar-placeholder"; + yield startCustomizing(); + CustomizableUI.addWidgetToArea("personal-bookmarks", CustomizableUI.AREA_PANEL); + yield endCustomizing(); + + yield PanelUI.show(); + + let bookmarksToolbarPlaceholder = document.getElementById(buttonId); + ok(bookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"), + "Button should have toolbarbutton-1 class"); + is(bookmarksToolbarPlaceholder.getAttribute("wrap"), "true", + "Button should have the 'wrap' attribute"); + + info("Waiting for panel to close"); + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + + info("Waiting for window to open"); + let newWin = yield openAndLoadWindow({}, true); + + info("Waiting for panel in new window to open"); + let hideTrace = function() { + info(new Error().stack); + info("Panel was hidden."); + }; + newWin.PanelUI.panel.addEventListener("popuphidden", hideTrace); + + yield newWin.PanelUI.show(); + let newWinBookmarksToolbarPlaceholder = newWin.document.getElementById(buttonId); + ok(newWinBookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"), + "Button in new window should have toolbarbutton-1 class"); + is(newWinBookmarksToolbarPlaceholder.getAttribute("wrap"), "true", + "Button in new window should have 'wrap' attribute"); + + newWin.PanelUI.panel.removeEventListener("popuphidden", hideTrace); + // XXXgijs on Linux, we're sometimes seeing the panel being hidden early + // in the newly created window, probably because something else steals focus. + if (newWin.PanelUI.panel.state != "closed") { + info("Panel is still open in new window, waiting for it to close"); + panelHiddenPromise = promisePanelHidden(newWin); + newWin.PanelUI.hide(); + yield panelHiddenPromise; + } else { + info("panel was already closed"); + } + + info("Waiting for new window to close"); + yield promiseWindowClosed(newWin); +}); + +add_task(function* asyncCleanUp() { + yield endCustomizing(); + CustomizableUI.reset(); +}); + diff --git a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js new file mode 100644 index 000000000..f7504fc41 --- /dev/null +++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kHidden1Id = "test-hidden-button-1"; +const kHidden2Id = "test-hidden-button-2"; + +var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + +// When we drag an item onto a customizable area, and not over a specific target, we +// should assume that we're appending them to the area. If doing so, we should scan +// backwards over any hidden items and insert the item before those hidden items. +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Should be in the default state"); + + // Iterate backwards over the items in the nav-bar until we find the first + // one that is not hidden. + let placements = CustomizableUI.getWidgetsInArea(CustomizableUI.AREA_NAVBAR); + let lastVisible = null; + for (let widgetGroup of placements.reverse()) { + let widget = widgetGroup.forWindow(window); + if (widget && widget.node && !widget.node.hidden) { + lastVisible = widget.node; + break; + } + } + + if (!lastVisible) { + ok(false, "Apparently, there are no visible items in the nav-bar."); + } + + info("The last visible item in the nav-bar has ID: " + lastVisible.id); + + let hidden1 = createDummyXULButton(kHidden1Id, "You can't see me"); + let hidden2 = createDummyXULButton(kHidden2Id, "You can't see me either."); + hidden1.hidden = hidden2.hidden = true; + + // Make sure we have some hidden items at the end of the nav-bar. + navbar.insertItem(hidden1.id); + navbar.insertItem(hidden2.id); + + // Drag an item and drop it onto the nav-bar customization target, but + // not over a particular item. + yield startCustomizing(); + let downloadsButton = document.getElementById("downloads-button"); + simulateItemDrag(downloadsButton, navbar.customizationTarget); + + yield endCustomizing(); + + is(downloadsButton.previousSibling.id, lastVisible.id, + "The downloads button should be placed after the last visible item."); + + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js b/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js new file mode 100644 index 000000000..b5479fcb7 --- /dev/null +++ b/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function getPlacementArea(id) { + let placement = CustomizableUI.getPlacementOfWidget(id); + return placement && placement.area; +} + +// Check that a destroyed widget recreated after a reset call goes to +// the navigation bar. +add_task(function() { + const kWidgetId = "test-recreate-after-reset"; + let spec = {id: kWidgetId, label: "Test re-create after reset.", + removable: true, defaultArea: CustomizableUI.AREA_NAVBAR}; + + CustomizableUI.createWidget(spec); + is(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR, + "widget is in the navigation bar"); + + CustomizableUI.destroyWidget(kWidgetId); + isnot(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR, + "widget removed from the navigation bar"); + + CustomizableUI.reset(); + + CustomizableUI.createWidget(spec); + is(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR, + "widget recreated and added back to the nav bar"); + + CustomizableUI.destroyWidget(kWidgetId); +}); diff --git a/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js b/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js new file mode 100644 index 000000000..6f057a100 --- /dev/null +++ b/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + + +// Adding the character encoding menu to the panel, exiting customize mode, +// and moving it to the nav-bar should have it enabled, not disabled. +add_task(function*() { + yield startCustomizing(); + CustomizableUI.addWidgetToArea("characterencoding-button", "PanelUI-contents"); + yield endCustomizing(); + yield PanelUI.show(); + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + CustomizableUI.addWidgetToArea("characterencoding-button", 'nav-bar'); + let button = document.getElementById("characterencoding-button"); + ok(!button.hasAttribute("disabled"), "Button shouldn't be disabled"); +}); + +add_task(function asyncCleanup() { + resetCustomization(); +}); + diff --git a/browser/components/customizableui/test/browser_970511_undo_restore_default.js b/browser/components/customizableui/test/browser_970511_undo_restore_default.js new file mode 100644 index 000000000..e7b3ca674 --- /dev/null +++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +// Restoring default should reset theme and show an "undo" option which undoes the restoring operation. +add_task(function*() { + let homeButtonId = "home-button"; + CustomizableUI.removeWidgetFromArea(homeButtonId); + yield startCustomizing(); + ok(!CustomizableUI.inDefaultState, "Not in default state to begin with"); + is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette"); + let undoResetButton = document.getElementById("customization-undo-reset-button"); + is(undoResetButton.hidden, true, "The undo button is hidden before reset"); + + let themesButton = document.getElementById("customization-lwtheme-button"); + let popup = document.getElementById("customization-lwtheme-menu"); + let popupShownPromise = popupShown(popup); + EventUtils.synthesizeMouseAtCenter(themesButton, {}); + info("Clicked on themes button"); + yield popupShownPromise; + + let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended"); + let firstLWTheme = recommendedHeader.nextSibling; + let firstLWThemeId = firstLWTheme.theme.id; + let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed"); + firstLWTheme.doCommand(); + info("Clicked on first theme"); + yield themeChangedPromise; + + is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme changed to first option"); + + yield gCustomizeMode.reset(); + + ok(CustomizableUI.inDefaultState, "In default state after reset"); + is(undoResetButton.hidden, false, "The undo button is visible after reset"); + is(LightweightThemeManager.currentTheme, null, "Theme reset to default"); + + yield gCustomizeMode.undoReset() + + is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme has been reset from default to original choice"); + ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset"); + is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button"); + is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette"); + + yield gCustomizeMode.reset(); +}); + +// Performing an action after a reset will hide the reset button. +add_task(function*() { + let homeButtonId = "home-button"; + CustomizableUI.removeWidgetFromArea(homeButtonId); + ok(!CustomizableUI.inDefaultState, "Not in default state to begin with"); + is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette"); + let undoResetButton = document.getElementById("customization-undo-reset-button"); + is(undoResetButton.hidden, true, "The undo button is hidden before reset"); + + yield gCustomizeMode.reset(); + + ok(CustomizableUI.inDefaultState, "In default state after reset"); + is(undoResetButton.hidden, false, "The undo button is visible after reset"); + + CustomizableUI.addWidgetToArea(homeButtonId, CustomizableUI.AREA_PANEL); + is(undoResetButton.hidden, true, "The undo button is hidden after another change"); +}); + +// "Restore defaults", exiting customize, and re-entering shouldn't show the Undo button +add_task(function*() { + let undoResetButton = document.getElementById("customization-undo-reset-button"); + is(undoResetButton.hidden, true, "The undo button is hidden before a reset"); + ok(!CustomizableUI.inDefaultState, "The browser should not be in default state"); + yield gCustomizeMode.reset(); + + is(undoResetButton.hidden, false, "The undo button is visible after a reset"); + yield endCustomizing(); + yield startCustomizing(); + is(undoResetButton.hidden, true, "The undo reset button should be hidden after entering customization mode"); +}); + +// Bug 971626 - Restore Defaults should collapse the Title Bar +add_task(function*() { + if (Services.appinfo.OS != "WINNT" && + Services.appinfo.OS != "Darwin") { + return; + } + let prefName = "browser.tabs.drawInTitlebar"; + let defaultValue = Services.prefs.getBoolPref(prefName); + let restoreDefaultsButton = document.getElementById("customization-reset-button"); + let titleBarButton = document.getElementById("customization-titlebar-visibility-button"); + let undoResetButton = document.getElementById("customization-undo-reset-button"); + ok(CustomizableUI.inDefaultState, "Should be in default state at start of test"); + ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled when in default state"); + is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect pref value"); + is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test"); + + Services.prefs.setBoolPref(prefName, !defaultValue); + ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed"); + is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect changed pref value"); + ok(!CustomizableUI.inDefaultState, "With titlebar flipped, no longer default"); + is(undoResetButton.hidden, true, "Undo reset button should be hidden after pref change"); + + yield gCustomizeMode.reset(); + ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled after reset"); + is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect default value after reset"); + is(Services.prefs.getBoolPref(prefName), defaultValue, "Reset should reset drawInTitlebar"); + ok(CustomizableUI.inDefaultState, "In default state after titlebar reset"); + is(undoResetButton.hidden, false, "Undo reset button should be visible after reset"); + ok(!undoResetButton.disabled, "Undo reset button should be enabled after reset"); + + yield gCustomizeMode.undoReset(); + ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled after undo-reset"); + is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect undo-reset value"); + ok(!CustomizableUI.inDefaultState, "No longer in default state after undo"); + is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value"); + is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked"); + + Services.prefs.clearUserPref(prefName); + ok(CustomizableUI.inDefaultState, "In default state after pref cleared"); + is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test"); +}); + +add_task(function* asyncCleanup() { + yield gCustomizeMode.reset(); + yield endCustomizing(); +}); diff --git a/browser/components/customizableui/test/browser_972267_customizationchange_events.js b/browser/components/customizableui/test/browser_972267_customizationchange_events.js new file mode 100644 index 000000000..b37dbe954 --- /dev/null +++ b/browser/components/customizableui/test/browser_972267_customizationchange_events.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Create a new window, then move the home button to the menu and check both windows have +// customizationchange events fire on the toolbox: +add_task(function*() { + let newWindow = yield openAndLoadWindow(); + let otherToolbox = newWindow.gNavToolbox; + + let handlerCalledCount = 0; + let handler = (ev) => { + handlerCalledCount++; + }; + + let homeButton = document.getElementById("home-button"); + + gNavToolbox.addEventListener("customizationchange", handler); + otherToolbox.addEventListener("customizationchange", handler); + + gCustomizeMode.addToPanel(homeButton); + + is(handlerCalledCount, 2, "Should be called for both windows."); + + // If the test is run in isolation and the panel has never been open, + // the button will be in the palette. Deal with this case: + if (homeButton.parentNode.id == "BrowserToolbarPalette") { + yield PanelUI.ensureReady(); + isnot(homeButton.parentNode.id, "BrowserToolbarPalette", "Home button should now be in panel"); + } + + handlerCalledCount = 0; + gCustomizeMode.addToToolbar(homeButton); + is(handlerCalledCount, 2, "Should be called for both windows."); + + gNavToolbox.removeEventListener("customizationchange", handler); + otherToolbox.removeEventListener("customizationchange", handler); + + yield promiseWindowClosed(newWindow); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_973641_button_addon.js b/browser/components/customizableui/test/browser_973641_button_addon.js new file mode 100755 index 000000000..796bf3d0e --- /dev/null +++ b/browser/components/customizableui/test/browser_973641_button_addon.js @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kButton = "test_button_for_addon"; +var initialLocation = gBrowser.currentURI.spec; + +add_task(function*() { + info("Check addon button functionality"); + + // create mocked addon button on the navigation bar + let widgetSpec = { + id: kButton, + type: 'button', + onClick: function() { + gBrowser.selectedTab = gBrowser.addTab("about:addons"); + } + }; + CustomizableUI.createWidget(widgetSpec); + CustomizableUI.addWidgetToArea(kButton, CustomizableUI.AREA_NAVBAR); + + // check the button's functionality in navigation bar + let addonButton = document.getElementById(kButton); + let navBar = document.getElementById("nav-bar"); + ok(addonButton, "Addon button exists"); + ok(navBar.contains(addonButton), "Addon button is in the navbar"); + yield checkButtonFunctionality(addonButton); + + resetTabs(); + + // move the add-on button in the Panel Menu + CustomizableUI.addWidgetToArea(kButton, CustomizableUI.AREA_PANEL); + ok(!navBar.contains(addonButton), "Addon button was removed from the browser bar"); + + // check the addon button's functionality in the Panel Menu + yield PanelUI.show(); + var panelMenu = document.getElementById("PanelUI-mainView"); + let addonButtonInPanel = panelMenu.getElementsByAttribute("id", kButton); + ok(panelMenu.contains(addonButton), "Addon button was added to the Panel Menu"); + yield checkButtonFunctionality(addonButtonInPanel[0]); +}); + +add_task(function* asyncCleanup() { + resetTabs(); + + // reset the UI to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The UI is in default state again."); + + // destroy the widget + CustomizableUI.destroyWidget(kButton); +}); + +function resetTabs() { + // close all opened tabs + while (gBrowser.tabs.length > 1) { + gBrowser.removeTab(gBrowser.selectedTab); + } + + // restore the initial tab + gBrowser.addTab(initialLocation); + gBrowser.removeTab(gBrowser.selectedTab); +} + +function* checkButtonFunctionality(aButton) { + aButton.click(); + yield waitForCondition(() => gBrowser.currentURI && + gBrowser.currentURI.spec == "about:addons"); +} diff --git a/browser/components/customizableui/test/browser_973932_addonbar_currentset.js b/browser/components/customizableui/test/browser_973932_addonbar_currentset.js new file mode 100644 index 000000000..66fa6ef47 --- /dev/null +++ b/browser/components/customizableui/test/browser_973932_addonbar_currentset.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var addonbarID = CustomizableUI.AREA_ADDONBAR; +var addonbar = document.getElementById(addonbarID); + +// Check that currentset is correctly updated after a reset: +add_task(function*() { + let placements = CustomizableUI.getWidgetIdsInArea(addonbarID); + is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match default placements"); + ok(CustomizableUI.inDefaultState, "Should be in default state"); + info("Adding a spring to add-on bar shim"); + CustomizableUI.addWidgetToArea("spring", addonbarID, 1); + ok(addonbar.getElementsByTagName("toolbarspring").length, "There should be a spring in the toolbar"); + ok(!CustomizableUI.inDefaultState, "Should no longer be in default state"); + placements = CustomizableUI.getWidgetIdsInArea(addonbarID); + is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match placements after spring addition"); + + yield startCustomizing(); + yield gCustomizeMode.reset(); + ok(CustomizableUI.inDefaultState, "Should be in default state after reset"); + placements = CustomizableUI.getWidgetIdsInArea(addonbarID); + is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match default placements after reset"); + ok(!addonbar.getElementsByTagName("toolbarspring").length, "There should be no spring in the toolbar"); + yield endCustomizing(); +}); + diff --git a/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js new file mode 100644 index 000000000..73fc7c1ff --- /dev/null +++ b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js @@ -0,0 +1,145 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +const kXULWidgetId = "a-test-button"; // we'll create a button with this ID. + +add_task(function setup() { + // create a XUL button and add it to the palette. + createDummyXULButton(kXULWidgetId, "test-button"); +}); + +add_task(function* customizeToolbarAndKeepIt() { + ok(gNavToolbox.toolbarset, "There should be a toolbarset"); + let toolbarID = "testAustralisCustomToolbar"; + gNavToolbox.appendCustomToolbar(toolbarID, ""); + let toolbarDOMID = getToolboxCustomToolbarId(toolbarID); + let toolbarElement = document.getElementById(toolbarDOMID); + ok(toolbarElement, "There should be a toolbar"); + if (!toolbarElement) { + ok(false, "No toolbar created, bailing out of the test."); + return; + } + is(toolbarElement.nextSibling, gNavToolbox.toolbarset, + "Toolbar should have been inserted in toolbox, before toolbarset element"); + let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); + is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR, + "CustomizableUI should know the area and think it's a toolbar"); + if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) { + ok(false, "Toolbar not registered successfully, bailing out of the test."); + toolbarElement.remove(); + return; + } + ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet."); + CustomizableUI.addWidgetToArea("open-file-button", toolbarDOMID, 0); + ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button."); + assertAreaPlacements(toolbarDOMID, ["open-file-button"]); + + gNavToolbox.toolbarset.setAttribute("toolbar1", toolbarID + ":open-file-button"); + document.persist(gNavToolbox.toolbarset.id, "toolbar1"); + + yield startCustomizing(); + // First, exit customize mode without doing anything, and verify the toolbar doesn't get removed. + yield endCustomizing(); + ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state, the toolbar should still be there."); + cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); + is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR, + "CustomizableUI should still know the area and think it's a toolbar"); + ok(toolbarElement.parentNode, "Toolbar should still be in the DOM."); + ok(toolbarElement.hasChildNodes(), "Toolbar should still have items in it."); + assertAreaPlacements(toolbarDOMID, ["open-file-button"]); + + let newWindow = yield openAndLoadWindow({}, true); + is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar1"), + gNavToolbox.toolbarset.getAttribute("toolbar1"), + "Attribute should be the same in new window"); + yield promiseWindowClosed(newWindow); + + // Then customize again, and this time empty out the toolbar and verify it *does* get removed. + yield startCustomizing(); + let openFileButton = document.getElementById("open-file-button"); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(openFileButton, palette); + ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's still a non-collapsed toolbar."); + ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes."); + + toolbarElement.collapsed = true; + ok(CustomizableUI.inDefaultState, "Should be in default state because there's now just a collapsed toolbar."); + toolbarElement.collapsed = false; + ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's a non-collapsed toolbar again."); + yield endCustomizing(); + ok(CustomizableUI.inDefaultState, "Should be in default state because the toolbar should have been removed."); + + newWindow = yield openAndLoadWindow({}, true); + ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar1"), + "Attribute should be gone in new window"); + yield promiseWindowClosed(newWindow); + + ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM."); + cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); + is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area"); +}); + +add_task(function* resetShouldDealWithCustomToolbars() { + ok(gNavToolbox.toolbarset, "There should be a toolbarset"); + let toolbarID = "testAustralisCustomToolbar"; + gNavToolbox.appendCustomToolbar(toolbarID, ""); + let toolbarDOMID = getToolboxCustomToolbarId(toolbarID); + let toolbarElement = document.getElementById(toolbarDOMID); + ok(toolbarElement, "There should be a toolbar"); + if (!toolbarElement) { + ok(false, "No toolbar created, bailing out of the test."); + return; + } + is(toolbarElement.nextSibling, gNavToolbox.toolbarset, + "Toolbar should have been inserted in toolbox, before toolbarset element"); + let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); + is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR, + "CustomizableUI should know the area and think it's a toolbar"); + if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) { + ok(false, "Toolbar not registered successfully, bailing out of the test."); + toolbarElement.remove(); + return; + } + ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet."); + CustomizableUI.addWidgetToArea(kXULWidgetId, toolbarDOMID, 0); + ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button."); + assertAreaPlacements(toolbarDOMID, [kXULWidgetId]); + + gNavToolbox.toolbarset.setAttribute("toolbar2", `${toolbarID}:${kXULWidgetId}`); + document.persist(gNavToolbox.toolbarset.id, "toolbar2"); + + let newWindow = yield openAndLoadWindow({}, true); + is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar2"), + gNavToolbox.toolbarset.getAttribute("toolbar2"), + "Attribute should be the same in new window"); + yield promiseWindowClosed(newWindow); + + CustomizableUI.reset(); + + newWindow = yield openAndLoadWindow({}, true); + ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar2"), + "Attribute should be gone in new window"); + yield promiseWindowClosed(newWindow); + + ok(CustomizableUI.inDefaultState, "Should be in default state after reset."); + let xulButton = document.getElementById(kXULWidgetId); + ok(!xulButton, "XUL button shouldn't be in the document anymore."); + ok(gNavToolbox.palette.querySelector(`#${kXULWidgetId}`), "XUL button should be in the palette"); + ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes."); + ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM."); + cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); + is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area"); +}); + + +add_task(function*() { + let newWin = yield openAndLoadWindow({}, true); + ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar1"), "New window shouldn't have attribute toolbar1"); + ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar2"), "New window shouldn't have attribute toolbar2"); + yield promiseWindowClosed(newWin); +}); diff --git a/browser/components/customizableui/test/browser_976792_insertNodeInWindow.js b/browser/components/customizableui/test/browser_976792_insertNodeInWindow.js new file mode 100644 index 000000000..3bfa8c25d --- /dev/null +++ b/browser/components/customizableui/test/browser_976792_insertNodeInWindow.js @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarName = "test-insertNodeInWindow-placements-toolbar"; +const kTestWidgetPrefix = "test-widget-for-insertNodeInWindow-placements-"; + + +/* +Tries to replicate the situation of having a placement list like this: + +exists-1,trying-to-insert-this,doesn't-exist,exists-2 +*/ +add_task(function*() { + let testWidgetExists = [true, false, false, true]; + let widgetIds = []; + for (let i = 0; i < testWidgetExists.length; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + if (testWidgetExists[i]) { + let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + } + } + + let toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds); + assertAreaPlacements(kToolbarName, widgetIds); + + let btnId = kTestWidgetPrefix + 1; + let btn = createDummyXULButton(btnId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(btnId, window); + + is(btn.parentNode.id, kToolbarName, "New XUL widget should be placed inside new toolbar"); + + is(btn.previousSibling.id, toolbarNode.firstChild.id, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + btn.remove(); + removeCustomToolbars(); + yield resetCustomization(); +}); + + +/* +Tests nodes get placed inside the toolbar's overflow as expected. Replicates a +situation similar to: + +exists-1,exists-2,overflow-1,trying-to-insert-this,overflow-2 +*/ +add_task(function*() { + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + + let widgetIds = []; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + CustomizableUI.addWidgetToArea(id, "nav-bar"); + } + + for (let id of widgetIds) { + document.getElementById(id).style.minWidth = "200px"; + } + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + + let testWidgetId = kTestWidgetPrefix + 3; + + CustomizableUI.destroyWidget(testWidgetId); + + let btn = createDummyXULButton(testWidgetId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window); + + is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar"); + is(btn.previousSibling.id, kTestWidgetPrefix + 2, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + is(btn.nextSibling.id, kTestWidgetPrefix + 4, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName); + btn.remove(); + yield resetCustomization(); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); +}); + + +/* +Tests nodes get placed inside the toolbar's overflow as expected. Replicates a +placements situation similar to: + +exists-1,exists-2,overflow-1,doesn't-exist,trying-to-insert-this,overflow-2 +*/ +add_task(function*() { + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + + let widgetIds = []; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + CustomizableUI.addWidgetToArea(id, "nav-bar"); + } + + for (let id of widgetIds) { + document.getElementById(id).style.minWidth = "200px"; + } + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + + let testWidgetId = kTestWidgetPrefix + 3; + + CustomizableUI.destroyWidget(kTestWidgetPrefix + 2); + CustomizableUI.destroyWidget(testWidgetId); + + let btn = createDummyXULButton(testWidgetId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window); + + is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar"); + is(btn.previousSibling.id, kTestWidgetPrefix + 1, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + is(btn.nextSibling.id, kTestWidgetPrefix + 4, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName); + btn.remove(); + yield resetCustomization(); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); +}); + + +/* +Tests nodes get placed inside the toolbar's overflow as expected. Replicates a +placements situation similar to: + +exists-1,exists-2,overflow-1,doesn't-exist,trying-to-insert-this,doesn't-exist +*/ +add_task(function*() { + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + + let widgetIds = []; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + CustomizableUI.addWidgetToArea(id, "nav-bar"); + } + + for (let id of widgetIds) { + document.getElementById(id).style.minWidth = "200px"; + } + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + + let testWidgetId = kTestWidgetPrefix + 3; + + CustomizableUI.destroyWidget(kTestWidgetPrefix + 2); + CustomizableUI.destroyWidget(testWidgetId); + CustomizableUI.destroyWidget(kTestWidgetPrefix + 4); + + let btn = createDummyXULButton(testWidgetId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window); + + is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar"); + is(btn.previousSibling.id, kTestWidgetPrefix + 1, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + is(btn.nextSibling, null, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName); + btn.remove(); + yield resetCustomization(); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); +}); + + +/* +Tests nodes get placed inside the toolbar's overflow as expected. Replicates a +placements situation similar to: + +exists-1,exists-2,overflow-1,can't-overflow,trying-to-insert-this,overflow-2 +*/ +add_task(function*() { + let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR); + + let widgetIds = []; + for (let i = 5; i >= 0; i--) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + CustomizableUI.addWidgetToArea(id, "nav-bar", 0); + } + + for (let i = 10; i < 15; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + CustomizableUI.addWidgetToArea(id, "nav-bar"); + } + + for (let id of widgetIds) { + document.getElementById(id).style.minWidth = "200px"; + } + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => navbar.hasAttribute("overflowing")); + + // Find last widget that doesn't allow overflowing + let nonOverflowing = navbar.customizationTarget.lastChild; + is(nonOverflowing.getAttribute("overflows"), "false", "Last child is expected to not allow overflowing"); + isnot(nonOverflowing.getAttribute("skipintoolbarset"), "true", "Last child is expected to not be skipintoolbarset"); + + let testWidgetId = kTestWidgetPrefix + 10; + CustomizableUI.destroyWidget(testWidgetId); + + let btn = createDummyXULButton(testWidgetId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window); + + is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar"); + is(btn.nextSibling.id, kTestWidgetPrefix + 11, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName); + btn.remove(); + yield resetCustomization(); + yield waitForCondition(() => !navbar.hasAttribute("overflowing")); +}); + + +/* +Tests nodes get placed inside the toolbar's overflow as expected. Replicates a +placements situation similar to: + +exists-1,exists-2,overflow-1,trying-to-insert-this,can't-overflow,overflow-2 +*/ +add_task(function*() { + let widgetIds = []; + let missingId = 2; + let nonOverflowableId = 3; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + if (i != missingId) { + // Setting min-width to make the overflow state not depend on styling of the button and/or + // screen width + let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i, + onCreated: function(node) { + node.style.minWidth = "200px"; + if (id == (kTestWidgetPrefix + nonOverflowableId)) { + node.setAttribute("overflows", false); + } + }}; + info("Creating: " + id); + CustomizableUI.createWidget(spec); + } + } + + let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds); + assertAreaPlacements(kToolbarName, widgetIds); + ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with."); + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => toolbarNode.hasAttribute("overflowing")); + ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + let btnId = kTestWidgetPrefix + missingId; + let btn = createDummyXULButton(btnId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(btnId, window); + + is(btn.parentNode.id, kToolbarName + "-overflow-list", "New XUL widget should be placed inside new toolbar's overflow"); + is(btn.previousSibling.id, kTestWidgetPrefix + 1, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + is(btn.nextSibling.id, kTestWidgetPrefix + 4, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing")); + + btn.remove(); + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + removeCustomToolbars(); + yield resetCustomization(); +}); + + +/* +Tests nodes do *not* get placed in the toolbar's overflow. Replicates a +plcements situation similar to: + +exists-1,trying-to-insert-this,exists-2,overflowed-1 +*/ +add_task(function*() { + let widgetIds = []; + let missingId = 1; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + if (i != missingId) { + // Setting min-width to make the overflow state not depend on styling of the button and/or + // screen width + let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i, + onCreated: function(node) { node.style.minWidth = "100px"; }}; + info("Creating: " + id); + CustomizableUI.createWidget(spec); + } + } + + let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds); + assertAreaPlacements(kToolbarName, widgetIds); + ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with."); + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => toolbarNode.hasAttribute("overflowing")); + ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + let btnId = kTestWidgetPrefix + missingId; + let btn = createDummyXULButton(btnId, "test"); + CustomizableUI.ensureWidgetPlacedInWindow(btnId, window); + + is(btn.parentNode.id, kToolbarName + "-target", "New XUL widget should be placed inside new toolbar"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing")); + + btn.remove(); + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + removeCustomToolbars(); + yield resetCustomization(); +}); + + +/* +Tests inserting a node onto the end of an overflowing toolbar *doesn't* put it in +the overflow list when the widget disallows overflowing. ie: + +exists-1,exists-2,overflows-1,trying-to-insert-this + +Where trying-to-insert-this has overflows=false +*/ +add_task(function*() { + let widgetIds = []; + let missingId = 3; + for (let i = 0; i < 5; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + if (i != missingId) { + // Setting min-width to make the overflow state not depend on styling of the button and/or + // screen width + let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i, + onCreated: function(node) { node.style.minWidth = "200px"; }}; + info("Creating: " + id); + CustomizableUI.createWidget(spec); + } + } + + let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds); + assertAreaPlacements(kToolbarName, widgetIds); + ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with."); + + let originalWindowWidth = window.outerWidth; + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => toolbarNode.hasAttribute("overflowing")); + ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + + let btnId = kTestWidgetPrefix + missingId; + let btn = createDummyXULButton(btnId, "test"); + btn.setAttribute("overflows", false); + CustomizableUI.ensureWidgetPlacedInWindow(btnId, window); + + is(btn.parentNode.id, kToolbarName + "-target", "New XUL widget should be placed inside new toolbar"); + is(btn.nextSibling, null, + "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements"); + + window.resizeTo(originalWindowWidth, window.outerHeight); + yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing")); + + btn.remove(); + widgetIds.forEach(id => CustomizableUI.destroyWidget(id)); + removeCustomToolbars(); + yield resetCustomization(); +}); + + +add_task(function* asyncCleanUp() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js new file mode 100644 index 000000000..a653c2d51 --- /dev/null +++ b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var draggedItem; + +/** + * Check that customizing-movingItem gets removed on a drop when the item is moved. + */ + +// Drop on the palette +add_task(function*() { + draggedItem = document.createElement("toolbarbutton"); + draggedItem.id = "test-dragEnd-after-move1"; + draggedItem.setAttribute("label", "Test"); + draggedItem.setAttribute("removable", "true"); + let navbar = document.getElementById("nav-bar"); + navbar.customizationTarget.appendChild(draggedItem); + yield startCustomizing(); + simulateItemDrag(draggedItem, gCustomizeMode.visiblePalette); + is(document.documentElement.hasAttribute("customizing-movingItem"), false, + "Make sure customizing-movingItem is removed after dragging to the palette"); + yield endCustomizing(); +}); + +// Drop on a customization target itself +add_task(function*() { + draggedItem = document.createElement("toolbarbutton"); + draggedItem.id = "test-dragEnd-after-move2"; + draggedItem.setAttribute("label", "Test"); + draggedItem.setAttribute("removable", "true"); + let dest = createToolbarWithPlacements("test-dragEnd"); + let navbar = document.getElementById("nav-bar"); + navbar.customizationTarget.appendChild(draggedItem); + yield startCustomizing(); + simulateItemDrag(draggedItem, dest.customizationTarget); + is(document.documentElement.hasAttribute("customizing-movingItem"), false, + "Make sure customizing-movingItem is removed"); + yield endCustomizing(); +}); + +add_task(function* asyncCleanup() { + yield endCustomizing(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js b/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js new file mode 100644 index 000000000..15197ac86 --- /dev/null +++ b/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarName = "test-new-overflowable-toolbar"; +const kTestWidgetPrefix = "test-widget-for-overflowable-toolbar-"; + +add_task(function* addOverflowingToolbar() { + let originalWindowWidth = window.outerWidth; + + let widgetIds = []; + for (let i = 0; i < 10; i++) { + let id = kTestWidgetPrefix + i; + widgetIds.push(id); + let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i}; + CustomizableUI.createWidget(spec); + } + + let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds); + assertAreaPlacements(kToolbarName, widgetIds); + + for (let id of widgetIds) { + document.getElementById(id).style.minWidth = "200px"; + } + + isnot(toolbarNode.overflowable, null, "Toolbar should have overflowable controller"); + isnot(toolbarNode.customizationTarget, null, "Toolbar should have customization target"); + isnot(toolbarNode.customizationTarget, toolbarNode, "Customization target should not be toolbar node"); + + let oldChildCount = toolbarNode.customizationTarget.childElementCount; + let overflowableList = document.getElementById(kToolbarName + "-overflow-list"); + let oldOverflowCount = overflowableList.childElementCount; + + isnot(oldChildCount, 0, "Toolbar should have non-overflowing widgets"); + + window.resizeTo(400, window.outerHeight); + yield waitForCondition(() => toolbarNode.hasAttribute("overflowing")); + ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar."); + ok(toolbarNode.customizationTarget.childElementCount < oldChildCount, "Should have fewer children."); + ok(overflowableList.childElementCount > oldOverflowCount, "Should have more overflowed widgets."); + + window.resizeTo(originalWindowWidth, window.outerHeight); +}); + + +add_task(function* asyncCleanup() { + removeCustomToolbars(); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_981305_separator_insertion.js b/browser/components/customizableui/test/browser_981305_separator_insertion.js new file mode 100644 index 000000000..8d4d86c2a --- /dev/null +++ b/browser/components/customizableui/test/browser_981305_separator_insertion.js @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var tempElements = []; + +function insertTempItemsIntoMenu(parentMenu) { + // Last element is null to insert at the end: + let beforeEls = [parentMenu.firstChild, parentMenu.lastChild, null]; + for (let i = 0; i < beforeEls.length; i++) { + let sep = document.createElement("menuseparator"); + tempElements.push(sep); + parentMenu.insertBefore(sep, beforeEls[i]); + let menu = document.createElement("menu"); + tempElements.push(menu); + parentMenu.insertBefore(menu, beforeEls[i]); + // And another separator for good measure: + sep = document.createElement("menuseparator"); + tempElements.push(sep); + parentMenu.insertBefore(sep, beforeEls[i]); + } +} + +function checkSeparatorInsertion(menuId, buttonId, subviewId) { + return function*() { + info("Checking for duplicate separators in " + buttonId + " widget"); + let menu = document.getElementById(menuId); + insertTempItemsIntoMenu(menu); + + let placement = CustomizableUI.getPlacementOfWidget(buttonId); + let changedPlacement = false; + if (!placement || placement.area != CustomizableUI.AREA_PANEL) { + CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_PANEL); + changedPlacement = true; + } + yield PanelUI.show(); + + let button = document.getElementById(buttonId); + button.click(); + + yield waitForCondition(() => !PanelUI.multiView.hasAttribute("transitioning")); + let subview = document.getElementById(subviewId); + ok(subview.firstChild, "Subview should have a kid"); + is(subview.firstChild.localName, "toolbarbutton", "There should be no separators to start with"); + + for (let kid of subview.children) { + if (kid.localName == "menuseparator") { + ok(kid.previousSibling && kid.previousSibling.localName != "menuseparator", + "Separators should never have another separator next to them, and should never be the first node."); + } + } + + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + + if (changedPlacement) { + CustomizableUI.reset(); + } + }; +} + +add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems")); +add_task(checkSeparatorInsertion("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems")); + +registerCleanupFunction(function() { + for (let el of tempElements) { + el.remove(); + } + tempElements = null; +}); diff --git a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js new file mode 100644 index 000000000..9a7227a47 --- /dev/null +++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +const kWidgetId = 'test-981418-widget-onbeforecreated'; + +// Should be able to add broken view widget +add_task(function* testAddOnBeforeCreatedWidget() { + let viewShownDeferred = Promise.defer(); + let onBeforeCreatedCalled = false; + let widgetSpec = { + id: kWidgetId, + type: 'view', + viewId: kWidgetId + 'idontexistyet', + onBeforeCreated: function(doc) { + let view = doc.createElement("panelview"); + view.id = kWidgetId + 'idontexistyet'; + let label = doc.createElement("label"); + label.setAttribute("value", "Hello world"); + label.className = 'panel-subview-header'; + view.appendChild(label); + document.getElementById("PanelUI-multiView").appendChild(view); + onBeforeCreatedCalled = true; + }, + onViewShowing: function() { + viewShownDeferred.resolve(); + } + }; + + let noError = true; + try { + CustomizableUI.createWidget(widgetSpec); + CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Should not throw an exception trying to add the widget."); + ok(onBeforeCreatedCalled, "onBeforeCreated should have been called"); + + let widgetNode = document.getElementById(kWidgetId); + ok(widgetNode, "Widget should exist"); + if (widgetNode) { + try { + widgetNode.click(); + + let tempPanel = document.getElementById("customizationui-widget-panel"); + let panelShownPromise = promisePanelElementShown(window, tempPanel); + + let shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000); + yield viewShownDeferred.promise; + yield panelShownPromise; + clearTimeout(shownTimeout); + ok(true, "Found view shown"); + + let panelHiddenPromise = promisePanelElementHidden(window, tempPanel); + tempPanel.hidePopup(); + yield panelHiddenPromise; + + CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL); + yield PanelUI.show(); + + viewShownDeferred = Promise.defer(); + widgetNode.click(); + + shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000); + yield viewShownDeferred.promise; + clearTimeout(shownTimeout); + ok(true, "Found view shown"); + + let panelHidden = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidden; + } catch (ex) { + ok(false, "Unexpected exception (like a timeout for one of the yields) " + + "when testing view widget."); + } + } + + noError = true; + try { + CustomizableUI.destroyWidget(kWidgetId); + } catch (ex) { + Cu.reportError(ex); + noError = false; + } + ok(noError, "Should not throw an exception trying to remove the broken view widget."); +}); + +add_task(function* asyncCleanup() { + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js b/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js new file mode 100644 index 000000000..e7f8d0cf4 --- /dev/null +++ b/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Restoring default should not place addon widgets back in the toolbar +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Default state to begin"); + + const kWidgetId = "bug982656-add-on-widget-should-not-restore-to-default-area"; + let widgetSpec = { + id: kWidgetId, + defaultArea: CustomizableUI.AREA_NAVBAR + }; + CustomizableUI.createWidget(widgetSpec); + + ok(!CustomizableUI.inDefaultState, "Not in default state after widget added"); + is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar"); + + yield resetCustomization(); + + ok(CustomizableUI.inDefaultState, "Back in default state after reset"); + is(CustomizableUI.getPlacementOfWidget(kWidgetId), null, "Widget now in palette"); + CustomizableUI.destroyWidget(kWidgetId); +}); + + +// resetCustomization shouldn't move 3rd party widgets out of custom toolbars +add_task(function*() { + const kToolbarId = "bug982656-toolbar-with-defaultset"; + const kWidgetId = "bug982656-add-on-widget-should-restore-to-default-area-when-area-is-not-builtin"; + ok(CustomizableUI.inDefaultState, "Everything should be in its default state."); + let toolbar = createToolbarWithPlacements(kToolbarId); + ok(CustomizableUI.areas.indexOf(kToolbarId) != -1, + "Toolbar has been registered."); + is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR, + "Area should be registered as toolbar"); + + let widgetSpec = { + id: kWidgetId, + defaultArea: kToolbarId + }; + CustomizableUI.createWidget(widgetSpec); + + ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible."); + is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget should be in custom toolbar"); + + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "Back in default state after reset"); + is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget still in custom toolbar"); + ok(toolbar.collapsed, "Custom toolbar should be collapsed after reset"); + + toolbar.remove(); + CustomizableUI.destroyWidget(kWidgetId); + CustomizableUI.unregisterArea(kToolbarId); +}); diff --git a/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js new file mode 100644 index 000000000..42b346c10 --- /dev/null +++ b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR); +var gOverflowList = document.getElementById(gNavBar.getAttribute("overflowtarget")); + +const kBookmarksButton = "bookmarks-menu-button"; +const kBookmarksItems = "personal-bookmarks"; +const kOriginalWindowWidth = window.outerWidth; +const kSmallWidth = 400; + +/** + * Helper function that opens the bookmarks menu, and returns a Promise that + * resolves as soon as the menu is ready for interaction. + */ +function bookmarksMenuPanelShown() { + let deferred = Promise.defer(); + let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup"); + let onTransitionEnd = (e) => { + if (e.target == bookmarksMenuPopup) { + bookmarksMenuPopup.removeEventListener("transitionend", onTransitionEnd); + deferred.resolve(); + } + } + bookmarksMenuPopup.addEventListener("transitionend", onTransitionEnd); + return deferred.promise; +} + +/** + * Checks that the placesContext menu is correctly attached to the + * controller of some view. Returns a Promise that resolves as soon + * as the context menu is closed. + * + * @param aItemWithContextMenu the item that we need to synthesize hte + * right click on in order to open the context menu. + */ +function checkPlacesContextMenu(aItemWithContextMenu) { + return Task.spawn(function* () { + let contextMenu = document.getElementById("placesContext"); + let newBookmarkItem = document.getElementById("placesContext_new:bookmark"); + info("Waiting for context menu on " + aItemWithContextMenu.id); + let shownPromise = popupShown(contextMenu); + EventUtils.synthesizeMouseAtCenter(aItemWithContextMenu, + {type: "contextmenu", button: 2}); + yield shownPromise; + + ok(!newBookmarkItem.hasAttribute("disabled"), + "New bookmark item shouldn't be disabled"); + + info("Closing context menu"); + yield closePopup(contextMenu); + }); +} + +/** + * Opens the bookmarks menu panel, and then opens each of the "special" + * submenus in that list. Then it checks that those submenu's context menus + * are properly hooked up to a controller. + */ +function checkSpecialContextMenus() { + return Task.spawn(function* () { + let bookmarksMenuButton = document.getElementById(kBookmarksButton); + let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup"); + + const kSpecialItemIDs = { + "BMB_bookmarksToolbar": "BMB_bookmarksToolbarPopup", + "BMB_unsortedBookmarks": "BMB_unsortedBookmarksPopup", + }; + + // Open the bookmarks menu button context menus and ensure that + // they have the proper views attached. + let shownPromise = bookmarksMenuPanelShown(); + let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton, + "anonid", "dropmarker"); + EventUtils.synthesizeMouseAtCenter(dropmarker, {}); + info("Waiting for bookmarks menu popup to show after clicking dropmarker.") + yield shownPromise; + + for (let menuID in kSpecialItemIDs) { + let menuItem = document.getElementById(menuID); + let menuPopup = document.getElementById(kSpecialItemIDs[menuID]); + info("Waiting to open menu for " + menuID); + let shownPromise = popupShown(menuPopup); + menuPopup.openPopup(menuItem, null, 0, 0, false, false, null); + yield shownPromise; + + yield checkPlacesContextMenu(menuPopup); + info("Closing menu for " + menuID); + yield closePopup(menuPopup); + } + + info("Closing bookmarks menu"); + yield closePopup(bookmarksMenuPopup); + }); +} + +/** + * Closes a focused popup by simulating pressing the Escape key, + * and returns a Promise that resolves as soon as the popup is closed. + * + * @param aPopup the popup node to close. + */ +function closePopup(aPopup) { + let hiddenPromise = popupHidden(aPopup); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + return hiddenPromise; +} + +/** + * Helper function that checks that the context menu of the + * bookmark toolbar items chevron popup is correctly hooked up + * to the controller of a view. + */ +function checkBookmarksItemsChevronContextMenu() { + return Task.spawn(function*() { + let chevronPopup = document.getElementById("PlacesChevronPopup"); + let shownPromise = popupShown(chevronPopup); + let chevron = document.getElementById("PlacesChevron"); + EventUtils.synthesizeMouseAtCenter(chevron, {}); + info("Waiting for bookmark toolbar item chevron popup to show"); + yield shownPromise; + yield waitForCondition(() => { + for (let child of chevronPopup.children) { + if (child.style.visibility != "hidden") + return true; + } + return false; + }); + yield checkPlacesContextMenu(chevronPopup); + info("Waiting for bookmark toolbar item chevron popup to close"); + yield closePopup(chevronPopup); + }); +} + +/** + * Forces the window to a width that causes the nav-bar to overflow + * its contents. Returns a Promise that resolves as soon as the + * overflowable nav-bar is showing its chevron. + */ +function overflowEverything() { + info("Waiting for overflow"); + window.resizeTo(kSmallWidth, window.outerHeight); + return waitForCondition(() => gNavBar.hasAttribute("overflowing")); +} + +/** + * Returns the window to its original size from the start of the test, + * and returns a Promise that resolves when the nav-bar is no longer + * overflowing. + */ +function stopOverflowing() { + info("Waiting until we stop overflowing"); + window.resizeTo(kOriginalWindowWidth, window.outerHeight); + return waitForCondition(() => !gNavBar.hasAttribute("overflowing")); +} + +/** + * Checks that an item with ID aID is overflowing in the nav-bar. + * + * @param aID the ID of the node to check for overflowingness. + */ +function checkOverflowing(aID) { + ok(!gNavBar.querySelector("#" + aID), + "Item with ID " + aID + " should no longer be in the gNavBar"); + let item = gOverflowList.querySelector("#" + aID); + ok(item, "Item with ID " + aID + " should be overflowing"); + is(item.getAttribute("overflowedItem"), "true", + "Item with ID " + aID + " should have overflowedItem attribute"); +} + +/** + * Checks that an item with ID aID is not overflowing in the nav-bar. + * + * @param aID the ID of hte node to check for non-overflowingness. + */ +function checkNotOverflowing(aID) { + ok(!gOverflowList.querySelector("#" + aID), + "Item with ID " + aID + " should no longer be overflowing"); + let item = gNavBar.querySelector("#" + aID); + ok(item, "Item with ID " + aID + " should be in the nav bar"); + ok(!item.hasAttribute("overflowedItem"), + "Item with ID " + aID + " should not have overflowedItem attribute"); +} + +/** + * Test that overflowing the bookmarks menu button doesn't break the + * context menus for the Unsorted and Bookmarks Toolbar menu items. + */ +add_task(function* testOverflowingBookmarksButtonContextMenu() { + ok(!gNavBar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar."); + ok(CustomizableUI.inDefaultState, "Should start in default state."); + + // Open the Unsorted and Bookmarks Toolbar context menus and ensure + // that they have views attached. + yield checkSpecialContextMenus(); + + yield overflowEverything(); + checkOverflowing(kBookmarksButton); + + yield stopOverflowing(); + checkNotOverflowing(kBookmarksButton); + + yield checkSpecialContextMenus(); +}); + +/** + * Test that the bookmarks toolbar items context menu still works if moved + * to the menu from the overflow panel, and then back to the toolbar. + */ +add_task(function* testOverflowingBookmarksItemsContextMenu() { + info("Ensuring panel is ready."); + yield PanelUI.ensureReady(); + + let bookmarksToolbarItems = document.getElementById(kBookmarksItems); + gCustomizeMode.addToToolbar(bookmarksToolbarItems); + yield checkPlacesContextMenu(bookmarksToolbarItems); + + yield overflowEverything(); + checkOverflowing(kBookmarksItems) + + gCustomizeMode.addToPanel(bookmarksToolbarItems); + + yield stopOverflowing(); + + gCustomizeMode.addToToolbar(bookmarksToolbarItems); + yield checkPlacesContextMenu(bookmarksToolbarItems); +}); + +/** + * Test that overflowing the bookmarks toolbar items doesn't cause the + * context menu in the bookmarks toolbar items chevron to stop working. + */ +add_task(function* testOverflowingBookmarksItemsChevronContextMenu() { + // If it's not already there, let's move the bookmarks toolbar items to + // the nav-bar. + let bookmarksToolbarItems = document.getElementById(kBookmarksItems); + gCustomizeMode.addToToolbar(bookmarksToolbarItems); + + // We make the PlacesToolbarItems element be super tiny in order to force + // the bookmarks toolbar items into overflowing and making the chevron + // show itself. + let placesToolbarItems = document.getElementById("PlacesToolbarItems"); + let placesChevron = document.getElementById("PlacesChevron"); + placesToolbarItems.style.maxWidth = "10px"; + info("Waiting for chevron to no longer be collapsed"); + yield waitForCondition(() => !placesChevron.collapsed); + + yield checkBookmarksItemsChevronContextMenu(); + + yield overflowEverything(); + checkOverflowing(kBookmarksItems); + + yield stopOverflowing(); + checkNotOverflowing(kBookmarksItems); + + yield checkBookmarksItemsChevronContextMenu(); + + placesToolbarItems.style.removeProperty("max-width"); +}); + +add_task(function* asyncCleanup() { + window.resizeTo(kOriginalWindowWidth, window.outerHeight); + yield resetCustomization(); +}); diff --git a/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js b/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js new file mode 100644 index 000000000..c341c2158 --- /dev/null +++ b/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function*() { + ok(CustomizableUI.inDefaultState, "Should start in default state."); + this.otherWin = yield openAndLoadWindow({private: true}, true); + yield startCustomizing(this.otherWin); + let resetButton = this.otherWin.document.getElementById("customization-reset-button"); + ok(resetButton.disabled, "Reset button should be disabled"); + + if (typeof CustomizableUI.setToolbarVisibility == "function") { + CustomizableUI.setToolbarVisibility("PersonalToolbar", true); + } else { + setToolbarVisibility(document.getElementById("PersonalToolbar"), true); + } + + let otherPersonalToolbar = this.otherWin.document.getElementById("PersonalToolbar"); + let personalToolbar = document.getElementById("PersonalToolbar"); + ok(!otherPersonalToolbar.collapsed, "Toolbar should be uncollapsed in private window"); + ok(!personalToolbar.collapsed, "Toolbar should be uncollapsed in normal window"); + ok(!resetButton.disabled, "Reset button should be enabled"); + + yield this.otherWin.gCustomizeMode.reset(); + + ok(otherPersonalToolbar.collapsed, "Toolbar should be collapsed in private window"); + ok(personalToolbar.collapsed, "Toolbar should be collapsed in normal window"); + ok(resetButton.disabled, "Reset button should be disabled"); + + yield endCustomizing(this.otherWin); + + yield promiseWindowClosed(this.otherWin); +}); + + +add_task(function* asyncCleanup() { + if (this.otherWin && !this.otherWin.closed) { + yield promiseWindowClosed(this.otherWin); + } + if (!CustomizableUI.inDefaultState) { + CustomizableUI.reset(); + } +}); diff --git a/browser/components/customizableui/test/browser_987177_destroyWidget_xul.js b/browser/components/customizableui/test/browser_987177_destroyWidget_xul.js new file mode 100644 index 000000000..6a4d0aab4 --- /dev/null +++ b/browser/components/customizableui/test/browser_987177_destroyWidget_xul.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const BUTTONID = "test-XUL-wrapper-destroyWidget"; + + +add_task(function() { + let btn = createDummyXULButton(BUTTONID, "XUL btn"); + gNavToolbox.palette.appendChild(btn); + let firstWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window); + ok(firstWrapper, "Should get a wrapper"); + ok(firstWrapper.node, "Node should be there on first wrapper."); + + btn.remove(); + CustomizableUI.destroyWidget(BUTTONID); + let secondWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window); + isnot(firstWrapper, secondWrapper, "Wrappers should be different after destroyWidget call."); + ok(!firstWrapper.node, "No node should be there on old wrapper."); + ok(!secondWrapper.node, "No node should be there on new wrapper."); + + btn = createDummyXULButton(BUTTONID, "XUL btn"); + gNavToolbox.palette.appendChild(btn); + let thirdWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window); + ok(thirdWrapper, "Should get a wrapper"); + is(secondWrapper, thirdWrapper, "Should get the second wrapper again."); + ok(firstWrapper.node, "Node should be there on old wrapper."); + ok(secondWrapper.node, "Node should be there on second wrapper."); + ok(thirdWrapper.node, "Node should be there on third wrapper."); +}); + diff --git a/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js b/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js new file mode 100644 index 000000000..f838e204d --- /dev/null +++ b/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const BUTTONID = "test-XUL-wrapper-widget"; +add_task(function() { + let btn = createDummyXULButton(BUTTONID, "XUL btn"); + gNavToolbox.palette.appendChild(btn); + let groupWrapper = CustomizableUI.getWidget(BUTTONID); + ok(groupWrapper, "Should get a group wrapper"); + let singleWrapper = groupWrapper.forWindow(window); + ok(singleWrapper, "Should get a single wrapper"); + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR); + + let otherSingleWrapper = groupWrapper.forWindow(window); + is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar."); + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + CustomizableUI.removeWidgetFromArea(BUTTONID); + + otherSingleWrapper = groupWrapper.forWindow(window); + isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar."); + singleWrapper = otherSingleWrapper; + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + btn.remove(); + otherSingleWrapper = groupWrapper.forWindow(window); + is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node."); + is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM."); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, null, "That instance should be null."); + + btn = createDummyXULButton(BUTTONID, "XUL btn"); + gNavToolbox.palette.appendChild(btn); + otherSingleWrapper = groupWrapper.forWindow(window); + is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after readding the node."); + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR); + + otherSingleWrapper = groupWrapper.forWindow(window); + is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar."); + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + CustomizableUI.removeWidgetFromArea(BUTTONID); + + otherSingleWrapper = groupWrapper.forWindow(window); + isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar."); + singleWrapper = otherSingleWrapper; + is(singleWrapper.node, btn, "Node should be in the wrapper"); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, btn, "Button should be that instance."); + + btn.remove(); + otherSingleWrapper = groupWrapper.forWindow(window); + is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node."); + is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM."); + is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper"); + is(groupWrapper.instances[0].node, null, "That instance should be null."); +}); diff --git a/browser/components/customizableui/test/browser_987185_syncButton.js b/browser/components/customizableui/test/browser_987185_syncButton.js new file mode 100755 index 000000000..988d738be --- /dev/null +++ b/browser/components/customizableui/test/browser_987185_syncButton.js @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +"use strict"; + +var syncService = {}; +Components.utils.import("resource://services-sync/service.js", syncService); + +var needsSetup; +var originalSync; +var service = syncService.Service; +var syncWasCalled = false; + +add_task(function* testSyncButtonFunctionality() { + info("Check Sync button functionality"); + storeInitialValues(); + mockFunctions(); + + // add the Sync button to the panel + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + + // check the button's functionality + yield PanelUI.show(); + info("The panel menu was opened"); + + let syncButton = document.getElementById("sync-button"); + ok(syncButton, "The Sync button was added to the Panel Menu"); + // click the button - the panel should open. + syncButton.click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + // Find and click the "setup" button. + let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow"); + syncNowButton.click(); + + info("The sync button was clicked"); + + yield waitForCondition(() => syncWasCalled); +}); + +add_task(function* asyncCleanup() { + // reset the panel UI to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The panel UI is in default state again."); + + if (isPanelUIOpen()) { + let panelHidePromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHidePromise; + } + + restoreValues(); +}); + +function mockFunctions() { + // mock needsSetup + gSyncUI._needsSetup = () => Promise.resolve(false); + + // mock service.errorHandler.syncAndReportErrors() + service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors; +} + +function mocked_syncAndReportErrors() { + syncWasCalled = true; +} + +function restoreValues() { + gSyncUI._needsSetup = needsSetup; + service.sync = originalSync; +} + +function storeInitialValues() { + needsSetup = gSyncUI._needsSetup; + originalSync = service.sync; +} diff --git a/browser/components/customizableui/test/browser_987492_window_api.js b/browser/components/customizableui/test/browser_987492_window_api.js new file mode 100644 index 000000000..1718303e1 --- /dev/null +++ b/browser/components/customizableui/test/browser_987492_window_api.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + + +add_task(function* testOneWindow() { + let windows = []; + for (let win of CustomizableUI.windows) + windows.push(win); + is(windows.length, 1, "Should have one customizable window"); +}); + + +add_task(function* testOpenCloseWindow() { + let newWindow = null; + let openListener = { + onWindowOpened: function(window) { + newWindow = window; + } + } + CustomizableUI.addListener(openListener); + let win = yield openAndLoadWindow(null, true); + isnot(newWindow, null, "Should have gotten onWindowOpen event"); + is(newWindow, win, "onWindowOpen event should have received expected window"); + CustomizableUI.removeListener(openListener); + + let windows = []; + for (let win of CustomizableUI.windows) + windows.push(win); + is(windows.length, 2, "Should have two customizable windows"); + isnot(windows.indexOf(window), -1, "Current window should be in window collection."); + isnot(windows.indexOf(newWindow), -1, "New window should be in window collection."); + + let closedWindow = null; + let closeListener = { + onWindowClosed: function(window) { + closedWindow = window; + } + } + CustomizableUI.addListener(closeListener); + yield promiseWindowClosed(newWindow); + isnot(closedWindow, null, "Should have gotten onWindowClosed event") + is(newWindow, closedWindow, "Closed window should match previously opened window"); + CustomizableUI.removeListener(closeListener); + + windows = []; + for (let win of CustomizableUI.windows) + windows.push(win); + is(windows.length, 1, "Should have one customizable window"); + isnot(windows.indexOf(window), -1, "Current window should be in window collection."); + is(windows.indexOf(closedWindow), -1, "Closed window should not be in window collection."); +}); diff --git a/browser/components/customizableui/test/browser_987640_charEncoding.js b/browser/components/customizableui/test/browser_987640_charEncoding.js new file mode 100644 index 000000000..dfe02f940 --- /dev/null +++ b/browser/components/customizableui/test/browser_987640_charEncoding.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html"; + +add_task(function*() { + info("Check Character Encoding panel functionality"); + + // add the Character Encoding button to the panel + CustomizableUI.addWidgetToArea("characterencoding-button", + CustomizableUI.AREA_PANEL); + + let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE, true, true); + + yield PanelUI.show(); + let charEncodingButton = document.getElementById("characterencoding-button"); + let characterEncodingView = document.getElementById("PanelUI-characterEncodingView"); + let subviewShownPromise = subviewShown(characterEncodingView); + charEncodingButton.click(); + yield subviewShownPromise; + + let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']"); + let initialEncoding = checkedButtons[0]; + is(initialEncoding.getAttribute("label"), "Unicode", "The unicode encoding is initially selected"); + + // change the encoding + let encodings = characterEncodingView.querySelectorAll("toolbarbutton"); + let newEncoding = encodings[0].hasAttribute("checked") ? encodings[1] : encodings[0]; + let tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE); + newEncoding.click(); + yield tabLoadPromise; + + // check that the new encodng is applied + yield PanelUI.show(); + charEncodingButton.click(); + checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']"); + let selectedEncodingName = checkedButtons[0].getAttribute("label"); + ok(selectedEncodingName != "Unicode", "The encoding was changed to " + selectedEncodingName); + + // reset the initial encoding + yield PanelUI.show(); + charEncodingButton.click(); + tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE); + initialEncoding.click(); + yield tabLoadPromise; + yield PanelUI.show(); + charEncodingButton.click(); + checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']"); + is(checkedButtons[0].getAttribute("label"), "Unicode", "The encoding was reset to Unicode"); + yield BrowserTestUtils.removeTab(newTab); +}); + +add_task(function* asyncCleanup() { + // reset the panel to the default state + yield resetCustomization(); + ok(CustomizableUI.inDefaultState, "The UI is in default state again."); +}); diff --git a/browser/components/customizableui/test/browser_988072_sidebar_events.js b/browser/components/customizableui/test/browser_988072_sidebar_events.js new file mode 100644 index 000000000..6791be67a --- /dev/null +++ b/browser/components/customizableui/test/browser_988072_sidebar_events.js @@ -0,0 +1,392 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var gSidebarMenu = document.getElementById("viewSidebarMenu"); +var gTestSidebarItem = null; + +var EVENTS = { + click: 0, command: 0, + onclick: 0, oncommand: 0 +}; + +window.sawEvent = function(event, isattr) { + let type = (isattr ? "on" : "") + event.type + EVENTS[type]++; +}; + +registerCleanupFunction(() => { + delete window.sawEvent; + + // Ensure sidebar is hidden after each test: + if (!document.getElementById("sidebar-box").hidden) { + SidebarUI.hide(); + } +}); + +function checkExpectedEvents(expected) { + for (let type of Object.keys(EVENTS)) { + let count = (type in expected ? expected[type] : 0); + is(EVENTS[type], count, "Should have seen the right number of " + type + " events"); + EVENTS[type] = 0; + } +} + +function createSidebarItem() { + gTestSidebarItem = document.createElement("menuitem"); + gTestSidebarItem.id = "testsidebar"; + gTestSidebarItem.setAttribute("label", "Test Sidebar"); + gSidebarMenu.insertBefore(gTestSidebarItem, gSidebarMenu.firstChild); +} + +function addWidget() { + CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar"); + PanelUI.disableSingleSubviewPanelAnimations(); +} + +function removeWidget() { + CustomizableUI.removeWidgetFromArea("sidebar-button"); + PanelUI.enableSingleSubviewPanelAnimations(); +} + +// Filters out the trailing menuseparators from the sidebar list +function getSidebarList() { + let sidebars = [...gSidebarMenu.children].filter(sidebar => { + if (sidebar.localName == "menuseparator") + return false; + if (sidebar.getAttribute("hidden") == "true") + return false; + return true; + }); + return sidebars; +} + +function compareElements(original, displayed) { + let attrs = ["label", "key", "disabled", "hidden", "origin", "image", "checked"]; + for (let attr of attrs) { + is(displayed.getAttribute(attr), original.getAttribute(attr), "Should have the same " + attr + " attribute"); + } +} + +function compareList(original, displayed) { + is(displayed.length, original.length, "Should have the same number of children"); + + for (let i = 0; i < Math.min(original.length, displayed.length); i++) { + compareElements(displayed[i], original[i]); + } +} + +var showSidebarPopup = Task.async(function*() { + let button = document.getElementById("sidebar-button"); + let subview = document.getElementById("PanelUI-sidebar"); + + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + + let subviewShownPromise = subviewShown(subview); + EventUtils.synthesizeMouseAtCenter(button, {}); + return Promise.all([subviewShownPromise, popupShownPromise]); +}); + +// Check the sidebar widget shows the default items +add_task(function*() { + addWidget(); + + yield showSidebarPopup(); + + let sidebars = getSidebarList(); + let displayed = [...document.getElementById("PanelUI-sidebarItems").children]; + compareList(sidebars, displayed); + + let subview = document.getElementById("PanelUI-sidebar"); + let subviewHiddenPromise = subviewHidden(subview); + document.getElementById("customizationui-widget-panel").hidePopup(); + yield subviewHiddenPromise; + + removeWidget(); +}); + +function add_sidebar_task(description, setup, teardown) { + add_task(function*() { + info(description); + createSidebarItem(); + addWidget(); + yield setup(); + + CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar"); + + yield showSidebarPopup(); + + let sidebars = getSidebarList(); + let displayed = [...document.getElementById("PanelUI-sidebarItems").children]; + compareList(sidebars, displayed); + + is(displayed[0].label, "Test Sidebar", "Should have the right element at the top"); + let subview = document.getElementById("PanelUI-sidebar"); + let subviewHiddenPromise = subviewHidden(subview); + EventUtils.synthesizeMouseAtCenter(displayed[0], {}); + yield subviewHiddenPromise; + + yield teardown(); + gTestSidebarItem.remove(); + removeWidget(); + }); +} + +add_sidebar_task( + "Check that a sidebar that uses a command event listener works", +function*() { + gTestSidebarItem.addEventListener("command", window.sawEvent); +}, function*() { + checkExpectedEvents({ command: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses a click event listener works", +function*() { + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ click: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses both click and command event listeners works", +function*() { + gTestSidebarItem.addEventListener("command", window.sawEvent); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ command: 1, click: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses an oncommand attribute works", +function*() { + gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)"); +}, function*() { + checkExpectedEvents({ oncommand: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses an onclick attribute works", +function*() { + gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)"); +}, function*() { + checkExpectedEvents({ onclick: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses both onclick and oncommand attributes works", +function*() { + gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)"); + gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)"); +}, function*() { + checkExpectedEvents({ onclick: 1, oncommand: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses an onclick attribute and a command listener works", +function*() { + gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)"); + gTestSidebarItem.addEventListener("command", window.sawEvent); +}, function*() { + checkExpectedEvents({ onclick: 1, command: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses an oncommand attribute and a click listener works", +function*() { + gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)"); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ click: 1, oncommand: 1 }); +}); + +add_sidebar_task( + "A sidebar with both onclick attribute and click listener sees only one event :(", +function*() { + gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)"); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ onclick: 1 }); +}); + +add_sidebar_task( + "A sidebar with both oncommand attribute and command listener sees only one event :(", +function*() { + gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)"); + gTestSidebarItem.addEventListener("command", window.sawEvent); +}, function*() { + checkExpectedEvents({ oncommand: 1 }); +}); + +add_sidebar_task( + "Check that a sidebar that uses a broadcaster with an oncommand attribute works", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); +}, function*() { + checkExpectedEvents({ oncommand: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar that uses a broadcaster with an onclick attribute works", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("onclick", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); +}, function*() { + checkExpectedEvents({ onclick: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar that uses a broadcaster with both onclick and oncommand attributes works", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("onclick", "window.sawEvent(event, true)"); + broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); +}, function*() { + checkExpectedEvents({ onclick: 1, oncommand: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar with a click listener and a broadcaster with an oncommand attribute works", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ click: 1, oncommand: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar with a command listener and a broadcaster with an onclick attribute works", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("onclick", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); + gTestSidebarItem.addEventListener("command", window.sawEvent); +}, function*() { + checkExpectedEvents({ onclick: 1, command: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar with a click listener and a broadcaster with an onclick " + + "attribute only sees one event :(", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("onclick", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ onclick: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar with a command listener and a broadcaster with an oncommand " + + "attribute only sees one event :(", +function*() { + let broadcaster = document.createElement("broadcaster"); + broadcaster.setAttribute("id", "testbroadcaster"); + broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)"); + broadcaster.setAttribute("label", "Test Sidebar"); + document.getElementById("mainBroadcasterSet").appendChild(broadcaster); + + gTestSidebarItem.setAttribute("observes", "testbroadcaster"); + gTestSidebarItem.addEventListener("command", window.sawEvent); +}, function*() { + checkExpectedEvents({ oncommand: 1 }); + document.getElementById("testbroadcaster").remove(); +}); + +add_sidebar_task( + "Check that a sidebar that uses a command element with a command event listener works", +function*() { + let command = document.createElement("command"); + command.setAttribute("id", "testcommand"); + document.getElementById("mainCommandSet").appendChild(command); + command.addEventListener("command", window.sawEvent); + + gTestSidebarItem.setAttribute("command", "testcommand"); +}, function*() { + checkExpectedEvents({ command: 1 }); + document.getElementById("testcommand").remove(); +}); + +add_sidebar_task( + "Check that a sidebar that uses a command element with an oncommand attribute works", +function*() { + let command = document.createElement("command"); + command.setAttribute("id", "testcommand"); + command.setAttribute("oncommand", "window.sawEvent(event, true)"); + document.getElementById("mainCommandSet").appendChild(command); + + gTestSidebarItem.setAttribute("command", "testcommand"); +}, function*() { + checkExpectedEvents({ oncommand: 1 }); + document.getElementById("testcommand").remove(); +}); + +add_sidebar_task("Check that a sidebar that uses a command element with a " + + "command event listener and oncommand attribute works", +function*() { + let command = document.createElement("command"); + command.setAttribute("id", "testcommand"); + command.setAttribute("oncommand", "window.sawEvent(event, true)"); + document.getElementById("mainCommandSet").appendChild(command); + command.addEventListener("command", window.sawEvent); + + gTestSidebarItem.setAttribute("command", "testcommand"); +}, function*() { + checkExpectedEvents({ command: 1, oncommand: 1 }); + document.getElementById("testcommand").remove(); +}); + +add_sidebar_task( + "A sidebar with a command element will still see click events", +function*() { + let command = document.createElement("command"); + command.setAttribute("id", "testcommand"); + command.setAttribute("oncommand", "window.sawEvent(event, true)"); + document.getElementById("mainCommandSet").appendChild(command); + command.addEventListener("command", window.sawEvent); + + gTestSidebarItem.setAttribute("command", "testcommand"); + gTestSidebarItem.addEventListener("click", window.sawEvent); +}, function*() { + checkExpectedEvents({ click: 1, command: 1, oncommand: 1 }); + document.getElementById("testcommand").remove(); +}); diff --git a/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js b/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js new file mode 100644 index 000000000..2a1b01bf7 --- /dev/null +++ b/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const BUTTONID = "test-widget-saved-earlier"; +const AREAID = "test-area-saved-earlier"; + +var hadSavedState; +function test() { + // Hack our way into the module to fake a saved state that isn't there... + let backstagePass = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + hadSavedState = backstagePass.gSavedState != null; + if (!hadSavedState) { + backstagePass.gSavedState = {placements: {}}; + } + backstagePass.gSavedState.placements[AREAID] = [BUTTONID]; + // Put bogus stuff in the saved state for the nav-bar, so as to check the current placements + // override this one... + backstagePass.gSavedState.placements[CustomizableUI.AREA_NAVBAR] = ["bogus-navbar-item"]; + + backstagePass.gDirty = true; + backstagePass.CustomizableUIInternal.saveState(); + + let newSavedState = JSON.parse(Services.prefs.getCharPref("browser.uiCustomization.state")); + let savedArea = Array.isArray(newSavedState.placements[AREAID]); + ok(savedArea, "Should have re-saved the state, even though the area isn't registered"); + + if (savedArea) { + placementArraysEqual(AREAID, newSavedState.placements[AREAID], [BUTTONID]); + } + ok(!backstagePass.gPlacements.has(AREAID), "Placements map shouldn't have been affected"); + + let savedNavbar = Array.isArray(newSavedState.placements[CustomizableUI.AREA_NAVBAR]); + ok(savedNavbar, "Should have saved nav-bar contents"); + if (savedNavbar) { + placementArraysEqual(CustomizableUI.AREA_NAVBAR, newSavedState.placements[CustomizableUI.AREA_NAVBAR], + CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR)); + } +} + +registerCleanupFunction(function() { + let backstagePass = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + if (!hadSavedState) { + backstagePass.gSavedState = null; + } else { + let savedPlacements = backstagePass.gSavedState.placements; + delete savedPlacements[AREAID]; + let realNavBarPlacements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR); + savedPlacements[CustomizableUI.AREA_NAVBAR] = realNavBarPlacements; + } + backstagePass.gDirty = true; + backstagePass.CustomizableUIInternal.saveState(); +}); + diff --git a/browser/components/customizableui/test/browser_989751_subviewbutton_class.js b/browser/components/customizableui/test/browser_989751_subviewbutton_class.js new file mode 100644 index 000000000..0d11324ed --- /dev/null +++ b/browser/components/customizableui/test/browser_989751_subviewbutton_class.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kCustomClass = "acustomclassnoonewilluse"; +var tempElement = null; + +function insertClassNameToMenuChildren(parentMenu) { + let el = parentMenu.querySelector("menuitem:first-of-type"); + el.classList.add(kCustomClass); + tempElement = el; +} + +function checkSubviewButtonClass(menuId, buttonId, subviewId) { + return function*() { + info("Checking for items without the subviewbutton class in " + buttonId + " widget"); + let menu = document.getElementById(menuId); + insertClassNameToMenuChildren(menu); + + let placement = CustomizableUI.getPlacementOfWidget(buttonId); + let changedPlacement = false; + if (!placement || placement.area != CustomizableUI.AREA_PANEL) { + CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_PANEL); + changedPlacement = true; + } + yield PanelUI.show(); + + let button = document.getElementById(buttonId); + button.click(); + + yield waitForCondition(() => !PanelUI.multiView.hasAttribute("transitioning")); + let subview = document.getElementById(subviewId); + ok(subview.firstChild, "Subview should have a kid"); + let subviewchildren = subview.querySelectorAll("toolbarbutton"); + for (let i = 0; i < subviewchildren.length; i++) { + let item = subviewchildren[i]; + let itemReadable = "Item '" + item.label + "' (classes: " + item.className + ")"; + ok(item.classList.contains("subviewbutton"), itemReadable + " should have the subviewbutton class."); + if (i == 0) { + ok(item.classList.contains(kCustomClass), itemReadable + " should still have its own class, too."); + } + } + + let panelHiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield panelHiddenPromise; + + if (changedPlacement) { + CustomizableUI.reset(); + } + }; +} + +add_task(checkSubviewButtonClass("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems")); +add_task(checkSubviewButtonClass("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems")); + +registerCleanupFunction(function() { + tempElement.classList.remove(kCustomClass) + tempElement = null; +}); diff --git a/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js b/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js new file mode 100644 index 000000000..eb0a8c8ee --- /dev/null +++ b/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TOOLBARID = "test-noncustomizable-toolbar-for-toggling"; +function test() { + let tb = document.createElementNS(kNSXUL, "toolbar"); + tb.id = TOOLBARID; + gNavToolbox.appendChild(tb); + try { + CustomizableUI.setToolbarVisibility(TOOLBARID, false); + } catch (ex) { + ok(false, "Should not throw exceptions trying to set toolbar visibility."); + } + is(tb.getAttribute("collapsed"), "true", "Toolbar should be collapsed"); + try { + CustomizableUI.setToolbarVisibility(TOOLBARID, true); + } catch (ex) { + ok(false, "Should not throw exceptions trying to set toolbar visibility."); + } + is(tb.getAttribute("collapsed"), "false", "Toolbar should be uncollapsed"); + tb.remove(); +} + diff --git a/browser/components/customizableui/test/browser_993322_widget_notoolbar.js b/browser/components/customizableui/test/browser_993322_widget_notoolbar.js new file mode 100644 index 000000000..9264eb78a --- /dev/null +++ b/browser/components/customizableui/test/browser_993322_widget_notoolbar.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const BUTTONID = "test-API-created-widget-toolbar-gone"; +const TOOLBARID = "test-API-created-extra-toolbar"; + +add_task(function*() { + let toolbar = createToolbarWithPlacements(TOOLBARID, []); + CustomizableUI.addWidgetToArea(BUTTONID, TOOLBARID); + is(CustomizableUI.getPlacementOfWidget(BUTTONID).area, TOOLBARID, "Should be on toolbar"); + is(toolbar.children.length, 0, "Toolbar has no kid"); + + CustomizableUI.unregisterArea(TOOLBARID); + CustomizableUI.createWidget({id: BUTTONID, label: "Test widget toolbar gone"}); + + let currentWidget = CustomizableUI.getWidget(BUTTONID); + + yield startCustomizing(); + let buttonNode = document.getElementById(BUTTONID); + ok(buttonNode, "Should find button in window"); + if (buttonNode) { + is(buttonNode.parentNode.localName, "toolbarpaletteitem", "Node should be wrapped"); + is(buttonNode.parentNode.getAttribute("place"), "palette", "Node should be in palette"); + is(buttonNode, gNavToolbox.palette.querySelector("#" + BUTTONID), "Node should really be in palette."); + } + is(currentWidget.forWindow(window).node, buttonNode, "Should have the same node for customize mode"); + yield endCustomizing(); + + CustomizableUI.destroyWidget(BUTTONID); + CustomizableUI.unregisterArea(TOOLBARID, true); + toolbar.remove(); + gAddedToolbars.clear(); +}); diff --git a/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js new file mode 100644 index 000000000..4d292a929 --- /dev/null +++ b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js @@ -0,0 +1,149 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TOOLBARID = "test-toolbar-added-during-customize-mode"; + +// The ID of a button that is not placed (ie, is in the palette) by default +const kNonPlacedWidgetId = "open-file-button"; + +add_task(function*() { + yield startCustomizing(); + let toolbar = createToolbarWithPlacements(TOOLBARID, []); + CustomizableUI.addWidgetToArea(kNonPlacedWidgetId, TOOLBARID); + let button = document.getElementById(kNonPlacedWidgetId); + ok(button, "Button should exist."); + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper."); + + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); + + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); + + yield endCustomizing(); + isnot(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should not be a wrapper outside customize mode."); + yield startCustomizing(); + + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper back in customize mode."); + + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); + + ok(!CustomizableUI.inDefaultState, "Not in default state while toolbar is not collapsed yet."); + setToolbarVisibility(toolbar, false); + ok(CustomizableUI.inDefaultState, "In default state while toolbar is collapsed."); + + setToolbarVisibility(toolbar, true); + + info("Check that removing the area registration from within customize mode works"); + CustomizableUI.unregisterArea(TOOLBARID); + ok(CustomizableUI.inDefaultState, "Now that the toolbar is no longer registered, should be in default state."); + ok(!gCustomizeMode.areas.has(toolbar), "Toolbar shouldn't be known to customize mode."); + + CustomizableUI.registerArea(TOOLBARID, {legacy: true, defaultPlacements: []}); + CustomizableUI.registerToolbarNode(toolbar, []); + ok(!CustomizableUI.inDefaultState, "Now that the toolbar is registered again, should no longer be in default state."); + ok(gCustomizeMode.areas.has(toolbar), "Toolbar should be known to customize mode again."); + + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); + + let otherWin = yield openAndLoadWindow({}, true); + let otherTB = otherWin.document.createElementNS(kNSXUL, "toolbar"); + otherTB.id = TOOLBARID; + otherTB.setAttribute("customizable", "true"); + let wasInformedCorrectlyOfAreaAppearing = false; + let listener = { + onAreaNodeRegistered: function(aArea, aNode) { + if (aNode == otherTB) { + wasInformedCorrectlyOfAreaAppearing = true; + } + } + }; + CustomizableUI.addListener(listener); + otherWin.gNavToolbox.appendChild(otherTB); + ok(wasInformedCorrectlyOfAreaAppearing, "Should have been told area was registered."); + CustomizableUI.removeListener(listener); + + ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too."); + + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); + ok(!otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is in palette in other window, too."); + + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); + ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too."); + + let wasInformedCorrectlyOfAreaDisappearing = false; + // XXXgijs So we could be using promiseWindowClosed here. However, after + // repeated random oranges, I'm instead relying on onWindowClosed below to + // fire appropriately - it is linked to an unload event as well, and so + // reusing it prevents a potential race between unload handlers where the + // one from promiseWindowClosed could fire before the onWindowClosed + // (and therefore onAreaNodeRegistered) one, causing the test to fail. + let windowCloseDeferred = Promise.defer(); + listener = { + onAreaNodeUnregistered: function(aArea, aNode, aReason) { + if (aArea == TOOLBARID) { + is(aNode, otherTB, "Should be informed about other toolbar"); + is(aReason, CustomizableUI.REASON_WINDOW_CLOSED, "Reason should be correct."); + wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_WINDOW_CLOSED); + } + }, + onWindowClosed: function(aWindow) { + if (aWindow == otherWin) { + windowCloseDeferred.resolve(aWindow); + } else { + info("Other window was closed!"); + info("Other window title: " + (aWindow.document && aWindow.document.title)); + info("Our window title: " + (otherWin.document && otherWin.document.title)); + } + }, + }; + CustomizableUI.addListener(listener); + otherWin.close(); + let windowClosed = yield windowCloseDeferred.promise; + + is(windowClosed, otherWin, "Window should have sent onWindowClosed notification."); + ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about window closing."); + // Closing the other window should not be counted against this window's customize mode: + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should still be a wrapper."); + ok(gCustomizeMode.areas.has(toolbar), "Toolbar should still be a customizable area for this customize mode instance."); + + yield gCustomizeMode.reset(); + + yield endCustomizing(); + + CustomizableUI.removeListener(listener); + wasInformedCorrectlyOfAreaDisappearing = false; + listener = { + onAreaNodeUnregistered: function(aArea, aNode, aReason) { + if (aArea == TOOLBARID) { + is(aNode, toolbar, "Should be informed about this window's toolbar"); + is(aReason, CustomizableUI.REASON_AREA_UNREGISTERED, "Reason for final removal should be correct."); + wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_AREA_UNREGISTERED); + } + }, + } + CustomizableUI.addListener(listener); + removeCustomToolbars(); + ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about area being unregistered."); + CustomizableUI.removeListener(listener); + ok(CustomizableUI.inDefaultState, "Should be fine after exiting customize mode."); +}); diff --git a/browser/components/customizableui/test/browser_996364_registerArea_different_properties.js b/browser/components/customizableui/test/browser_996364_registerArea_different_properties.js new file mode 100644 index 000000000..b9de5f687 --- /dev/null +++ b/browser/components/customizableui/test/browser_996364_registerArea_different_properties.js @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Calling CustomizableUI.registerArea twice with no +// properties should not throw an exception. +add_task(function() { + try { + CustomizableUI.registerArea("area-996364", {}); + CustomizableUI.registerArea("area-996364", {}); + } catch (ex) { + ok(false, ex.message); + } + + CustomizableUI.unregisterArea("area-996364", true); +}); + +add_task(function() { + let exceptionThrown = false; + try { + CustomizableUI.registerArea("area-996364-2", {type: CustomizableUI.TYPE_TOOLBAR, defaultCollapsed: "false"}); + } catch (ex) { + exceptionThrown = true; + } + ok(exceptionThrown, "defaultCollapsed is not allowed as an external property"); + + // No need to unregister the area because registration fails. +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996364-3", {type: CustomizableUI.TYPE_TOOLBAR}); + CustomizableUI.registerArea("area-996364-3", {type: CustomizableUI.TYPE_MENU_PANEL}); + } catch (ex) { + exceptionThrown = ex; + } + ok(exceptionThrown, "Exception expected, an area cannot change types: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + + CustomizableUI.unregisterArea("area-996364-3", true); +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996364-4", {type: CustomizableUI.TYPE_MENU_PANEL}); + CustomizableUI.registerArea("area-996364-4", {type: CustomizableUI.TYPE_TOOLBAR}); + } catch (ex) { + exceptionThrown = ex; + } + ok(exceptionThrown, "Exception expected, an area cannot change types: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + + CustomizableUI.unregisterArea("area-996364-4", true); +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996899-1", { anchor: "PanelUI-menu-button", + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: [] }); + CustomizableUI.registerArea("area-996899-1", { anchor: "home-button", + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: [] }); + } catch (ex) { + exceptionThrown = ex; + } + ok(!exceptionThrown, "Changing anchors shouldn't throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + CustomizableUI.unregisterArea("area-996899-1", true); +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button", + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: [] }); + CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button", + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: ["feed-button"] }); + } catch (ex) { + exceptionThrown = ex; + } + ok(!exceptionThrown, "Changing defaultPlacements shouldn't throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + CustomizableUI.unregisterArea("area-996899-2", true); +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996899-3", { legacy: true }); + CustomizableUI.registerArea("area-996899-3", { legacy: false }); + } catch (ex) { + exceptionThrown = ex; + } + ok(exceptionThrown, "Changing 'legacy' should throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + CustomizableUI.unregisterArea("area-996899-3", true); +}); + +add_task(function() { + let exceptionThrown; + try { + CustomizableUI.registerArea("area-996899-4", { overflowable: true }); + CustomizableUI.registerArea("area-996899-4", { overflowable: false }); + } catch (ex) { + exceptionThrown = ex; + } + ok(exceptionThrown, "Changing 'overflowable' should throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]")); + CustomizableUI.unregisterArea("area-996899-4", true); +}); diff --git a/browser/components/customizableui/test/browser_996635_remove_non_widgets.js b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js new file mode 100644 index 000000000..14a446eec --- /dev/null +++ b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// NB: This is testing what happens if something that /isn't/ a customizable +// widget gets used in CustomizableUI APIs. Don't use this as an example of +// what should happen in a "normal" case or how you should use the API. +function test() { + // First create a button that isn't customizable, and add it in the nav-bar, + // but not in the customizable part of it (the customization target) but + // next to the main (hamburger) menu button. + const buttonID = "Test-non-widget-non-removable-button"; + let btn = document.createElement("toolbarbutton"); + btn.id = buttonID; + btn.label = "Hi"; + btn.setAttribute("style", "width: 20px; height: 20px; background-color: red"); + document.getElementById("nav-bar").appendChild(btn); + registerCleanupFunction(function() { + btn.remove(); + }); + + // Now try to add this non-customizable button to the tabstrip. This will + // update the internal bookkeeping (ie placements) information, but shouldn't + // move the node. + CustomizableUI.addWidgetToArea(buttonID, CustomizableUI.AREA_TABSTRIP); + let placement = CustomizableUI.getPlacementOfWidget(buttonID); + // Check our bookkeeping + ok(placement, "Button should be placed"); + is(placement && placement.area, CustomizableUI.AREA_TABSTRIP, "Should be placed on tabstrip."); + // Check we didn't move the node. + is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar."); + + // Now remove the node again. This should remove the bookkeeping, but again + // not affect the actual node. + CustomizableUI.removeWidgetFromArea(buttonID); + placement = CustomizableUI.getPlacementOfWidget(buttonID); + // Check our bookkeeping: + ok(!placement, "Button should no longer have a placement."); + // Check our node. + is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar."); +} + diff --git a/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js b/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js new file mode 100644 index 000000000..2c5f0c79c --- /dev/null +++ b/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +requestLongerTimeout(2); + +const kTestBarID = "testBar"; +const kWidgetID = "characterencoding-button"; + +function createTestBar(aLegacy) { + let testBar = document.createElement("toolbar"); + testBar.id = kTestBarID; + testBar.setAttribute("customizable", "true"); + CustomizableUI.registerArea(kTestBarID, { + type: CustomizableUI.TYPE_TOOLBAR, + legacy: aLegacy, + }); + gNavToolbox.appendChild(testBar); + return testBar; +} + +/** + * Helper function that does the following: + * + * 1) Creates a custom toolbar and registers it + * with CustomizableUI. Sets the legacy attribute + * of the object passed to registerArea to aLegacy. + * 2) Adds the widget with ID aWidgetID to that new + * toolbar. + * 3) Enters customize mode and makes sure that the + * widget is still in the right toolbar. + * 4) Exits customize mode, then removes and deregisters + * the custom toolbar. + * 5) Checks that the widget has no placement. + * 6) Re-adds and re-registers a custom toolbar with the same + * ID and options as the first one. + * 7) Enters customize mode and checks that the widget is + * properly back in the toolbar. + * 8) Exits customize mode, removes and de-registers the + * toolbar, and resets the toolbars to default. + */ +function checkRestoredPresence(aWidgetID, aLegacy) { + return Task.spawn(function* () { + let testBar = createTestBar(aLegacy); + CustomizableUI.addWidgetToArea(aWidgetID, kTestBarID); + let placement = CustomizableUI.getPlacementOfWidget(aWidgetID); + is(placement.area, kTestBarID, + "Expected " + aWidgetID + " to be in the test toolbar"); + + CustomizableUI.unregisterArea(testBar.id); + testBar.remove(); + + placement = CustomizableUI.getPlacementOfWidget(aWidgetID); + is(placement, null, "Expected " + aWidgetID + " to be in the palette"); + + testBar = createTestBar(aLegacy); + + yield startCustomizing(); + placement = CustomizableUI.getPlacementOfWidget(aWidgetID); + is(placement.area, kTestBarID, + "Expected " + aWidgetID + " to be in the test toolbar"); + yield endCustomizing(); + + CustomizableUI.unregisterArea(testBar.id); + testBar.remove(); + + yield resetCustomization(); + }); +} + +add_task(function* () { + yield checkRestoredPresence("downloads-button", false); + yield checkRestoredPresence("downloads-button", true); +}); + +add_task(function* () { + yield checkRestoredPresence("characterencoding-button", false); + yield checkRestoredPresence("characterencoding-button", true); +}); diff --git a/browser/components/customizableui/test/browser_check_tooltips_in_navbar.js b/browser/components/customizableui/test/browser_check_tooltips_in_navbar.js new file mode 100644 index 000000000..31dd42ad8 --- /dev/null +++ b/browser/components/customizableui/test/browser_check_tooltips_in_navbar.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function* check_tooltips_in_navbar() { + yield startCustomizing(); + let homeButtonWrapper = document.getElementById("wrapper-home-button"); + let homeButton = document.getElementById("home-button"); + is(homeButtonWrapper.getAttribute("tooltiptext"), homeButton.getAttribute("label"), "the wrapper's tooltip should match the button's label"); + ok(homeButtonWrapper.getAttribute("tooltiptext"), "the button should have tooltip text"); + yield endCustomizing(); +}); diff --git a/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js b/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js new file mode 100644 index 000000000..8e1950291 --- /dev/null +++ b/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js @@ -0,0 +1,24 @@ +"use strict"; + +add_task(function*() { + ok(!PanelUI.menuButton.hasAttribute("open"), "Menu button should not be 'pressed' outside customize mode"); + yield startCustomizing(); + + is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode"); + + let contextMenu = document.getElementById("customizationPanelItemContextMenu"); + let shownPromise = popupShown(contextMenu); + let newWindowButton = document.getElementById("wrapper-new-window-button"); + EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); + yield shownPromise; + is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode after opening a context menu"); + + let hiddenContextPromise = popupHidden(contextMenu); + contextMenu.hidePopup(); + yield hiddenContextPromise; + is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode after hiding a context menu"); + yield endCustomizing(); + + ok(!PanelUI.menuButton.hasAttribute("open"), "Menu button should not be 'pressed' after ending customize mode"); +}); + diff --git a/browser/components/customizableui/test/browser_panel_toggle.js b/browser/components/customizableui/test/browser_panel_toggle.js new file mode 100644 index 000000000..4c286fb85 --- /dev/null +++ b/browser/components/customizableui/test/browser_panel_toggle.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Test opening and closing the menu panel UI. + */ + +// Show and hide the menu panel programmatically without an event (like UITour.jsm would) +add_task(function*() { + let shownPromise = promisePanelShown(window); + PanelUI.show(); + yield shownPromise; + + is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute"); + is(PanelUI.panel.state, "open", "Check that panel state is 'open'"); + + let hiddenPromise = promisePanelHidden(window); + PanelUI.hide(); + yield hiddenPromise; + + ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute"); + is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'"); +}); + +// Toggle the menu panel open and closed +add_task(function*() { + let shownPromise = promisePanelShown(window); + PanelUI.toggle({type: "command"}); + yield shownPromise; + + is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute"); + is(PanelUI.panel.state, "open", "Check that panel state is 'open'"); + + let hiddenPromise = promisePanelHidden(window); + PanelUI.toggle({type: "command"}); + yield hiddenPromise; + + ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute"); + is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'"); +}); diff --git a/browser/components/customizableui/test/browser_switch_to_customize_mode.js b/browser/components/customizableui/test/browser_switch_to_customize_mode.js new file mode 100644 index 000000000..459ea7a1c --- /dev/null +++ b/browser/components/customizableui/test/browser_switch_to_customize_mode.js @@ -0,0 +1,34 @@ +"use strict"; + +add_task(function*() { + yield startCustomizing(); + is(gBrowser.tabs.length, 2, "Should have 2 tabs"); + + let paletteKidCount = document.getElementById("customization-palette").childElementCount; + let nonCustomizingTab = gBrowser.tabContainer.querySelector("tab:not([customizemode=true])"); + let finishedCustomizing = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization"); + yield BrowserTestUtils.switchTab(gBrowser, nonCustomizingTab); + yield finishedCustomizing; + + let startedCount = 0; + let handler = e => startedCount++; + gNavToolbox.addEventListener("customizationstarting", handler); + yield startCustomizing(); + CustomizableUI.removeWidgetFromArea("home-button"); + yield gCustomizeMode.reset().catch(e => { + ok(false, "Threw an exception trying to reset after making modifications in customize mode: " + e); + }); + + let newKidCount = document.getElementById("customization-palette").childElementCount; + is(newKidCount, paletteKidCount, "Should have just as many items in the palette as before."); + yield endCustomizing(); + is(startedCount, 1, "Should have only started once"); + gNavToolbox.removeEventListener("customizationstarting", handler); + let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])"); + for (let toolbar of customizableToolbars) { + ok(!toolbar.hasAttribute("customizing"), "Toolbar " + toolbar.id + " is no longer customizing"); + } + let menuitem = document.getElementById("PanelUI-customize"); + isnot(menuitem.getAttribute("label"), menuitem.getAttribute("exitLabel"), "Should have exited successfully"); +}); + diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js new file mode 100644 index 000000000..7b8d84e20 --- /dev/null +++ b/browser/components/customizableui/test/head.js @@ -0,0 +1,499 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Avoid leaks by using tmp for imports... +var tmp = {}; +Cu.import("resource://gre/modules/Promise.jsm", tmp); +Cu.import("resource:///modules/CustomizableUI.jsm", tmp); +Cu.import("resource://gre/modules/AppConstants.jsm", tmp); +var {Promise, CustomizableUI, AppConstants} = tmp; + +var EventUtils = {}; +Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + +Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true); +registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck")); + +// Remove temporary e10s related new window options in customize ui, +// they break a lot of tests. +CustomizableUI.destroyWidget("e10s-button"); +CustomizableUI.removeWidgetFromArea("e10s-button"); + +var {synthesizeDragStart, synthesizeDrop} = EventUtils; + +const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +const kTabEventFailureTimeoutInMs = 20000; + +function createDummyXULButton(id, label, win = window) { + let btn = document.createElementNS(kNSXUL, "toolbarbutton"); + btn.id = id; + btn.setAttribute("label", label || id); + btn.className = "toolbarbutton-1 chromeclass-toolbar-additional"; + win.gNavToolbox.palette.appendChild(btn); + return btn; +} + +var gAddedToolbars = new Set(); + +function createToolbarWithPlacements(id, placements = []) { + gAddedToolbars.add(id); + let tb = document.createElementNS(kNSXUL, "toolbar"); + tb.id = id; + tb.setAttribute("customizable", "true"); + CustomizableUI.registerArea(id, { + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: placements + }); + gNavToolbox.appendChild(tb); + return tb; +} + +function createOverflowableToolbarWithPlacements(id, placements) { + gAddedToolbars.add(id); + + let tb = document.createElementNS(kNSXUL, "toolbar"); + tb.id = id; + tb.setAttribute("customizationtarget", id + "-target"); + + let customizationtarget = document.createElementNS(kNSXUL, "hbox"); + customizationtarget.id = id + "-target"; + customizationtarget.setAttribute("flex", "1"); + tb.appendChild(customizationtarget); + + let overflowPanel = document.createElementNS(kNSXUL, "panel"); + overflowPanel.id = id + "-overflow"; + document.getElementById("mainPopupSet").appendChild(overflowPanel); + + let overflowList = document.createElementNS(kNSXUL, "vbox"); + overflowList.id = id + "-overflow-list"; + overflowPanel.appendChild(overflowList); + + let chevron = document.createElementNS(kNSXUL, "toolbarbutton"); + chevron.id = id + "-chevron"; + tb.appendChild(chevron); + + CustomizableUI.registerArea(id, { + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: placements, + overflowable: true, + }); + + tb.setAttribute("customizable", "true"); + tb.setAttribute("overflowable", "true"); + tb.setAttribute("overflowpanel", overflowPanel.id); + tb.setAttribute("overflowtarget", overflowList.id); + tb.setAttribute("overflowbutton", chevron.id); + + gNavToolbox.appendChild(tb); + return tb; +} + +function removeCustomToolbars() { + CustomizableUI.reset(); + for (let toolbarId of gAddedToolbars) { + CustomizableUI.unregisterArea(toolbarId, true); + let tb = document.getElementById(toolbarId); + if (tb.hasAttribute("overflowpanel")) { + let panel = document.getElementById(tb.getAttribute("overflowpanel")); + if (panel) + panel.remove(); + } + tb.remove(); + } + gAddedToolbars.clear(); +} + +function getToolboxCustomToolbarId(toolbarName) { + return "__customToolbar_" + toolbarName.replace(" ", "_"); +} + +function resetCustomization() { + return CustomizableUI.reset(); +} + +function isInDevEdition() { + return AppConstants.MOZ_DEV_EDITION; +} + +function removeDeveloperButtonIfDevEdition(areaPanelPlacements) { + if (isInDevEdition()) { + areaPanelPlacements.splice(areaPanelPlacements.indexOf("developer-button"), 1); + } +} + +function assertAreaPlacements(areaId, expectedPlacements) { + let actualPlacements = getAreaWidgetIds(areaId); + placementArraysEqual(areaId, actualPlacements, expectedPlacements); +} + +function placementArraysEqual(areaId, actualPlacements, expectedPlacements) { + is(actualPlacements.length, expectedPlacements.length, + "Area " + areaId + " should have " + expectedPlacements.length + " items."); + let minItems = Math.min(expectedPlacements.length, actualPlacements.length); + for (let i = 0; i < minItems; i++) { + if (typeof expectedPlacements[i] == "string") { + is(actualPlacements[i], expectedPlacements[i], + "Item " + i + " in " + areaId + " should match expectations."); + } else if (expectedPlacements[i] instanceof RegExp) { + ok(expectedPlacements[i].test(actualPlacements[i]), + "Item " + i + " (" + actualPlacements[i] + ") in " + + areaId + " should match " + expectedPlacements[i]); + } else { + ok(false, "Unknown type of expected placement passed to " + + " assertAreaPlacements. Is your test broken?"); + } + } +} + +function todoAssertAreaPlacements(areaId, expectedPlacements) { + let actualPlacements = getAreaWidgetIds(areaId); + let isPassing = actualPlacements.length == expectedPlacements.length; + let minItems = Math.min(expectedPlacements.length, actualPlacements.length); + for (let i = 0; i < minItems; i++) { + if (typeof expectedPlacements[i] == "string") { + isPassing = isPassing && actualPlacements[i] == expectedPlacements[i]; + } else if (expectedPlacements[i] instanceof RegExp) { + isPassing = isPassing && expectedPlacements[i].test(actualPlacements[i]); + } else { + ok(false, "Unknown type of expected placement passed to " + + " assertAreaPlacements. Is your test broken?"); + } + } + todo(isPassing, "The area placements for " + areaId + + " should equal the expected placements."); +} + +function getAreaWidgetIds(areaId) { + return CustomizableUI.getWidgetIdsInArea(areaId); +} + +function simulateItemDrag(aToDrag, aTarget) { + synthesizeDrop(aToDrag.parentNode, aTarget); +} + +function endCustomizing(aWindow=window) { + if (aWindow.document.documentElement.getAttribute("customizing") != "true") { + return true; + } + Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true); + let deferredEndCustomizing = Promise.defer(); + function onCustomizationEnds() { + Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false); + aWindow.gNavToolbox.removeEventListener("aftercustomization", onCustomizationEnds); + deferredEndCustomizing.resolve(); + } + aWindow.gNavToolbox.addEventListener("aftercustomization", onCustomizationEnds); + aWindow.gCustomizeMode.exit(); + + return deferredEndCustomizing.promise; +} + +function startCustomizing(aWindow=window) { + if (aWindow.document.documentElement.getAttribute("customizing") == "true") { + return null; + } + Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true); + let deferred = Promise.defer(); + function onCustomizing() { + aWindow.gNavToolbox.removeEventListener("customizationready", onCustomizing); + Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false); + deferred.resolve(); + } + aWindow.gNavToolbox.addEventListener("customizationready", onCustomizing); + aWindow.gCustomizeMode.enter(); + return deferred.promise; +} + +function promiseObserverNotified(aTopic) { + let deferred = Promise.defer(); + Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) { + Services.obs.removeObserver(onNotification, aTopic); + deferred.resolve({subject: aSubject, data: aData}); + }, aTopic, false); + return deferred.promise; +} + +function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) { + let deferred = Promise.defer(); + let win = OpenBrowserWindow(aOptions); + if (aWaitForDelayedStartup) { + Services.obs.addObserver(function onDS(aSubject, aTopic, aData) { + if (aSubject != win) { + return; + } + Services.obs.removeObserver(onDS, "browser-delayed-startup-finished"); + deferred.resolve(win); + }, "browser-delayed-startup-finished", false); + + } else { + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + deferred.resolve(win); + }); + } + return deferred.promise; +} + +function promiseWindowClosed(win) { + let deferred = Promise.defer(); + win.addEventListener("unload", function onunload() { + win.removeEventListener("unload", onunload); + deferred.resolve(); + }); + win.close(); + return deferred.promise; +} + +function promisePanelShown(win) { + let panelEl = win.PanelUI.panel; + return promisePanelElementShown(win, panelEl); +} + +function promiseOverflowShown(win) { + let panelEl = win.document.getElementById("widget-overflow"); + return promisePanelElementShown(win, panelEl); +} + +function promisePanelElementShown(win, aPanel) { + let deferred = Promise.defer(); + let timeoutId = win.setTimeout(() => { + deferred.reject("Panel did not show within 20 seconds."); + }, 20000); + function onPanelOpen(e) { + aPanel.removeEventListener("popupshown", onPanelOpen); + win.clearTimeout(timeoutId); + deferred.resolve(); + } + aPanel.addEventListener("popupshown", onPanelOpen); + return deferred.promise; +} + +function promisePanelHidden(win) { + let panelEl = win.PanelUI.panel; + return promisePanelElementHidden(win, panelEl); +} + +function promiseOverflowHidden(win) { + let panelEl = document.getElementById("widget-overflow"); + return promisePanelElementHidden(win, panelEl); +} + +function promisePanelElementHidden(win, aPanel) { + let deferred = Promise.defer(); + let timeoutId = win.setTimeout(() => { + deferred.reject("Panel did not hide within 20 seconds."); + }, 20000); + function onPanelClose(e) { + aPanel.removeEventListener("popuphidden", onPanelClose); + win.clearTimeout(timeoutId); + deferred.resolve(); + } + aPanel.addEventListener("popuphidden", onPanelClose); + return deferred.promise; +} + +function isPanelUIOpen() { + return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing"; +} + +function subviewShown(aSubview) { + let deferred = Promise.defer(); + let win = aSubview.ownerGlobal; + let timeoutId = win.setTimeout(() => { + deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds."); + }, 20000); + function onViewShowing(e) { + aSubview.removeEventListener("ViewShowing", onViewShowing); + win.clearTimeout(timeoutId); + deferred.resolve(); + } + aSubview.addEventListener("ViewShowing", onViewShowing); + return deferred.promise; +} + +function subviewHidden(aSubview) { + let deferred = Promise.defer(); + let win = aSubview.ownerGlobal; + let timeoutId = win.setTimeout(() => { + deferred.reject("Subview (" + aSubview.id + ") did not hide within 20 seconds."); + }, 20000); + function onViewHiding(e) { + aSubview.removeEventListener("ViewHiding", onViewHiding); + win.clearTimeout(timeoutId); + deferred.resolve(); + } + aSubview.addEventListener("ViewHiding", onViewHiding); + return deferred.promise; +} + +function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) { + function tryNow() { + tries++; + if (aConditionFn()) { + deferred.resolve(); + } else if (tries < aMaxTries) { + tryAgain(); + } else { + deferred.reject("Condition timed out: " + aConditionFn.toSource()); + } + } + function tryAgain() { + setTimeout(tryNow, aCheckInterval); + } + let deferred = Promise.defer(); + let tries = 0; + tryAgain(); + return deferred.promise; +} + +function waitFor(aTimeout=100) { + let deferred = Promise.defer(); + setTimeout(() => deferred.resolve(), aTimeout); + return deferred.promise; +} + +/** + * Starts a load in an existing tab and waits for it to finish (via some event). + * + * @param aTab The tab to load into. + * @param aUrl The url to load. + * @param aEventType The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + */ +function promiseTabLoadEvent(aTab, aURL) { + let browser = aTab.linkedBrowser; + + BrowserTestUtils.loadURI(browser, aURL); + return BrowserTestUtils.browserLoaded(browser); +} + +/** + * Navigate back or forward in tab history and wait for it to finish. + * + * @param aDirection Number to indicate to move backward or forward in history. + * @param aConditionFn Function that returns the result of an evaluated condition + * that needs to be `true` to resolve the promise. + * @return {Promise} resolved when navigation has finished. + */ +function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) { + let deferred = Promise.defer(); + + let timeoutId = setTimeout(() => { + gBrowser.removeEventListener("pageshow", listener, true); + deferred.reject("Pageshow did not happen within " + kTabEventFailureTimeoutInMs + "ms"); + }, kTabEventFailureTimeoutInMs); + + function listener(event) { + gBrowser.removeEventListener("pageshow", listener, true); + clearTimeout(timeoutId); + + if (aConditionFn) { + waitForCondition(aConditionFn).then(() => deferred.resolve(), + aReason => deferred.reject(aReason)); + } else { + deferred.resolve(); + } + } + gBrowser.addEventListener("pageshow", listener, true); + + content.history.go(aDirection); + + return deferred.promise; +} + +/** + * Wait for an attribute on a node to change + * + * @param aNode Node on which the mutation is expected + * @param aAttribute The attribute we're interested in + * @param aFilterFn A function to check if the new value is what we want. + * @return {Promise} resolved when the requisite mutation shows up. + */ +function promiseAttributeMutation(aNode, aAttribute, aFilterFn) { + return new Promise((resolve, reject) => { + info("waiting for mutation of attribute '" + aAttribute + "'."); + let obs = new MutationObserver((mutations) => { + for (let mut of mutations) { + let attr = mut.attributeName; + let newValue = mut.target.getAttribute(attr); + if (aFilterFn(newValue)) { + ok(true, "mutation occurred: attribute '" + attr + "' changed to '" + newValue + "' from '" + mut.oldValue + "'."); + obs.disconnect(); + resolve(); + } else { + info("Ignoring mutation that produced value " + newValue + " because of filter."); + } + } + }); + obs.observe(aNode, {attributeFilter: [aAttribute]}); + }); +} + +function popupShown(aPopup) { + return promisePopupEvent(aPopup, "shown"); +} + +function popupHidden(aPopup) { + return promisePopupEvent(aPopup, "hidden"); +} + +/** + * Returns a Promise that resolves when aPopup fires an event of type + * aEventType. Times out and rejects after 20 seconds. + * + * @param aPopup the popup to monitor for events. + * @param aEventSuffix the _suffix_ for the popup event type to watch for. + * + * Example usage: + * let popupShownPromise = promisePopupEvent(somePopup, "shown"); + * // ... something that opens a popup + * yield popupShownPromise; + * + * let popupHiddenPromise = promisePopupEvent(somePopup, "hidden"); + * // ... something that hides a popup + * yield popupHiddenPromise; + */ +function promisePopupEvent(aPopup, aEventSuffix) { + let deferred = Promise.defer(); + let eventType = "popup" + aEventSuffix; + + function onPopupEvent(e) { + aPopup.removeEventListener(eventType, onPopupEvent); + deferred.resolve(); + } + + aPopup.addEventListener(eventType, onPopupEvent); + return deferred.promise; +} + +// This is a simpler version of the context menu check that +// exists in contextmenu_common.js. +function checkContextMenu(aContextMenu, aExpectedEntries, aWindow=window) { + let childNodes = [...aContextMenu.childNodes]; + // Ignore hidden nodes: + childNodes = childNodes.filter((n) => !n.hidden); + + for (let i = 0; i < childNodes.length; i++) { + let menuitem = childNodes[i]; + try { + if (aExpectedEntries[i][0] == "---") { + is(menuitem.localName, "menuseparator", "menuseparator expected"); + continue; + } + + let selector = aExpectedEntries[i][0]; + ok(menuitem.matches(selector), "menuitem should match " + selector + " selector"); + let commandValue = menuitem.getAttribute("command"); + let relatedCommand = commandValue ? aWindow.document.getElementById(commandValue) : null; + let menuItemDisabled = relatedCommand ? + relatedCommand.getAttribute("disabled") == "true" : + menuitem.getAttribute("disabled") == "true"; + is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector); + } catch (e) { + ok(false, "Exception when checking context menu: " + e); + } + } +} diff --git a/browser/components/customizableui/test/support/feeds_test_page.html b/browser/components/customizableui/test/support/feeds_test_page.html new file mode 100644 index 000000000..be78e4dff --- /dev/null +++ b/browser/components/customizableui/test/support/feeds_test_page.html @@ -0,0 +1,10 @@ +<html> +<head> + <title>Feeds test page</title> + <link rel="alternate" type="application/rss+xml" href="test-feed.xml" title="Test feed"> +</head> + +<body> + This is a test page for feeds +</body> +</html> diff --git a/browser/components/customizableui/test/support/test-feed.xml b/browser/components/customizableui/test/support/test-feed.xml new file mode 100644 index 000000000..0e700b6d8 --- /dev/null +++ b/browser/components/customizableui/test/support/test-feed.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + + <title>Example Feed</title> + <link href="http://example.org/"/> + <updated>2010-08-22T18:30:02Z</updated> + + <author> + <name>John Doe</name> + </author> + <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id> + + <entry> + + <title>Item</title> + <link href="http://example.org/first"/> + <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id> + <updated>2010-08-22T18:30:02Z</updated> + + <summary>Some text.</summary> + </entry> + +</feed> diff --git a/browser/components/customizableui/test/support/test_967000_charEncoding_page.html b/browser/components/customizableui/test/support/test_967000_charEncoding_page.html new file mode 100644 index 000000000..c8d35115c --- /dev/null +++ b/browser/components/customizableui/test/support/test_967000_charEncoding_page.html @@ -0,0 +1,11 @@ +<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test page</title>
+ </head>
+
+ <body>
+ This is a test page
+ </body>
+</html>
|