diff options
12 files changed, 510 insertions, 333 deletions
diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js index 556e7a2b7..6dbd9677e 100644 --- a/application/palemoon/base/content/browser.js +++ b/application/palemoon/base/content/browser.js @@ -10,6 +10,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/RecentWindow.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", "resource:///modules/CharsetMenu.jsm"); @@ -1895,88 +1898,126 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) { } catch (e) {} } -function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) { - // Initialize outparam to false - if (aMayInheritPrincipal) - aMayInheritPrincipal.value = false; +/** + * Given a urlbar value, discerns between URIs, keywords and aliases. + * + * @param url + * The urlbar value. + * @param callback (optional, deprecated) + * The callback function invoked when done. This parameter is + * deprecated, please use the Promise that is returned. + * + * @return Promise<{ postData, url, mayInheritPrincipal }> + */ +function getShortcutOrURIAndPostData(url, callback = null) { + if (callback) { + Deprecated.warning("Please use the Promise returned by " + + "getShortcutOrURIAndPostData() instead of passing a " + + "callback", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294"); + } - var shortcutURL = null; - var keyword = aURL; - var param = ""; + return Task.spawn(function* () { + let mayInheritPrincipal = false; + let postData = null; + let shortcutURL = null; + let keyword = url; + let param = ""; - var offset = aURL.indexOf(" "); - if (offset > 0) { - keyword = aURL.substr(0, offset); - param = aURL.substr(offset + 1); - } + let offset = url.indexOf(" "); + if (offset > 0) { + keyword = url.substr(0, offset); + param = url.substr(offset + 1); + } - if (!aPostDataRef) - aPostDataRef = {}; + let engine = Services.search.getEngineByAlias(keyword); + if (engine) { + let submission = engine.getSubmission(param, null, "keyword"); + postData = submission.postData; + return { postData: submission.postData, url: submission.uri.spec, + mayInheritPrincipal }; + } - var engine = Services.search.getEngineByAlias(keyword); - if (engine) { - var submission = engine.getSubmission(param); - aPostDataRef.value = submission.postData; - return submission.uri.spec; - } + let entry = yield PlacesUtils.keywords.fetch(keyword); + if (entry) { + shortcutURL = entry.url.href; + postData = entry.postData; + } - [shortcutURL, aPostDataRef.value] = - PlacesUtils.getURLAndPostDataForKeyword(keyword); + if (!shortcutURL) { + return { postData, url, mayInheritPrincipal }; + } - if (!shortcutURL) - return aURL; + let escapedPostData = ""; + if (postData) + escapedPostData = unescape(postData); - var postData = ""; - if (aPostDataRef.value) - postData = unescape(aPostDataRef.value); + if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { + let charset = ""; + const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; + let matches = shortcutURL.match(re); - if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) { - var charset = ""; - const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; - var matches = shortcutURL.match(re); - if (matches) - [, shortcutURL, charset] = matches; - else { - // Try to get the saved character-set. - try { - // makeURI throws if URI is invalid. - // Will return an empty string if character-set is not found. - charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL)); - } catch (e) {} - } + if (matches) { + [, shortcutURL, charset] = matches; + } else { + let uri; + try { + // makeURI() throws if URI is invalid. + uri = makeURI(shortcutURL); + } catch (ex) {} + + if (uri) { + // Try to get the saved character-set. + // Will return an empty string if character-set is not found. + charset = yield PlacesUtils.getCharsetForURI(uri); + } + } - // encodeURIComponent produces UTF-8, and cannot be used for other charsets. - // escape() works in those cases, but it doesn't uri-encode +, @, and /. - // Therefore we need to manually replace these ASCII characters by their - // encodeURIComponent result, to match the behavior of nsEscape() with - // url_XPAlphas - var encodedParam = ""; - if (charset && charset != "UTF-8") - encodedParam = escape(convertFromUnicode(charset, param)). - replace(/[+@\/]+/g, encodeURIComponent); - else // Default charset is UTF-8 - encodedParam = encodeURIComponent(param); + // encodeURIComponent produces UTF-8, and cannot be used for other charsets. + // escape() works in those cases, but it doesn't uri-encode +, @, and /. + // Therefore we need to manually replace these ASCII characters by their + // encodeURIComponent result, to match the behavior of nsEscape() with + // url_XPAlphas + let encodedParam = ""; + if (charset && charset != "UTF-8") + encodedParam = escape(convertFromUnicode(charset, param)). + replace(/[+@\/]+/g, encodeURIComponent); + else // Default charset is UTF-8 + encodedParam = encodeURIComponent(param); - shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); + shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); - if (/%s/i.test(postData)) // POST keyword - aPostDataRef.value = getPostDataStream(postData, param, encodedParam, - "application/x-www-form-urlencoded"); - } - else if (param) { - // This keyword doesn't take a parameter, but one was provided. Just return - // the original URL. - aPostDataRef.value = null; + if (/%s/i.test(escapedPostData)) // POST keyword + postData = getPostDataStream(escapedPostData, param, encodedParam, + "application/x-www-form-urlencoded"); - return aURL; - } + // This URL came from a bookmark, so it's safe to let it inherit the current + // document's principal. + mayInheritPrincipal = true; + + return { postData, url: shortcutURL, mayInheritPrincipal }; + } + + if (param) { + // This keyword doesn't take a parameter, but one was provided. Just return + // the original URL. + postData = null; + + return { postData, url, mayInheritPrincipal }; + } - // This URL came from a bookmark, so it's safe to let it inherit the current - // document's principal. - if (aMayInheritPrincipal) - aMayInheritPrincipal.value = true; + // This URL came from a bookmark, so it's safe to let it inherit the current + // document's principal. + mayInheritPrincipal = true; - return shortcutURL; + return { postData, url: shortcutURL, mayInheritPrincipal }; + }).then(data => { + if (callback) { + callback(data); + } + + return data; + }); } function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { @@ -2635,8 +2676,8 @@ var browserDragAndDrop = { } }, - drop: function (aEvent, aName, aDisallowInherit) { - return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit); + dropLinks: function (aEvent, aDisallowInherit) { + return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit); } }; @@ -2644,8 +2685,10 @@ var homeButtonObserver = { onDrop: function (aEvent) { // disallow setting home pages that inherit the principal - let url = browserDragAndDrop.drop(aEvent, {}, true); - setTimeout(openHomeDialog, 0, url); + let links = browserDragAndDrop.dropLinks(aEvent, true); + if (links.length) { + setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|")); + } }, onDragOver: function (aEvent) @@ -2661,18 +2704,24 @@ var homeButtonObserver = { function openHomeDialog(aURL) { var promptTitle = gNavigatorBundle.getString("droponhometitle"); - var promptMsg = gNavigatorBundle.getString("droponhomemsg"); + var promptMsg; + if (aURL.includes("|")) { + promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple"); + } else { + promptMsg = gNavigatorBundle.getString("droponhomemsg"); + } + var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, Services.prompt.STD_YES_NO_BUTTONS, null, null, null, null, {value:0}); if (pressedVal == 0) { try { - var str = Components.classes["@mozilla.org/supports-string;1"] - .createInstance(Components.interfaces.nsISupportsString); - str.data = aURL; + var homepageStr = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + homepageStr.data = aURL; gPrefService.setComplexValue("browser.startup.homepage", - Components.interfaces.nsISupportsString, str); + Components.interfaces.nsISupportsString, homepageStr); } catch (ex) { dump("Failed to set the home page.\n"+ex+"\n"); } @@ -2720,13 +2769,16 @@ var newTabButtonObserver = { onDrop: function (aEvent) { - let url = browserDragAndDrop.drop(aEvent, { }); - var postData = {}; - url = getShortcutOrURI(url, postData); - if (url) { - // allow third-party services to fixup this URL - openNewTabWith(url, null, postData.value, aEvent, true); - } + let links = browserDragAndDrop.dropLinks(aEvent); + Task.spawn(function*() { + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + if (data.url) { + // allow third-party services to fixup this URL + openNewTabWith(data.url, null, data.postData, aEvent, true); + } + } + }); } } @@ -2740,13 +2792,16 @@ var newWindowButtonObserver = { }, onDrop: function (aEvent) { - let url = browserDragAndDrop.drop(aEvent, { }); - var postData = {}; - url = getShortcutOrURI(url, postData); - if (url) { - // allow third-party services to fixup this URL - openNewWindowWith(url, null, postData.value, true); - } + let links = browserDragAndDrop.dropLinks(aEvent); + Task.spawn(function*() { + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + if (data.url) { + // allow third-party services to fixup this URL + openNewWindowWith(data.url, null, data.postData, true); + } + } + }); } } @@ -5034,36 +5089,81 @@ function middleMousePaste(event) { // bar's behavior (stripsurroundingwhitespace) clipboard = clipboard.replace(/\s*\n\s*/g, ""); - let mayInheritPrincipal = { value: false }; - let url = getShortcutOrURI(clipboard, mayInheritPrincipal); - try { - makeURI(url); - } catch (ex) { - // Not a valid URI. - return; + // if it's not the current tab, we don't need to do anything because the + // browser doesn't exist. + let where = whereToOpenLink(event, true, false); + let lastLocationChange; + if (where == "current") { + lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; } - try { - addToUrlbarHistory(url); - } catch (ex) { - // Things may go wrong when adding url to session history, - // but don't let that interfere with the loading of the url. - Cu.reportError(ex); - } + getShortcutOrURIAndPostData(clipboard).then(data => { + try { + makeURI(data.url); + } catch (ex) { + // Not a valid URI. + return; + } - openUILink(url, event, - { ignoreButton: true, - disallowInheritPrincipal: !mayInheritPrincipal.value }); + try { + addToUrlbarHistory(data.url); + } catch (ex) { + // Things may go wrong when adding url to session history, + // but don't let that interfere with the loading of the url. + Cu.reportError(ex); + } + + if (where != "current" || + lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { + openUILink(data.url, event, + { ignoreButton: true, + disallowInheritPrincipal: !data.mayInheritPrincipal, + initiatingDoc: event ? event.target.ownerDocument : null }); + } + }); event.stopPropagation(); } -function handleDroppedLink(event, url, name) +// handleDroppedLink has the following 2 overloads: +// handleDroppedLink(event, url, name) +// handleDroppedLink(event, links) +function handleDroppedLink(event, urlOrLinks, name) { - let postData = { }; - let uri = getShortcutOrURI(url, postData); - if (uri) - loadURI(uri, null, postData.value, false); + let links; + if (Array.isArray(urlOrLinks)) { + links = urlOrLinks; + } else { + links = [{ url: urlOrLinks, name, type: "" }]; + } + + let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; + + let userContextId = gBrowser.selectedBrowser + .getAttribute("usercontextid") || 0; + + let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + if (event.shiftKey) + inBackground = !inBackground; + + Task.spawn(function*() { + let urls = []; + let postDatas = []; + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + urls.push(data.url); + postDatas.push(data.postData); + } + if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { + gBrowser.loadTabs(urls, { + inBackground, + replace: true, + allowThirdPartyFixup: false, + postDatas, + userContextId, + }); + } + }); // Keep the event from being handled by the dragDrop listeners // built-in to goanna if they happen to be above us. @@ -5178,7 +5278,6 @@ function charsetLoadListener() { } } - var gPageStyleMenu = { _getAllStyleSheets: function (frameset) { diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js index 46e0b804b..a614d0396 100644 --- a/application/palemoon/base/content/newtab/grid.js +++ b/application/palemoon/base/content/newtab/grid.js @@ -59,8 +59,13 @@ var gGrid = { * Refreshes the grid and re-creates all sites. */ refresh: function Grid_refresh() { + let cells = this.cells; + if (!cells) { + return; + } + // Remove all sites. - this.cells.forEach(function (cell) { + cells.forEach(function (cell) { let node = cell.node; let child = node.firstElementChild; diff --git a/application/palemoon/base/content/openLocation.js b/application/palemoon/base/content/openLocation.js index 316dfac70..1a10334c7 100644 --- a/application/palemoon/base/content/openLocation.js +++ b/application/palemoon/base/content/openLocation.js @@ -61,45 +61,52 @@ function doEnabling() function open() { - var url; - var postData = {}; - var mayInheritPrincipal = {value: false}; - if (browser) - url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal); - else - url = dialog.input.value; + getShortcutOrURIAndPostData(dialog.input.value).then(data => { + let url; + let postData = null; + let mayInheritPrincipal = false; + + if (browser) { + url = data.url; + postData = data.postData; + mayInheritPrincipal = data.mayInheritPrincipal; + } else { + url = dialog.input.value; + } - try { - // Whichever target we use for the load, we allow third-party services to - // fixup the URI - switch (dialog.openWhereList.value) { - case "0": - var webNav = Components.interfaces.nsIWebNavigation; - var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - if (!mayInheritPrincipal.value) - flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; - browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value); - break; - case "1": - window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no", - url, postData.value, null, null, true); - break; - case "3": - browser.delayedOpenTab(url, null, null, postData.value, true); - break; + try { + // Whichever target we use for the load, we allow third-party services to + // fixup the URI + switch (dialog.openWhereList.value) { + case "0": + var webNav = Components.interfaces.nsIWebNavigation; + var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | + webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; + if (!mayInheritPrincipal) + flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData); + break; + case "1": + window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no", + url, postData, null, null, true); + break; + case "3": + browser.delayedOpenTab(url, null, null, postData, true); + break; + } + } + catch(exception) { } - } - catch(exception) { - } - if (pref) { - gOpenLocationLastURL.value = dialog.input.value; - pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value); - } + if (pref) { + gOpenLocationLastURL.value = dialog.input.value; + pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value); + } + + // Delay closing slightly to avoid timing bug on Linux. + window.close(); + }); - // Delay closing slightly to avoid timing bug on Linux. - window.close(); return false; } diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index a7cc6deea..c06b49af0 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -570,6 +570,12 @@ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const nsIChannel = Components.interfaces.nsIChannel; + let location, originalLocation; + try { + aRequest.QueryInterface(nsIChannel) + location = aRequest.URI; + originalLocation = aRequest.originalURI; + } catch (ex) {} if (aStateFlags & nsIWebProgressListener.STATE_START) { this.mRequestCount++; @@ -588,16 +594,8 @@ if (aStateFlags & nsIWebProgressListener.STATE_START && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { - // It's okay to clear what the user typed when we start - // loading a document. If the user types, this counter gets - // set to zero, if the document load ends without an - // onLocationChange, this counter gets decremented - // (so we keep it while switching tabs after failed loads) - // We need to add 2 because loadURIWithFlags may have - // cancelled a pending load which would have cleared - // its anchor scroll detection temporary increment. if (aWebProgress.isTopLevel) - this.mBrowser.userTypedClear += 2; + this.mBrowser.urlbarChangeTracker.startedLoad(); if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { @@ -625,8 +623,8 @@ this.mTab.removeAttribute("progress"); if (aWebProgress.isTopLevel) { - if (!Components.isSuccessCode(aStatus) && - !isTabEmpty(this.mTab)) { + let isSuccessful = Components.isSuccessCode(aStatus); + if (!isSuccessful && !isTabEmpty(this.mTab)) { // Restore the current document's location in case the // request was stopped (possibly from a content script) // before the location changed. @@ -635,14 +633,8 @@ if (this.mTab.selected && gURLBar) URLBarSetURI(); - } else { - // The document is done loading, we no longer want the - // value cleared. - - if (this.mBrowser.userTypedClear > 1) - this.mBrowser.userTypedClear -= 2; - else if (this.mBrowser.userTypedClear > 0) - this.mBrowser.userTypedClear--; + } else if (isSuccessful) { + this.mBrowser.urlbarChangeTracker.finishedLoad(); } if (!this.mBrowser.mIconURL) @@ -652,8 +644,6 @@ if (this.mBlank) this.mBlank = false; - var location = aRequest.QueryInterface(nsIChannel).URI; - // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword") this.mBrowser.userTypedValue = null; @@ -696,13 +686,12 @@ let topLevel = aWebProgress.isTopLevel; if (topLevel) { - // If userTypedClear > 0, the document loaded correctly and we should be - // clearing the user typed value. We also need to clear the typed value + // We need to clear the typed value // if the document failed to load, to make sure the urlbar reflects the // failed URI (particularly for SSL errors). However, don't clear the value // if the error page's URI is about:blank, because that causes complete // loss of urlbar contents for invalid URI errors (see bug 867957). - if (this.mBrowser.userTypedClear > 0 || + if (this.mBrowser.didStartLoadSinceLastUserTyping() || ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && aLocation.spec != "about:blank")) this.mBrowser.userTypedValue = null; @@ -738,8 +727,10 @@ aFlags]); } - if (topLevel) + if (topLevel) { this.mBrowser.lastURI = aLocation; + this.mBrowser.lastLocationChange = Date.now(); + } }, onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { @@ -1312,6 +1303,24 @@ <parameter name="aLoadInBackground"/> <parameter name="aReplace"/> <body><![CDATA[ + let aAllowThirdPartyFixup; + let aTargetTab; + let aNewIndex = -1; + let aPostDatas = []; + let aUserContextId; + if (arguments.length == 2 && + typeof arguments[1] == "object") { + let params = arguments[1]; + aLoadInBackground = params.inBackground; + aReplace = params.replace; + aAllowThirdPartyFixup = params.allowThirdPartyFixup; + aTargetTab = params.targetTab; + aNewIndex = typeof params.newIndex === "number" ? + params.newIndex : aNewIndex; + aPostDatas = params.postDatas || aPostDatas; + aUserContextId = params.userContextId; + } + if (!aURIs.length) return; @@ -1329,22 +1338,53 @@ var multiple = aURIs.length > 1; var owner = multiple || aLoadInBackground ? null : this.selectedTab; var firstTabAdded = null; + var targetTabIndex = -1; if (aReplace) { + let browser; + if (aTargetTab) { + browser = this.getBrowserForTab(aTargetTab); + targetTabIndex = aTargetTab._tPos; + } else { + browser = this.mCurrentBrowser; + targetTabIndex = this.tabContainer.selectedIndex; + } + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + if (aAllowThirdPartyFixup) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | + Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; + } try { - this.loadURI(aURIs[0], null, null); + browser.loadURIWithFlags(aURIs[0], { + flags, postData: aPostDatas[0] + }); } catch (e) { // Ignore failure in case a URI is wrong, so we can continue // opening the next ones. } + } else { + firstTabAdded = this.addTab(aURIs[0], { + ownerTab: owner, + skipAnimation: multiple, + allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostDatas[0], + userContextId: aUserContextId + }); + if (aNewIndex !== -1) { + this.moveTabTo(firstTabAdded, aNewIndex); + targetTabIndex = firstTabAdded._tPos; + } } - else - firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple}); - var tabNum = this.tabContainer.selectedIndex; + let tabNum = targetTabIndex; for (let i = 1; i < aURIs.length; ++i) { - let tab = this.addTab(aURIs[i], {skipAnimation: true}); - if (aReplace) + let tab = this.addTab(aURIs[i], { + skipAnimation: true, + allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostDatas[i], + userContextId: aUserContextId + }); + if (targetTabIndex !== -1) this.moveTabTo(tab, ++tabNum); } @@ -2976,10 +3016,6 @@ ]]></body> </method> - <property name="userTypedClear" - onget="return this.mCurrentBrowser.userTypedClear;" - onset="return this.mCurrentBrowser.userTypedClear = val;"/> - <property name="userTypedValue" onget="return this.mCurrentBrowser.userTypedValue;" onset="return this.mCurrentBrowser.userTypedValue = val;"/> @@ -4456,40 +4492,35 @@ this.tabbrowser.updateCurrentBrowser(true); } else { // Pass true to disallow dropping javascript: or data: urls - let url; + let links; try { - url = browserDragAndDrop.drop(event, { }, true); + links = browserDragAndDrop.dropLinks(event, true); } catch (ex) {} // // valid urls don't contain spaces ' '; if we have a space it isn't a valid url. // if (!url || url.includes(" ")) //PMed - if (!url) //FF + if (!links || links.length === 0) //FF return; - let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) - bgLoad = !bgLoad; + inBackground = !inBackground; - let tab = this._getDragTargetTab(event); - if (!tab || dropEffect == "copy") { - // We're adding a new tab. - let newIndex = this._getDropIndex(event); - let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true}); - this.tabbrowser.moveTabTo(newTab, newIndex); - } else { - // Load in an existing tab. - try { - let webNav = Ci.nsIWebNavigation; - let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags); - if (!bgLoad) - this.selectedItem = tab; - } catch(ex) { - // Just ignore invalid urls - } - } + let targetTab = this._getDragTargetTab(event); + let userContextId = this.selectedItem + .getAttribute("usercontextid") || 0; + let replace = !(!targetTab || dropEffect == "copy"); + let newIndex = this._getDropIndex(event); + let urls = links.map(link => link.url); + this.tabbrowser.loadTabs(urls, { + inBackground, + replace, + allowThirdPartyFixup: true, + targetTab, + newIndex, + userContextId, + }); } if (draggedTab) { diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml index 4a6cfeee5..985769ec2 100644 --- a/application/palemoon/base/content/urlbarBindings.xml +++ b/application/palemoon/base/content/urlbarBindings.xml @@ -263,6 +263,9 @@ var postData = null; var action = this._parseActionUrl(url); + let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; + + let matchLastLocationChange = true; if (action) { url = action.param; if (this.hasAttribute("actiontype")) { @@ -275,82 +278,94 @@ } return; } + continueOperation.call(this); } else { - [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent); - if (!url) - return; - } - - this.value = url; - gBrowser.userTypedValue = url; - try { - addToUrlbarHistory(url); - } catch (ex) { - // Things may go wrong when adding url to session history, - // but don't let that interfere with the loading of the url. - Cu.reportError(ex); + this._canonizeURL(aTriggeringEvent, response => { + [url, postData, mayInheritPrincipal] = response; + if (url) { + matchLastLocationChange = (lastLocationChange == + gBrowser.selectedBrowser.lastLocationChange); + continueOperation.call(this); + } + }); } - function loadCurrent() { - let webnav = Ci.nsIWebNavigation; - let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from - // inheriting the currently loaded document's principal, unless this - // URL is marked as safe to inherit (e.g. came from a bookmark - // keyword). - if (!mayInheritPrincipal) - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; - gBrowser.loadURIWithFlags(url, flags, null, null, postData); - } + function continueOperation() + { + this.value = url; + gBrowser.userTypedValue = url; + try { + addToUrlbarHistory(url); + } catch (ex) { + // Things may go wrong when adding url to session history, + // but don't let that interfere with the loading of the url. + Cu.reportError(ex); + } - // Focus the content area before triggering loads, since if the load - // occurs in a new tab, we want focus to be restored to the content - // area when the current tab is re-selected. - gBrowser.selectedBrowser.focus(); + function loadCurrent() { + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from + // inheriting the currently loaded document's principal, unless this + // URL is marked as safe to inherit (e.g. came from a bookmark + // keyword). + if (!mayInheritPrincipal) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + // If the value wasn't typed, we know that we decoded the value as + // UTF-8 (see losslessDecodeURI) + if (!this.valueIsTyped) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8; + gBrowser.loadURIWithFlags(url, flags, null, null, postData); + } - let isMouseEvent = aTriggeringEvent instanceof MouseEvent; - let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey; + // Focus the content area before triggering loads, since if the load + // occurs in a new tab, we want focus to be restored to the content + // area when the current tab is re-selected. + gBrowser.selectedBrowser.focus(); - if (altEnter) { - // XXX This was added a long time ago, and I'm not sure why it is - // necessary. Alt+Enter's default action might cause a system beep, - // or something like that? - aTriggeringEvent.preventDefault(); - aTriggeringEvent.stopPropagation(); - } + let isMouseEvent = aTriggeringEvent instanceof MouseEvent; - // If the current tab is empty, ignore Alt+Enter (just reuse this tab) - altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab); + // If the current tab is empty, ignore Alt+Enter (just reuse this tab) + let altEnter = !isMouseEvent && aTriggeringEvent && + aTriggeringEvent.altKey && !isTabEmpty(gBrowser.selectedTab); - if (isMouseEvent || altEnter) { - // Use the standard UI link behaviors for clicks or Alt+Enter - let where = "tab"; - if (isMouseEvent) - where = whereToOpenLink(aTriggeringEvent, false, false); + if (isMouseEvent || altEnter) { + // Use the standard UI link behaviors for clicks or Alt+Enter + let where = "tab"; + if (isMouseEvent) + where = whereToOpenLink(aTriggeringEvent, false, false); - if (where == "current") { - loadCurrent(); + if (where == "current") { + if (matchLastLocationChange) { + loadCurrent(); + } + } else { + this.handleRevert(); + let params = { allowThirdPartyFixup: true, + postData: postData, + initiatingDoc: document }; + if (!this.valueIsTyped) + params.isUTF8 = true; + openUILinkIn(url, where, params); + } } else { - this.handleRevert(); - let params = { allowThirdPartyFixup: true, - postData: postData, - initiatingDoc: document }; - openUILinkIn(url, where, params); + if (matchLastLocationChange) { + loadCurrent(); + } } - } else { - loadCurrent(); } ]]></body> </method> <method name="_canonizeURL"> <parameter name="aTriggeringEvent"/> + <parameter name="aCallback"/> <body><![CDATA[ var url = this.value; - if (!url) - return ["", null, false]; + if (!url) { + aCallback(["", null, false]); + return; + } // Only add the suffix when the URL bar value isn't already "URL-like", // and only if we get a keyboard event, to match user expectations. @@ -403,11 +418,9 @@ } } - var postData = {}; - var mayInheritPrincipal = { value: false }; - url = getShortcutOrURI(url, postData, mayInheritPrincipal); - - return [url, postData.value, mayInheritPrincipal.value]; + getShortcutOrURIAndPostData(url).then(data => { + aCallback([data.url, data.postData, data.mayInheritPrincipal]); + }); ]]></body> </method> @@ -442,11 +455,12 @@ <method name="onDrop"> <parameter name="aEvent"/> <body><![CDATA[ - let url = browserDragAndDrop.drop(aEvent, { }) + let links = browserDragAndDrop.dropLinks(aEvent); // The URL bar automatically handles inputs with newline characters, // so we can get away with treating text/x-moz-url flavours as text/plain. - if (url) { + if (links.length > 0 && links[0].url) { + let url = links[0].url; aEvent.preventDefault(); this.value = url; SetPageProxyState("invalid"); diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 46e867068..054f0405f 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -41,22 +41,6 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = const NOT_AVAILABLE = Number.MAX_VALUE; /** - * Download a URL. - * - * @param aURL - * the url to download (nsIURI object) - * @param [optional] aFileName - * the destination file name - */ -function DownloadURL(aURL, aFileName) { - // For private browsing, try to get document out of the most recent browser - // window, or provide our own if there's no browser window. - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - saveURL(aURL, aFileName, null, true, true, undefined, initiatingDoc); -} - -/** * A download element shell is responsible for handling the commands and the * displayed data for a single download view element. The download element * could represent either a past download (for which we get data from places) or @@ -654,7 +638,10 @@ DownloadElementShell.prototype = { // In future we may try to download into the same original target uri, when // we have it. Though that requires verifying the path is still valid and // may surprise the user if he wants to be requested every time. - DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName); + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName, + initiatingDoc); }, /* nsIController */ @@ -1407,16 +1394,11 @@ DownloadsPlacesView.prototype = { _copySelectedDownloadsToClipboard: function DPV__copySelectedDownloadsToClipboard() { - let selectedElements = this._richlistbox.selectedItems; - // Tycho: let urls = [e._shell.downloadURI for each (e in selectedElements)]; - let urls = []; - - for each (e in selectedElements) { - urls.push(e._shell.downloadURI); - } + let urls = [for (element of this._richlistbox.selectedItems) + element._shell.downloadURI]; Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper).copyString(urls.join("\n"), document); + getService(Ci.nsIClipboardHelper).copyString(urls.join("\n")); }, _getURLFromClipboardData: function DPV__getURLFromClipboardData() { @@ -1450,10 +1432,16 @@ DownloadsPlacesView.prototype = { _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() { let [url, name] = this._getURLFromClipboardData(); - DownloadURL(url, name); + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + DownloadURL(url, name, initiatingDoc); }, doCommand: function DPV_doCommand(aCommand) { + // Commands may be invoked with keyboard shortcuts even if disabled. + if (!this.isCommandEnabled(aCommand)) { + return; + } switch (aCommand) { case "cmd_copy": this._copySelectedDownloadsToClipboard(); @@ -1504,6 +1492,11 @@ DownloadsPlacesView.prototype = { else contextMenu.removeAttribute("state"); + if (state == nsIDM.DOWNLOAD_DOWNLOADING) { + // The resumable property of a download may change at any time, so + // ensure we update the related command now. + goUpdateCommand("downloadsCmd_pauseResume"); + } return true; }, @@ -1594,10 +1587,16 @@ DownloadsPlacesView.prototype = { if (dt.mozGetDataAt("application/x-moz-file", 0)) return; - let name = { }; - let url = Services.droppedLinkHandler.dropLink(aEvent, name); - if (url) - DownloadURL(url, name.value); + let links = Services.droppedLinkHandler.dropLinks(aEvent); + if (!links.length) + return; + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + for (let link of links) { + if (link.url.startsWith("about:")) + continue; + DownloadURL(link.url, link.name, initiatingDoc); + } } }; diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index 0412344bc..833d7d72f 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -507,8 +507,7 @@ const DownloadsPanel = { let uri = NetUtil.newURI(url); DownloadsCommon.log("Pasted URL seems valid. Starting download."); - saveURL(uri.spec, name || uri.spec, null, true, true, - undefined, document); + DownloadURL(uri.spec, name, document); } catch (ex) {} }, diff --git a/application/palemoon/components/downloads/content/indicator.js b/application/palemoon/components/downloads/content/indicator.js index e6a5bd012..1a2175a92 100644 --- a/application/palemoon/components/downloads/content/indicator.js +++ b/application/palemoon/components/downloads/content/indicator.js @@ -548,15 +548,18 @@ const DownloadsIndicatorView = { if (dt.mozGetDataAt("application/x-moz-file", 0)) return; - let name = {}; - let url = browserDragAndDrop.drop(aEvent, name); - if (url) { - if (url.startsWith("about:")) { - return; - } - - let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; - saveURL(url, name.value, null, true, true, null, sourceDoc); + let links = browserDragAndDrop.dropLinks(aEvent); + if (!links.length) + return; + let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; + let handled = false; + for (let link of links) { + if (link.url.startsWith("about:")) + continue; + saveURL(link.url, link.name, null, true, true, null, sourceDoc); + handled = true; + } + if (handled) { aEvent.preventDefault(); } }, diff --git a/application/palemoon/components/sessionstore/SessionStore.jsm b/application/palemoon/components/sessionstore/SessionStore.jsm index f7c495be8..b8d126e21 100644 --- a/application/palemoon/components/sessionstore/SessionStore.jsm +++ b/application/palemoon/components/sessionstore/SessionStore.jsm @@ -1951,7 +1951,13 @@ var SessionStoreInternal = { // userTypedValue. if (browser.userTypedValue) { tabData.userTypedValue = browser.userTypedValue; - tabData.userTypedClear = browser.userTypedClear; + // We always used to keep track of the loading state as an integer, where + // '0' indicated the user had typed since the last load (or no load was + // ongoing), and any positive value indicated we had started a load since + // the last time the user typed in the URL bar. Mimic this to keep the + // session store representation in sync, even though we now represent this + // more explicitly: + tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0; } else { delete tabData.userTypedValue; delete tabData.userTypedClear; diff --git a/application/palemoon/locales/en-US/chrome/browser/browser.properties b/application/palemoon/locales/en-US/chrome/browser/browser.properties index 2468f3c48..7f1c88a88 100644 --- a/application/palemoon/locales/en-US/chrome/browser/browser.properties +++ b/application/palemoon/locales/en-US/chrome/browser/browser.properties @@ -7,6 +7,7 @@ openFile=Open File droponhometitle=Set Home Page droponhomemsg=Do you want this document to be your new home page? +droponhomemsgMultiple=Do you want these documents to be your new home pages? # context menu strings diff --git a/devtools/client/shared/widgets/TableWidget.js b/devtools/client/shared/widgets/TableWidget.js index a0f0dfc11..57d2914d5 100644 --- a/devtools/client/shared/widgets/TableWidget.js +++ b/devtools/client/shared/widgets/TableWidget.js @@ -463,7 +463,14 @@ TableWidget.prototype = { return; } - let selectedCell = this.tbody.querySelector(".theme-selected"); + // We need to get the first *visible* selected cell. Some columns are hidden + // e.g. because they contain a unique compound key for cookies that is never + // displayed in the UI. To do this we get all selected cells and filter out + // any that are hidden. + let selectedCells = [...this.tbody.querySelectorAll(".theme-selected")] + .filter(cell => cell.clientWidth > 0); + // Select the first visible selected cell. + let selectedCell = selectedCells[0]; if (!selectedCell) { return; } diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js index 7745c8da9..5bd29d297 100644 --- a/devtools/client/storage/ui.js +++ b/devtools/client/storage/ui.js @@ -482,24 +482,27 @@ StorageUI.prototype = { * @param {object} See onUpdate docs */ handleChangedItems: function (changed) { - if (this.tree.selectedItem) { - let [type, host, db, objectStore] = this.tree.selectedItem; - if (!changed[type] || !changed[type][host] || - changed[type][host].length == 0) { - return; - } - try { - let toUpdate = []; - for (let name of changed[type][host]) { - let names = JSON.parse(name); - if (names[0] == db && names[1] == objectStore && names[2]) { - toUpdate.push(name); - } + let selectedItem = this.tree.selectedItem; + if (!selectedItem) { + return; + } + + let [type, host, db, objectStore] = selectedItem; + if (!changed[type] || !changed[type][host] || + changed[type][host].length == 0) { + return; + } + try { + let toUpdate = []; + for (let name of changed[type][host]) { + let names = JSON.parse(name); + if (names[0] == db && names[1] == objectStore && names[2]) { + toUpdate.push(name); } - this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE); - } catch (ex) { - this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE); } + this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE); + } catch (ex) { + this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE); } }, @@ -830,6 +833,7 @@ StorageUI.prototype = { if (!item) { return; } + this.table.clear(); this.hideSidebar(); this.searchBox.value = ""; @@ -1136,11 +1140,13 @@ StorageUI.prototype = { * Handles adding an item from the storage */ onAddItem: function () { - if (!this.tree.selectedItem) { + let selectedItem = this.tree.selectedItem; + if (!selectedItem) { return; } + let front = this.getCurrentFront(); - let [, host] = this.tree.selectedItem; + let [, host] = selectedItem; // Prepare to scroll into view. this.table.scrollIntoViewOnUpdate = true; |