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@;
}