diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index 1c938ab549f1..5be8ccfa5640 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -404,7 +404,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { DebuggerController.SourceScripts.togglePrettyPrint(source) .then(resetEditor, printError) - .then(DebuggerView.showEditor); + .then(DebuggerView.showEditor) + .then(this.updateToolbarButtonsState); }, /** diff --git a/browser/devtools/debugger/test/browser.ini b/browser/devtools/debugger/test/browser.ini index 592bbcaec10b..76edfd4c1061 100644 --- a/browser/devtools/debugger/test/browser.ini +++ b/browser/devtools/debugger/test/browser.ini @@ -128,6 +128,7 @@ skip-if = true [browser_dbg_pretty-print-09.js] [browser_dbg_pretty-print-10.js] [browser_dbg_pretty-print-11.js] +[browser_dbg_pretty-print-12.js] [browser_dbg_progress-listener-bug.js] [browser_dbg_reload-preferred-script-01.js] [browser_dbg_reload-preferred-script-02.js] diff --git a/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js b/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js new file mode 100644 index 000000000000..16b9941e7e83 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that we don't leave the pretty print button checked when we fail to + * pretty print a source (because it isn't a JS file, for example). + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; + +let gTab, gDebuggee, gPanel, gDebugger; +let gEditor, gSources; + +function test() { + initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => { + gTab = aTab; + gDebuggee = aDebuggee; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + + waitForSourceShown(gPanel, "") + .then(() => { + let shown = ensureSourceIs(gPanel, TAB_URL, true) + gSources.selectedValue = TAB_URL; + return shown; + }) + .then(clickPrettyPrintButton) + .then(testButtonIsntChecked) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError)); + }); + }); +} + +function clickPrettyPrintButton() { + gDebugger.document.getElementById("pretty-print").click(); +} + +function testButtonIsntChecked() { + is(gDebugger.document.getElementById("pretty-print").checked, false, + "The button shouldn't be checked after trying to pretty print a non-js file."); +} + +registerCleanupFunction(function() { + gTab = null; + gDebuggee = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; +}); diff --git a/browser/metro/base/content/ContextCommands.js b/browser/metro/base/content/ContextCommands.js index 338a75f1c695..04325807a58b 100644 --- a/browser/metro/base/content/ContextCommands.js +++ b/browser/metro/base/content/ContextCommands.js @@ -170,9 +170,8 @@ var ContextCommands = { // Link specific openLinkInNewTab: function cc_openLinkInNewTab() { - let tab = Browser.addTab(ContextMenuUI.popupState.linkURL, false, Browser.selectedTab); - ContextUI.peekTabs(kOpenInNewTabAnimationDelayMsec); - Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab); + let url = ContextMenuUI.popupState.linkURL; + BrowserUI.openLinkInNewTab(url, false, Browser.selectedTab); }, copyLink: function cc_copyLink() { diff --git a/browser/metro/base/content/ContextUI.js b/browser/metro/base/content/ContextUI.js index 338df3969e41..144811a61054 100644 --- a/browser/metro/base/content/ContextUI.js +++ b/browser/metro/base/content/ContextUI.js @@ -161,7 +161,7 @@ var ContextUI = { * Dismiss tab bar after a delay. Fires context ui events. */ dismissTabsWithDelay: function (aDelay) { - aDelay = aDelay || kNewTabAnimationDelayMsec; + aDelay = aDelay || kForegroundTabAnimationDelay; this._clearDelayedTimeout(); this._hidingId = setTimeout(function () { ContextUI.dismissTabs(); diff --git a/browser/metro/base/content/apzc.js b/browser/metro/base/content/apzc.js index 59b26775f22f..42a87d34d735 100644 --- a/browser/metro/base/content/apzc.js +++ b/browser/metro/base/content/apzc.js @@ -39,6 +39,9 @@ var APZCObserver = { handleEvent: function APZC_handleEvent(aEvent) { switch (aEvent.type) { case 'pageshow': + if (aEvent.target != Browser.selectedBrowser.contentDocument) + break; + // fall through to TabSelect: case 'TabSelect': // ROOT_ID doesn't really identify the view we want. When we call // this on a content document (tab), findElementWithViewId will diff --git a/browser/metro/base/content/browser-scripts.js b/browser/metro/base/content/browser-scripts.js index e8e69f2b053e..b3bc91365f1c 100644 --- a/browser/metro/base/content/browser-scripts.js +++ b/browser/metro/base/content/browser-scripts.js @@ -66,6 +66,9 @@ XPCOMUtils.defineLazyServiceGetter(window, "gFaviconService", XPCOMUtils.defineLazyServiceGetter(window, "gFocusManager", "@mozilla.org/focus-manager;1", "nsIFocusManager"); +XPCOMUtils.defineLazyServiceGetter(window, "gEventListenerService", + "@mozilla.org/eventlistenerservice;1", + "nsIEventListenerService"); #ifdef MOZ_CRASHREPORTER XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter", "@mozilla.org/xre/app-info;1", diff --git a/browser/metro/base/content/browser-ui.js b/browser/metro/base/content/browser-ui.js index da09eb7e75fd..dfdb50fe67fa 100644 --- a/browser/metro/base/content/browser-ui.js +++ b/browser/metro/base/content/browser-ui.js @@ -14,12 +14,12 @@ Cu.import("resource://gre/modules/devtools/dbg-server.jsm") const debugServerStateChanged = "devtools.debugger.remote-enabled"; const debugServerPortChanged = "devtools.debugger.remote-port"; -// delay when showing the tab bar briefly after a new (empty) tab opens -const kNewTabAnimationDelayMsec = 1000; -// delay when showing the tab bar after opening a link on a new tab -const kOpenInNewTabAnimationDelayMsec = 3000; -// delay before closing tab bar after selecting another tab -const kSelectTabAnimationDelayMsec = 500; +// delay when showing the tab bar briefly after a new foreground tab opens +const kForegroundTabAnimationDelay = 1000; +// delay when showing the tab bar after opening a new background tab opens +const kBackgroundTabAnimationDelay = 3000; +// delay before closing tab bar after closing or selecting a tab +const kChangeTabAnimationDelay = 500; /** * Cache of commonly used elements. @@ -173,6 +173,14 @@ var BrowserUI = { }, uninit: function() { + messageManager.removeMessageListener("DOMTitleChanged", this); + messageManager.removeMessageListener("DOMWillOpenModalDialog", this); + messageManager.removeMessageListener("DOMWindowClose", this); + + messageManager.removeMessageListener("Browser:OpenURI", this); + messageManager.removeMessageListener("Browser:SaveAs:Return", this); + messageManager.removeMessageListener("Content:StateChange", this); + messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps); Services.obs.removeObserver(this, "handle-xul-text-link"); @@ -180,7 +188,6 @@ var BrowserUI = { FlyoutPanelsUI.uninit(); MetroDownloadsView.uninit(); SettingsCharm.uninit(); - messageManager.removeMessageListener("Content:StateChange", this); PageThumbs.uninit(); this.stopDebugServer(); }, @@ -436,10 +443,25 @@ var BrowserUI = { * See Browser.addTab for more documentation. */ addAndShowTab: function (aURI, aOwner) { - ContextUI.peekTabs(kNewTabAnimationDelayMsec); + ContextUI.peekTabs(kForegroundTabAnimationDelay); return Browser.addTab(aURI || kStartURI, true, aOwner); }, + /** + * Open a new tab in response to clicking a link in an existing tab. + * See Browser.addTab for more documentation. + */ + openLinkInNewTab: function (aURI, aBringFront, aOwner) { + ContextUI.peekTabs(aBringFront ? kForegroundTabAnimationDelay + : kBackgroundTabAnimationDelay); + let tab = Browser.addTab(aURI, aBringFront, aOwner, { + referrerURI: aOwner.browser.documentURI, + charset: aOwner.browser.characterSet, + }); + Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab); + return tab; + }, + setOnTabAnimationEnd: function setOnTabAnimationEnd(aCallback) { Elements.tabs.addEventListener("animationend", function onAnimationEnd() { Elements.tabs.removeEventListener("animationend", onAnimationEnd); @@ -464,7 +486,7 @@ var BrowserUI = { this.setOnTabAnimationEnd(function() { Browser.closeTab(tabToClose, { forceClose: true } ); if (wasCollapsed) - ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec); + ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay); }); }, @@ -506,7 +528,7 @@ var BrowserUI = { selectTabAndDismiss: function selectTabAndDismiss(aTab) { this.selectTab(aTab); - ContextUI.dismissTabsWithDelay(kSelectTabAnimationDelayMsec); + ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay); }, selectTabAtIndex: function selectTabAtIndex(aIndex) { diff --git a/browser/metro/base/content/browser.js b/browser/metro/base/content/browser.js index 38446501ff0b..3e4b0e7982b4 100644 --- a/browser/metro/base/content/browser.js +++ b/browser/metro/base/content/browser.js @@ -79,6 +79,7 @@ var Browser = { // Call InputSourceHelper first so global listeners get called before // we start processing input in TouchModule. InputSourceHelper.init(); + ClickEventHandler.init(); TouchModule.init(); GestureModule.init(); @@ -213,6 +214,7 @@ var Browser = { shutdown: function shutdown() { APZCObserver.shutdown(); BrowserUI.uninit(); + ClickEventHandler.uninit(); ContentAreaObserver.shutdown(); Appbar.shutdown(); @@ -485,7 +487,7 @@ var Browser = { if (aBringFront) this.selectedTab = newTab; - this._announceNewTab(newTab, params, aBringFront); + this._announceNewTab(newTab); return newTab; }, @@ -511,7 +513,7 @@ var Browser = { * helper for addTab related methods. Fires events related to * new tab creation. */ - _announceNewTab: function _announceNewTab(aTab, aParams, aBringFront) { + _announceNewTab: function (aTab) { let event = document.createEvent("UIEvents"); event.initUIEvent("TabOpen", true, false, window, 0); aTab.chromeTab.dispatchEvent(event); @@ -1074,12 +1076,7 @@ nsBrowserAccess.prototype = { if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) { let owner = isExternal ? null : Browser.selectedTab; - let tab = Browser.addTab("about:blank", true, owner); - // Link clicks in content need to trigger peek tab functionality - ContextUI.peekTabs(kOpenInNewTabAnimationDelayMsec); - if (isExternal) { - tab.closeOnExit = true; - } + let tab = BrowserUI.openLinkInNewTab("about:blank", true, owner); browser = tab.browser; } else { browser = Browser.selectedBrowser; @@ -1580,3 +1577,68 @@ function rendererFactory(aBrowser, aCanvas) { return wrapper; }; + +// Based on ClickEventHandler from /browser/base/content/content.js +let ClickEventHandler = { + init: function () { + gEventListenerService.addSystemEventListener(Elements.browsers, "click", this, true); + }, + + uninit: function () { + gEventListenerService.removeSystemEventListener(Elements.browsers, "click", this, true); + }, + + handleEvent: function (aEvent) { + if (!aEvent.isTrusted || aEvent.defaultPrevented) { + return; + } + let [href, node] = this._hrefAndLinkNodeForClickEvent(aEvent); + if (href && (aEvent.button == 1 || aEvent.ctrlKey)) { + // Open link in a new tab for middle-click or ctrl-click + BrowserUI.openLinkInNewTab(href, aEvent.shiftKey, Browser.selectedTab); + } + }, + + /** + * Extracts linkNode and href for the current click target. + * + * @param event + * The click event. + * @return [href, linkNode]. + * + * @note linkNode will be null if the click wasn't on an anchor + * element (or XLink). + */ + _hrefAndLinkNodeForClickEvent: function(event) { + function isHTMLLink(aNode) { + return ((aNode instanceof content.HTMLAnchorElement && aNode.href) || + (aNode instanceof content.HTMLAreaElement && aNode.href) || + aNode instanceof content.HTMLLinkElement); + } + + let node = event.target; + while (node && !isHTMLLink(node)) { + node = node.parentNode; + } + + if (node) + return [node.href, node]; + + // If there is no linkNode, try simple XLink. + let href, baseURI; + node = event.target; + while (node && !href) { + if (node.nodeType == content.Node.ELEMENT_NODE) { + href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + if (href) + baseURI = node.ownerDocument.baseURIObject; + } + node = node.parentNode; + } + + // In case of XLink, we don't return the node we got href from since + // callers expect -like elements. + // Note: makeURI() will throw if aUri is not a valid URI. + return [href ? Services.io.newURI(href, null, baseURI).spec : null, null]; + } +}; diff --git a/browser/metro/base/tests/mochitest/browser_link_click.html b/browser/metro/base/tests/mochitest/browser_link_click.html new file mode 100644 index 000000000000..5a656385cc30 --- /dev/null +++ b/browser/metro/base/tests/mochitest/browser_link_click.html @@ -0,0 +1,10 @@ + + + + + link click test + + + link + + diff --git a/browser/metro/base/tests/mochitest/browser_link_click.js b/browser/metro/base/tests/mochitest/browser_link_click.js new file mode 100644 index 000000000000..f1a71deb5120 --- /dev/null +++ b/browser/metro/base/tests/mochitest/browser_link_click.js @@ -0,0 +1,97 @@ +/* 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 test() { + waitForExplicitFinish(); + runTests(); +} + +gTests.push({ + desc: "regular link click", + run: function () { + let tab = yield addTab(chromeRoot + "browser_link_click.html"); + let tabCount = Browser.tabs.length; + + EventUtils.sendMouseEvent({type: "click"}, "link", tab.browser.contentWindow); + yield waitForCondition(() => tab.browser.currentURI.spec == "about:blank"); + is(Browser.tabs.length, tabCount, "link loaded in the same tab"); + } +}); + +gTests.push({ + desc: "middle-click opens link in background tab", + run: function () { + let tab = yield addTab(chromeRoot + "browser_link_click.html"); + + let tabOpen = waitForEvent(window, "TabOpen"); + EventUtils.sendMouseEvent({type: "click", button: 1}, "link", tab.browser.contentWindow); + let event = yield tabOpen; + + let newTab = Browser.getTabFromChrome(event.originalTarget); + yield waitForEvent(newTab.browser, "pageshow"); + + is(newTab.browser.currentURI.spec, "about:blank"); + ok(newTab != Browser.selectedTab, "new tab is in the background"); + + Browser.closeTab(newTab, { forceClose: true }); + } +}); + +gTests.push({ + desc: "shift-middle-click opens link in background tab", + run: function () { + let tab = yield addTab(chromeRoot + "browser_link_click.html"); + + let tabOpen = waitForEvent(window, "TabOpen"); + EventUtils.sendMouseEvent({type: "click", button: 1, shiftKey: true}, "link", tab.browser.contentWindow); + let event = yield tabOpen; + + let newTab = Browser.getTabFromChrome(event.originalTarget); + yield waitForEvent(newTab.browser, "pageshow"); + + is(newTab.browser.currentURI.spec, "about:blank"); + ok(newTab == Browser.selectedTab, "new tab is in the foreground"); + + Browser.closeTab(newTab, { forceClose: true }); + } +}); + +gTests.push({ + desc: "ctrl-click opens link in background tab", + run: function () { + let tab = yield addTab(chromeRoot + "browser_link_click.html"); + + let tabOpen = waitForEvent(window, "TabOpen"); + EventUtils.sendMouseEvent({type: "click", ctrlKey: true}, "link", tab.browser.contentWindow); + let event = yield tabOpen; + + let newTab = Browser.getTabFromChrome(event.originalTarget); + yield waitForEvent(newTab.browser, "pageshow"); + + is(newTab.browser.currentURI.spec, "about:blank"); + ok(newTab != Browser.selectedTab, "new tab is in the background"); + + Browser.closeTab(newTab, { forceClose: true }); + } +}); + +gTests.push({ + desc: "shift-ctrl-click opens link in background tab", + run: function () { + let tab = yield addTab(chromeRoot + "browser_link_click.html"); + + let tabOpen = waitForEvent(window, "TabOpen"); + EventUtils.sendMouseEvent({type: "click", ctrlKey: true, shiftKey: true}, "link", tab.browser.contentWindow); + let event = yield tabOpen; + + let newTab = Browser.getTabFromChrome(event.originalTarget); + yield waitForEvent(newTab.browser, "pageshow"); + + is(newTab.browser.currentURI.spec, "about:blank"); + ok(newTab == Browser.selectedTab, "new tab is in the foreground"); + + Browser.closeTab(newTab, { forceClose: true }); + } +}); diff --git a/browser/metro/base/tests/mochitest/metro.ini b/browser/metro/base/tests/mochitest/metro.ini index c31d70aefb14..307029ee5c6d 100644 --- a/browser/metro/base/tests/mochitest/metro.ini +++ b/browser/metro/base/tests/mochitest/metro.ini @@ -6,6 +6,7 @@ support-files = browser_context_menu_tests_04.html browser_findbar.html browser_form_auto_complete.html + browser_link_click.html browser_onscreen_keyboard.html browser_progress_indicator.xul browser_selection_basic.html @@ -42,6 +43,7 @@ support-files = [browser_form_auto_complete.js] [browser_history.js] [browser_inputsource.js] +[browser_link_click.js] [browser_onscreen_keyboard.js] [browser_prefs_ui.js] [browser_remotetabs.js] diff --git a/mobile/android/themes/core/content.css b/mobile/android/themes/core/content.css index 8644d6e602ca..d815ca3c4e09 100644 --- a/mobile/android/themes/core/content.css +++ b/mobile/android/themes/core/content.css @@ -306,5 +306,5 @@ textarea:not([disabled]):active, option:active, label:active, xul|menulist:active { - background-color: @color_background_highlight_overlay@ !important; + background-color: @color_background_highlight_overlay@; }