diff --git a/b2g/components/AlertsService.js b/b2g/components/AlertsService.js index 38cc9b8bc521..fadfb4a8603d 100644 --- a/b2g/components/AlertsService.js +++ b/b2g/components/AlertsService.js @@ -66,23 +66,36 @@ AlertsService.prototype = { }, // nsIAlertsService + showAlert: function(aAlert, aAlertListener) { + if (!aAlert) { + return; + } + cpmm.sendAsyncMessage(kMessageAlertNotificationSend, { + imageURL: aAlert.imageURL, + title: aAlert.title, + text: aAlert.text, + clickable: aAlert.textClickable, + cookie: aAlert.cookie, + listener: aAlertListener, + id: aAlert.name, + dir: aAlert.dir, + lang: aAlert.lang, + dataStr: aAlert.data, + inPrivateBrowsing: aAlert.inPrivateBrowsing + }); + }, + showAlertNotification: function(aImageUrl, aTitle, aText, aTextClickable, aCookie, aAlertListener, aName, aBidi, aLang, aDataStr, aPrincipal, aInPrivateBrowsing) { - cpmm.sendAsyncMessage(kMessageAlertNotificationSend, { - imageURL: aImageUrl, - title: aTitle, - text: aText, - clickable: aTextClickable, - cookie: aCookie, - listener: aAlertListener, - id: aName, - dir: aBidi, - lang: aLang, - dataStr: aDataStr, - inPrivateBrowsing: aInPrivateBrowsing - }); + let alert = Cc["@mozilla.org/alert-notification;1"]. + createInstance(Ci.nsIAlertNotification); + + alert.init(aName, aImageUrl, aTitle, aText, aTextClickable, aCookie, + aBidi, aLang, aDataStr, aPrincipal, aInPrivateBrowsing); + + this.showAlert(alert, aAlertListener); }, closeAlert: function(aName) { diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index 8156c43f0b1d..3ef0308f31e6 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -739,8 +739,6 @@ HistoryMenu.prototype = { return; } - let enabled = PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem(); - menuitem.setAttribute("disabled", !enabled); menuitem.setAttribute("hidden", false); }, diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 67f0bb3680bc..d49586ceaeb8 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -803,6 +803,15 @@ BrowserGlue.prototype = { this._sanitizer.onStartup(); // check if we're in safe mode if (Services.appinfo.inSafeMode) { + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1231112#c7 . We need to + // register the observer early if we have to migrate tab groups + let currentUIVersion = 0; + try { + currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); + } catch(ex) {} + if (currentUIVersion < 35) { + this._maybeMigrateTabGroups(); + } Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul", "_blank", "chrome,centerscreen,modal,resizable=no", null); } @@ -2241,7 +2250,8 @@ BrowserGlue.prototype = { this._notifyNotificationsUpgrade().catch(Cu.reportError); } - if (currentUIVersion < 35) { + // Only do this outside of safe mode, because in safe mode we do this earlier. + if (currentUIVersion < 35 && !Services.appinfo.inSafeMode) { this._maybeMigrateTabGroups(); } diff --git a/browser/extensions/loop/bootstrap.js b/browser/extensions/loop/bootstrap.js index f19049011912..56e925405043 100644 --- a/browser/extensions/loop/bootstrap.js +++ b/browser/extensions/loop/bootstrap.js @@ -556,7 +556,8 @@ var WindowListener = { buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") : this._getString("infobar_button_pause_accesskey"); return true; - } + }, + type: "pause" }, { label: this._getString("infobar_button_stop_label"), @@ -565,7 +566,8 @@ var WindowListener = { callback: () => { this._hideBrowserSharingInfoBar(); LoopUI.MozLoopService.hangupAllChatWindows(); - } + }, + type: "stop" }] ); diff --git a/browser/extensions/loop/skin/osx/platform.css b/browser/extensions/loop/skin/osx/platform.css index 8960052547fb..def39bd465a6 100644 --- a/browser/extensions/loop/skin/osx/platform.css +++ b/browser/extensions/loop/skin/osx/platform.css @@ -21,6 +21,11 @@ border-radius: 0; } + /* Hide Pause/Resume button until the functionality is complete */ + notification[value="loop-sharing-notification"] .notification-button[type="pause"] { + display:none; + } + notification[value="loop-sharing-notification"].paused .notification-button { background: #57bd35; } diff --git a/browser/extensions/loop/skin/shared/loop.css b/browser/extensions/loop/skin/shared/loop.css index 391f33bfe996..a52e24384cba 100644 --- a/browser/extensions/loop/skin/shared/loop.css +++ b/browser/extensions/loop/skin/shared/loop.css @@ -192,6 +192,11 @@ text-shadow: none; } + /* Hide Pause/Resume button until the functionality is complete */ + notification[value="loop-sharing-notification"] .notification-button[type="pause"] { + display:none; + } + notification[value="loop-sharing-notification"].paused .notification-button { background-color: #57bd35; color: #fff; diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index fdb981842519..b50a82e13a8d 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -1440,6 +1440,10 @@ richlistitem[type~="action"][actiontype$="tab"] > .ac-url-box > .ac-action-icon background-color: Window; } +#sidebar-header > .close-icon:not(:hover):-moz-lwtheme-brighttext { + background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64); +} + .browserContainer > findbar { background-color: -moz-dialog; color: -moz-DialogText; diff --git a/browser/themes/linux/devedition.css b/browser/themes/linux/devedition.css index 0f4f1c7ddb2e..66a5274c3fcb 100644 --- a/browser/themes/linux/devedition.css +++ b/browser/themes/linux/devedition.css @@ -4,6 +4,8 @@ %include ../shared/devedition.inc.css +:root[devtoolstheme="dark"] .findbar-closebutton:not(:hover), +:root[devtoolstheme="dark"] #sidebar-header > .close-icon:not(:hover), .tab-close-button[visuallyselected]:not(:hover) { background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64); } @@ -76,8 +78,9 @@ border-top-width: 0 !important; } -/* Prevent devedition foreground color from seeping into the sidebar-box (since - * its background colors aren't affected by the devedition theme) */ -#sidebar-box { - color: initial; +/* Fix the bad-looking text-shadow in the sidebar header: */ +.sidebar-header, +#sidebar-header { + text-shadow: none; } + diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css index 79514f98761d..eb423a3f87b1 100644 --- a/browser/themes/windows/browser-aero.css +++ b/browser/themes/windows/browser-aero.css @@ -52,8 +52,8 @@ @media (-moz-os-version: windows-vista), (-moz-os-version: windows-win7) { - .sidebar-header, - #sidebar-header { + .sidebar-header:not(:-moz-lwtheme), + #sidebar-header:not(:-moz-lwtheme) { background-color: #EEF3FA; } diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index e50275eb6594..eab731fac895 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -1872,6 +1872,18 @@ richlistitem[type~="action"][actiontype$="tab"] > .ac-url-box > .ac-action-icon border: none; } +@media not all and (min-resolution: 1.1dppx) { + #sidebar-header > .close-icon:-moz-lwtheme-brighttext { + list-style-image: url("chrome://global/skin/icons/close-inverted.png"); + } +} + +@media (min-resolution: 1.1dppx) { + #sidebar-header > .close-icon:-moz-lwtheme-brighttext { + list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png"); + } +} + @media (-moz-os-version: windows-xp), (-moz-os-version: windows-vista), (-moz-os-version: windows-win7) { diff --git a/browser/themes/windows/devedition.css b/browser/themes/windows/devedition.css index ec945c22c11c..f441bf95e8a5 100644 --- a/browser/themes/windows/devedition.css +++ b/browser/themes/windows/devedition.css @@ -107,6 +107,7 @@ } :root[devtoolstheme="dark"] .findbar-closebutton, +:root[devtoolstheme="dark"] #sidebar-header > .close-icon, /* Tab styling - make sure to use an inverted icon for the selected tab (brighttext only covers the unselected tabs) */ .tab-close-button[visuallyselected=true] { @@ -115,6 +116,7 @@ @media (min-resolution: 1.1dppx) { :root[devtoolstheme="dark"] .findbar-closebutton, + :root[devtoolstheme="dark"] #sidebar-header > .close-icon, .tab-close-button[visuallyselected=true] { list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png"); } @@ -254,6 +256,14 @@ border-right: none !important; } +/* The sidebar header has no background now that the background of the #browser-panel + * has no image and is transparent. Fix: */ +.sidebar-header:-moz-lwtheme, +#sidebar-header { + background-color: var(--chrome-background-color); + color: var(--chrome-color); +} + @media (-moz-os-version: windows-vista), (-moz-os-version: windows-win7), (-moz-os-version: windows-win8) { diff --git a/devtools/bootstrap.js b/devtools/bootstrap.js new file mode 100644 index 000000000000..109d56a3f771 --- /dev/null +++ b/devtools/bootstrap.js @@ -0,0 +1,99 @@ +/* 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 Cu = Components.utils; +const Ci = Components.interfaces; +const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +// Helper to listen to a key on all windows +function MultiWindowKeyListener({ keyCode, ctrlKey, altKey, callback }) { + let keyListener = function (event) { + if (event.ctrlKey == !!ctrlKey && + event.altKey == !!altKey && + event.keyCode === keyCode) { + callback(event); + + // Call preventDefault to avoid duplicated events when + // doing the key stroke within a tab. + event.preventDefault(); + } + }; + + let observer = function (window, topic, data) { + // Listen on keyup to call keyListener only once per stroke + if (topic === "domwindowopened") { + window.addEventListener("keyup", keyListener); + } else { + window.removeEventListener("keyup", keyListener); + } + }; + + return { + start: function () { + // Automatically process already opened windows + let e = Services.ww.getWindowEnumerator(); + while (e.hasMoreElements()) { + let window = e.getNext(); + observer(window, "domwindowopened", null); + } + // And listen for new ones to come + Services.ww.registerNotification(observer); + }, + + stop: function () { + Services.ww.unregisterNotification(observer); + let e = Services.ww.getWindowEnumerator(); + while (e.hasMoreElements()) { + let window = e.getNext(); + observer(window, "domwindowclosed", null); + } + } + }; +}; + +let getTopLevelWindow = function (window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); +}; + +function reload(event) { + // We automatically reload the toolbox if we are on a browser tab + // with a toolbox already opened + let top = getTopLevelWindow(event.view) + let isBrowser = top.location.href.includes("/browser.xul") && top.gDevToolsBrowser; + let reloadToolbox = false; + if (isBrowser && top.gDevToolsBrowser.hasToolboxOpened) { + reloadToolbox = top.gDevToolsBrowser.hasToolboxOpened(top); + } + dump("Reload DevTools. (reload-toolbox:"+reloadToolbox+")\n"); + + // Invalidate xul cache in order to see changes made to chrome:// files + Services.obs.notifyObservers(null, "startupcache-invalidate", null); + + // Ask the loader to update itself and reopen the toolbox if needed + const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {}); + devtools.reload(reloadToolbox); +} + +let listener; +function startup() { + dump("DevTools addon started.\n"); + listener = new MultiWindowKeyListener({ + keyCode: Ci.nsIDOMKeyEvent.DOM_VK_R, ctrlKey: true, altKey: true, + callback: reload + }); + listener.start(); +} +function shutdown() { + listener.stop(); + listener = null; +} +function install() {} +function uninstall() {} diff --git a/devtools/chrome.manifest b/devtools/chrome.manifest new file mode 100644 index 000000000000..bcf3431992d0 --- /dev/null +++ b/devtools/chrome.manifest @@ -0,0 +1,6 @@ +content devtools client/ +skin devtools classic/1.0 client/themes/ +resource devtools . + +content webide client/webide/content/ +skin webide classic/1.0 client/webide/themes/ diff --git a/devtools/client/debugger/debugger-controller.js b/devtools/client/debugger/debugger-controller.js index a4a89b69c8cb..bd02b82c3012 100644 --- a/devtools/client/debugger/debugger-controller.js +++ b/devtools/client/debugger/debugger-controller.js @@ -18,7 +18,7 @@ const CALL_STACK_PAGE_SIZE = 25; // frames const EVENTS = { // When the debugger's source editor instance finishes loading or unloading. EDITOR_LOADED: "Debugger:EditorLoaded", - EDITOR_UNLOADED: "Debugger:EditorUnoaded", + EDITOR_UNLOADED: "Debugger:EditorUnloaded", // When new sources are received from the debugger server. NEW_SOURCE: "Debugger:NewSource", diff --git a/devtools/client/framework/gDevTools.jsm b/devtools/client/framework/gDevTools.jsm index cb3d0b3ac4fa..d90030d132ac 100644 --- a/devtools/client/framework/gDevTools.jsm +++ b/devtools/client/framework/gDevTools.jsm @@ -1161,6 +1161,16 @@ var gDevToolsBrowser = { }; }, + hasToolboxOpened: function(win) { + let tab = win.gBrowser.selectedTab; + for (let [target, toolbox] of gDevTools._toolboxes) { + if (target.tab == tab) { + return true; + } + } + return false; + }, + /** * Update the "Toggle Tools" checkbox in the developer tools menu. This is * called when a toolbox is created or destroyed. @@ -1168,13 +1178,7 @@ var gDevToolsBrowser = { _updateMenuCheckbox: function DT_updateMenuCheckbox() { for (let win of gDevToolsBrowser._trackedBrowserWindows) { - let hasToolbox = false; - if (TargetFactory.isKnownTab(win.gBrowser.selectedTab)) { - let target = TargetFactory.forTab(win.gBrowser.selectedTab); - if (gDevTools._toolboxes.has(target)) { - hasToolbox = true; - } - } + let hasToolbox = gDevToolsBrowser.hasToolboxOpened(win); let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); if (hasToolbox) { diff --git a/devtools/client/jsonview/components/reps/tree-view.js b/devtools/client/jsonview/components/reps/tree-view.js index a08534d68ed7..3452e1c62587 100644 --- a/devtools/client/jsonview/components/reps/tree-view.js +++ b/devtools/client/jsonview/components/reps/tree-view.js @@ -51,9 +51,8 @@ var TreeView = React.createClass({ } return ( - DOM.div({className: "domTable", cellPadding: 0, cellSpacing: 0, - onClick: this.onClick}, - children + DOM.div({className: "domTable", cellPadding: 0, cellSpacing: 0}, + children ) ); }, @@ -151,8 +150,9 @@ var TreeNode = React.createFactory(React.createClass({ } return ( - DOM.div({className: classNames.join(" "), onClick: this.onClick}, - DOM.span({className: "memberLabelCell"}, + DOM.div({className: classNames.join(" ")}, + DOM.span({className: "memberLabelCell", onClick: this.onClick}, + DOM.span({className: "memberIcon"}), DOM.span({className: "memberLabel " + member.type + "Label"}, member.name) ), diff --git a/devtools/client/jsonview/converter-sniffer.js b/devtools/client/jsonview/converter-sniffer.js index 0160ddc3144d..bc913dd316ec 100644 --- a/devtools/client/jsonview/converter-sniffer.js +++ b/devtools/client/jsonview/converter-sniffer.js @@ -22,7 +22,6 @@ const CONTRACT_ID = "@mozilla.org/devtools/jsonview-sniffer;1"; const CLASS_ID = "{4148c488-dca1-49fc-a621-2a0097a62422}"; const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view"; const JSON_VIEW_TYPE = "JSON View"; -const JSON_EXTENSION = "json"; const CONTENT_SNIFFER_CATEGORY = "net-content-sniffers"; /** @@ -64,10 +63,6 @@ var Sniffer = Class({ if (aRequest.contentType == JSON_TYPE) { return JSON_VIEW_MIME_TYPE; } - - if (NetworkHelper.getFileExtension(aRequest.name) == JSON_EXTENSION) { - return JSON_VIEW_MIME_TYPE; - } } return ""; diff --git a/devtools/client/jsonview/css/controls.png b/devtools/client/jsonview/css/controls.png new file mode 100644 index 000000000000..569c266e4e4c Binary files /dev/null and b/devtools/client/jsonview/css/controls.png differ diff --git a/devtools/client/jsonview/css/controls@2x.png b/devtools/client/jsonview/css/controls@2x.png new file mode 100644 index 000000000000..fb062516db8e Binary files /dev/null and b/devtools/client/jsonview/css/controls@2x.png differ diff --git a/devtools/client/jsonview/css/dom-tree.css b/devtools/client/jsonview/css/dom-tree.css index 8e65bf5deea3..127d05965d5f 100644 --- a/devtools/client/jsonview/css/dom-tree.css +++ b/devtools/client/jsonview/css/dom-tree.css @@ -20,7 +20,6 @@ .memberLabelCell { padding: 2px 0 2px 0px; - vertical-align: top; } .memberValueCell { @@ -31,7 +30,6 @@ .memberLabel { cursor: default; overflow: hidden; - padding-left: 18px; white-space: nowrap; } @@ -60,6 +58,11 @@ /******************************************************************************/ +.memberRow.hasChildren > .memberLabelCell > .memberIcon:hover, +.memberRow.cropped > .memberLabelCell > .memberIcon:hover { + cursor: pointer; +} + .memberRow.hasChildren > .memberLabelCell > .memberLabel:hover, .memberRow.cropped > .memberLabelCell > .memberLabel:hover { cursor: pointer; @@ -71,6 +74,10 @@ background-color: #EFEFEF; } +.memberRow { + padding: 3px 0 3px 0; +} + .panelNode-dom .memberRow td, .panelNode-domSide .memberRow td { border-bottom: 1px solid #EFEFEF; @@ -116,19 +123,38 @@ /******************************************************************************/ /* Twisties */ -.memberRow.hasChildren > .memberLabelCell > .memberLabel, -.memberRow.cropped > .memberLabelCell > .memberLabel { - background-image: url(twisty-closed.svg); - background-repeat: no-repeat; - background-position: 2px calc(0.5em - 3px); - min-height: 12px; +.memberRow > .memberLabelCell > .memberIcon { + height: 14px; + width: 14px; + display: inline-block; + line-height: 15px; + vertical-align: bottom; + padding-right: 2px; + margin-left: 3px; } -.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel, -.memberRow.cropped.opened > .memberLabelCell > .memberLabel { - background-image: url(twisty-open.svg); +.memberRow.hasChildren > .memberLabelCell > .memberIcon, +.memberRow.cropped > .memberLabelCell > .memberIcon { + background-image: url("./twisty-closed.svg"); background-repeat: no-repeat; - min-height: 12px; +} + +.memberRow.hasChildren.opened > .memberLabelCell > .memberIcon, +.memberRow.cropped.opened > .memberLabelCell > .memberIcon { + background-image: url("./twisty-open.svg"); + background-repeat: no-repeat; +} + +@media (min-resolution: 1.1dppx) { +.memberRow.hasChildren > .memberLabelCell > .memberIcon, +.memberRow.cropped > .memberLabelCell > .memberIcon { + background-image: url("./controls@2x.png"); +} + +.memberRow.hasChildren.opened > .memberLabelCell > .memberIcon, +.memberRow.cropped.opened > .memberLabelCell > .memberIcon { + background-image: url("./controls@2x.png"); +} } /******************************************************************************/ @@ -140,7 +166,6 @@ .memberLabelCell, .memberValueCell { - display: table-cell; } .memberLabelCell { @@ -150,3 +175,42 @@ .memberRow:hover { background-color: transparent !important; } + +/******************************************************************************/ +/* Themes */ + +.theme-light .memberRow.hasChildren > .memberLabelCell > .memberIcon, +.theme-light .memberRow.cropped > .memberLabelCell > .memberIcon { + background-image: url("./controls.png"); + background-size: 56px 28px; + background-repeat: no-repeat; + background-position: 0 -14px; +} + +.theme-light .memberRow.hasChildren.opened > .memberLabelCell > .memberIcon, +.theme-light .memberRow.cropped.opened > .memberLabelCell > .memberIcon { + background-image: url("./controls.png"); + background-size: 56px 28px; + background-repeat: no-repeat; + background-position: -14px -14px; +} + +.theme-dark .memberRow.hasChildren > .memberLabelCell > .memberIcon, +.theme-dark .memberRow.cropped > .memberLabelCell > .memberIcon { + background-image: url("./controls.png"); + background-size: 56px 28px; + background-repeat: no-repeat; + background-position: -28px -14px; +} + +.theme-dark .memberRow.hasChildren.opened > .memberLabelCell > .memberIcon, +.theme-dark .memberRow.cropped.opened > .memberLabelCell > .memberIcon { + background-image: url("./controls.png"); + background-size: 56px 28px; + background-repeat: no-repeat; + background-position: -42px -14px; +} + +.theme-dark .memberRow:hover { + background-color: var(--theme-selection-background-semitransparent); +} diff --git a/devtools/client/jsonview/css/moz.build b/devtools/client/jsonview/css/moz.build index be368cb8bba6..a71ba20ada79 100644 --- a/devtools/client/jsonview/css/moz.build +++ b/devtools/client/jsonview/css/moz.build @@ -6,6 +6,8 @@ DevToolsModules( + 'controls.png', + 'controls@2x.png', 'dom-tree.css', 'general.css', 'headers-panel.css', diff --git a/devtools/client/jsonview/css/reps.css b/devtools/client/jsonview/css/reps.css index 85a633ad03d3..bd4c73f64fe7 100644 --- a/devtools/client/jsonview/css/reps.css +++ b/devtools/client/jsonview/css/reps.css @@ -175,7 +175,7 @@ .theme-dark .domLabel, .theme-light .domLabel { - color: var(--theme-highlight-bluegrey); + color: var(--theme-highlight-blue); } .theme-dark .objectBox-array .length, @@ -207,7 +207,7 @@ .theme-light .objectBox-object { font-family: Lucida Grande, sans-serif; font-weight: normal; - color: var(--theme-highlight-bluegrey); + color: var(--theme-highlight-blue); white-space: pre-wrap; } @@ -215,5 +215,5 @@ .theme-light .caption { font-family: Lucida Grande, Tahoma, sans-serif; font-weight: normal; - color: var(--theme-highlight-bluegrey); + color: var(--theme-highlight-blue); } diff --git a/devtools/client/shared/components/test/mochitest/head.js b/devtools/client/shared/components/test/mochitest/head.js index e61d1de027fe..089bbbabc936 100644 --- a/devtools/client/shared/components/test/mochitest/head.js +++ b/devtools/client/shared/components/test/mochitest/head.js @@ -20,10 +20,10 @@ Services.prefs.setBoolPref("devtools.memory.enabled", true); var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {}); var { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); -var { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); -var { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); -var DevToolsUtils = require("devtools/shared/DevToolsUtils"); var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {}); +var { DebuggerServer } = require("devtools/server/main"); +var { DebuggerClient } = require("devtools/shared/client/main"); +var DevToolsUtils = require("devtools/shared/DevToolsUtils"); var { TargetFactory } = require("devtools/client/framework/target"); var { Toolbox } = require("devtools/client/framework/toolbox"); diff --git a/devtools/client/themes/dark-theme.css b/devtools/client/themes/dark-theme.css index 57f642d5d5e8..6e92d449bb54 100644 --- a/devtools/client/themes/dark-theme.css +++ b/devtools/client/themes/dark-theme.css @@ -168,7 +168,7 @@ body { .devtools-toolbar, .devtools-sidebar-tabs tabs, .devtools-sidebar-alltabs, -.CodeMirror-dialog { /* General toolbar styling */ +.cm-s-mozilla .CodeMirror-dialog { /* General toolbar styling */ color: var(--theme-body-color-alt); background-color: var(--theme-toolbar-background); border-color: hsla(210,8%,5%,.6); diff --git a/devtools/client/themes/light-theme.css b/devtools/client/themes/light-theme.css index 5d0d522f7c5d..148213943b76 100644 --- a/devtools/client/themes/light-theme.css +++ b/devtools/client/themes/light-theme.css @@ -171,7 +171,7 @@ body { .devtools-toolbar, .devtools-sidebar-tabs tabs, .devtools-sidebar-alltabs, -.CodeMirror-dialog { /* General toolbar styling */ +.cm-s-mozilla .CodeMirror-dialog { /* General toolbar styling */ color: var(--theme-body-color-alt); background-color: var(--theme-toolbar-background); border-color: var(--theme-splitter-color); diff --git a/devtools/install.rdf b/devtools/install.rdf new file mode 100644 index 000000000000..fbef350794ac --- /dev/null +++ b/devtools/install.rdf @@ -0,0 +1,27 @@ + + + + + + + true + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + 44.0a1 + * + + + + diff --git a/devtools/shared/webconsole/network-helper.js b/devtools/shared/webconsole/network-helper.js index ac5df06367e0..c6bc763ae8ef 100644 --- a/devtools/shared/webconsole/network-helper.js +++ b/devtools/shared/webconsole/network-helper.js @@ -803,36 +803,6 @@ var NetworkHelper = { let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL); aStore.set(aUrl, uri); return uri; - }, - - /** - * Returns extension for file URLs (e.g. 'json'). - * Not everyURL has an extension and this method works as follows: - * 1) Remove query string - * 2) Get part after the last slash (a file name) - * 3) Look for the last dot (an extension) - */ - getFileExtension: function(aUrl) { - if (!aUrl) { - return; - } - - // Remove query string from the URL if any. - let queryString = aUrl.indexOf("?"); - if (queryString != -1) { - aUrl = aUrl.substr(0, queryString); - } - - // Look for the part after last slash - var lastSlash = aUrl.lastIndexOf("/"); - var fileName = aUrl.substr(lastSlash + 1); - if (!fileName) { - return; - } - - // Now get the file extension. - var lastDot = fileName.lastIndexOf("."); - return fileName.substr(lastDot + 1); } }; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 1286fbed22a9..75abad0d3b0a 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -4415,25 +4415,26 @@ ContentParent::HasNotificationPermission(const IPC::Principal& aPrincipal) } bool -ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, - const nsString& aText, const bool& aTextClickable, - const nsString& aCookie, const nsString& aName, - const nsString& aBidi, const nsString& aLang, - const nsString& aData, - const IPC::Principal& aPrincipal, - const bool& aInPrivateBrowsing) +ContentParent::RecvShowAlert(const AlertNotificationType& aAlert) { - if (!HasNotificationPermission(aPrincipal)) { + nsCOMPtr alert(dont_AddRef(aAlert)); + if (NS_WARN_IF(!alert)) { return true; } + nsCOMPtr principal; + nsresult rv = alert->GetPrincipal(getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv)) || + !HasNotificationPermission(IPC::Principal(principal))) { + + return true; + } + nsCOMPtr sysAlerts(do_GetService(NS_ALERTSERVICE_CONTRACTID)); if (sysAlerts) { - sysAlerts->ShowAlertNotification(aImageUrl, aTitle, aText, aTextClickable, - aCookie, this, aName, aBidi, aLang, - aData, aPrincipal, aInPrivateBrowsing); - } - return true; + sysAlerts->ShowAlert(alert, this); + } + return true; } bool diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index df63164e27b7..a61a6ebfb6f9 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -901,14 +901,7 @@ private: bool HasNotificationPermission(const IPC::Principal& aPrincipal); - virtual bool - RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, - const nsString& aText, const bool& aTextClickable, - const nsString& aCookie, const nsString& aName, - const nsString& aBidi, const nsString& aLang, - const nsString& aData, - const IPC::Principal& aPrincipal, - const bool& aInPrivateBrowsing) override; + virtual bool RecvShowAlert(const AlertNotificationType& aAlert) override; virtual bool RecvCloseAlert(const nsString& aName, const IPC::Principal& aPrincipal) override; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index d516cb777a57..ad67d477ed80 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -74,6 +74,7 @@ include ProfilerTypes; include "mozilla/dom/PContentBridgeParent.h"; using GeoPosition from "nsGeoPositionIPCSerialiser.h"; +using AlertNotificationType from "mozilla/AlertNotificationIPCSerializer.h"; using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h"; using struct SubstitutionMapping from "mozilla/chrome/RegistryMessageUtils.h"; @@ -881,17 +882,7 @@ parent: CpowEntry[] aCpows, Principal aPrincipal) returns (StructuredCloneData[] retval); - ShowAlertNotification(nsString imageUrl, - nsString title, - nsString text, - bool textClickable, - nsString cookie, - nsString name, - nsString bidi, - nsString lang, - nsString data, - Principal principal, - bool inPrivateBrowsing); + ShowAlert(AlertNotificationType alert); CloseAlert(nsString name, Principal principal); diff --git a/dom/notification/DesktopNotification.cpp b/dom/notification/DesktopNotification.cpp index a25f1925931b..5b3c5c1c5e7a 100644 --- a/dom/notification/DesktopNotification.cpp +++ b/dom/notification/DesktopNotification.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/DesktopNotificationBinding.h" #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" #include "mozilla/dom/ToJSValue.h" +#include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsXULAppAPI.h" #include "mozilla/dom/PBrowserChild.h" @@ -114,16 +115,20 @@ DesktopNotification::PostDesktopNotification() nsIPrincipal* principal = doc->NodePrincipal(); nsCOMPtr loadContext = doc->GetLoadContext(); bool inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); - return alerts->ShowAlertNotification(mIconURL, mTitle, mDescription, - true, - uniqueName, - mObserver, - uniqueName, - NS_LITERAL_STRING("auto"), - EmptyString(), - EmptyString(), - principal, - inPrivateBrowsing); + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(uniqueName, mIconURL, mTitle, + mDescription, + true, + uniqueName, + NS_LITERAL_STRING("auto"), + EmptyString(), + EmptyString(), + principal, + inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return alerts->ShowAlert(alert, mObserver); } DesktopNotification::DesktopNotification(const nsAString & title, diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 201c7837abc2..49e01fcf30e0 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -23,6 +23,7 @@ #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" #include "nsAlertsUtils.h" +#include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsContentUtils.h" #include "nsCRTGlue.h" @@ -1787,11 +1788,19 @@ Notification::ShowInternal() nsAutoString alertName; GetAlertName(alertName); - alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true, - uniqueCookie, alertObserver, alertName, - DirectionToString(mDir), mLang, - mDataAsBase64, GetPrincipal(), - inPrivateBrowsing); + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE_VOID(alert); + rv = alert->Init(alertName, iconUrl, mTitle, mBody, + true, + uniqueCookie, + DirectionToString(mDir), + mLang, + mDataAsBase64, + GetPrincipal(), + inPrivateBrowsing); + NS_ENSURE_SUCCESS_VOID(rv); + alertService->ShowAlert(alert, alertObserver); } /* static */ bool diff --git a/dom/tests/browser/browser_test_toolbars_visibility.js b/dom/tests/browser/browser_test_toolbars_visibility.js index 77c15e67bd66..90e724b02e2d 100644 --- a/dom/tests/browser/browser_test_toolbars_visibility.js +++ b/dom/tests/browser/browser_test_toolbars_visibility.js @@ -98,15 +98,15 @@ function testNonDefaultContentToolbars(toolbars) { function testNonDefaultChromeToolbars(toolbars) { // None of the toolbars should be visible if hidden with chrome privileges ok(!toolbars.locationbar, - "locationbar should be visible on default window.open()"); + "locationbar should not be visible with location=no"); ok(!toolbars.menubar, - "menubar be visible on default window.open()"); + "menubar should not be visible with menubar=no"); ok(!toolbars.personalbar, - "personalbar should be visible on default window.open()"); + "personalbar should not be visible with personalbar=no"); ok(!toolbars.statusbar, - "statusbar should be visible on default window.open()"); + "statusbar should not be visible with status=no"); ok(!toolbars.toolbar, - "toolbar should be visible on default window.open()"); + "toolbar should not be visible with toolbar=no"); } /** @@ -147,7 +147,42 @@ add_task(function*() { let popupToolbars = yield getToolbarsFromBrowserContent(popupBrowser); testNonDefaultContentToolbars(popupToolbars); - // Cleanup + // Ensure that chrome toolbars agree with content + let chromeToolbars = getToolbarsFromWindowChrome(popupWindow); + testNonDefaultContentToolbars(chromeToolbars); + + // Close the new window + yield BrowserTestUtils.closeWindow(popupWindow); + }); +}); + +/** + * Ensure that toolbars of a window opened to about:blank in the content context + * have the correct visibility. + * + * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no, + * menubar=no, status=no", should only have location visible. + */ +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: CONTENT_PAGE, + }, function*(browser) { + // Open a blank window with toolbars hidden + let winPromise = BrowserTestUtils.waitForNewWindow(); + yield BrowserTestUtils.synthesizeMouseAtCenter("#winOpenNoURLNonDefault", {}, browser); + let popupWindow = yield winPromise; + + // No need to wait for this window to load, since it's loading about:blank + let popupBrowser = popupWindow.gBrowser.selectedBrowser; + let popupToolbars = yield getToolbarsFromBrowserContent(popupBrowser); + testNonDefaultContentToolbars(popupToolbars); + + // Ensure that chrome toolbars agree with content + let chromeToolbars = getToolbarsFromWindowChrome(popupWindow); + testNonDefaultContentToolbars(chromeToolbars); + + // Close the new window yield BrowserTestUtils.closeWindow(popupWindow); }); }); diff --git a/dom/tests/browser/test_new_window_from_content_child.html b/dom/tests/browser/test_new_window_from_content_child.html index 5d2733ba180a..e5da60b42b28 100644 --- a/dom/tests/browser/test_new_window_from_content_child.html +++ b/dom/tests/browser/test_new_window_from_content_child.html @@ -7,6 +7,7 @@

Open a new window via window.open with default features.

Open a new window via window.open with non-default features.

Open a new window via window.open with dialog=1.

+

Open a blank new window via window.open with non-default features.

Open a new window via target="_blank".

@@ -16,4 +17,9 @@ function openWindow(aFeatures="") { window.open("about:robots", "_blank", aFeatures); return false; } + +function openBlankWindow(aFeatures="") { + window.open("", "_blank", aFeatures); + return false; +} diff --git a/dom/tests/mochitest/notification/MockServices.js b/dom/tests/mochitest/notification/MockServices.js index b47194b5c60a..ff8c5da0a033 100644 --- a/dom/tests/mochitest/notification/MockServices.js +++ b/dom/tests/mochitest/notification/MockServices.js @@ -29,26 +29,34 @@ var MockServices = (function () { }); var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name) { + showAlert: function(alert, alertListener) { var listener = SpecialPowers.wrap(alertListener); - activeAlertNotifications[name] = { + activeAlertNotifications[alert.name] = { listener: listener, - cookie: cookie, - title: title + cookie: alert.cookie, + title: alert.title }; // fake async alert show event if (listener) { setTimeout(function () { - listener.observe(null, "alertshow", cookie); + listener.observe(null, "alertshow", alert.cookie); }, 100); setTimeout(function () { - listener.observe(null, "alertclickcallback", cookie); + listener.observe(null, "alertclickcallback", alert.cookie); }, 100); } }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name) { + this.showAlert({ + name: name, + cookie: cookie, + title: title + }, alertListener); + }, + showAppNotification: function(aImageUrl, aTitle, aText, aAlertListener, aDetails) { var listener = aAlertListener || (activeAlertNotifications[aDetails.id] ? activeAlertNotifications[aDetails.id].listener : undefined); activeAppNotifications[aDetails.id] = { diff --git a/dom/tests/mochitest/notification/desktop-notification/notification_common.js b/dom/tests/mochitest/notification/desktop-notification/notification_common.js index 47d9d22da978..3aae3cef4b91 100644 --- a/dom/tests/mochitest/notification/desktop-notification/notification_common.js +++ b/dom/tests/mochitest/notification/desktop-notification/notification_common.js @@ -8,17 +8,23 @@ var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager. QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar); var mockAlertsService = { + showAlert: function(alert, alertListener) { + // probably should do this async.... + SpecialPowers.wrap(alertListener).observe(null, "alertshow", alert.cookie); + + if (SpecialPowers.getBoolPref("notification.prompt.testing.click_on_notification") == true) { + SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", alert.cookie); + } + + SpecialPowers.wrap(alertListener).observe(null, "alertfinished", alert.cookie); + }, + showAlertNotification: function(imageUrl, title, text, textClickable, cookie, alertListener, name, bidi, lang, data) { - // probably should do this async.... - SpecialPowers.wrap(alertListener).observe(null, "alertshow", cookie); - - if (SpecialPowers.getBoolPref("notification.prompt.testing.click_on_notification") == true) { - SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie); - } - - SpecialPowers.wrap(alertListener).observe(null, "alertfinished", cookie); + return this.showAlert({ + cookie: cookie + }, alertListener); }, showAppNotification: function(imageUrl, title, text, alertListener, details) { diff --git a/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html b/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html index 086ff0a7b57a..37bcb16c0299 100644 --- a/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html +++ b/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html @@ -23,15 +23,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=782211 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name, dir, - lang, data) { - notificationsCreated.push(name); + showAlert: function(alert, alertListener) { + notificationsCreated.push(alert.name); if (notificationsCreated.length == 3) { checkNotifications(); } }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name, dir, + lang, data) { + this.showAlert({ name: name }); + }, + QueryInterface: function(aIID) { if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) || SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) { diff --git a/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul b/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul index 52312a76635a..1690bc101bdf 100644 --- a/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul +++ b/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul @@ -23,13 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=874090 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name, dir, lang, data) { - ok(true, "System principal was granted permission and is able to call showAlertNotification."); + showAlert: function(alert, alertListener) { + ok(true, "System principal was granted permission and is able to call showAlert."); unregisterMock(); SimpleTest.finish(); }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name, dir, lang, data) { + this.showAlert(); + }, + QueryInterface: function(aIID) { if (aIID.equals(Components.interfaces.nsISupports) || aIID.equals(Components.interfaces.nsIAlertsService)) { diff --git a/dom/tests/mochitest/webapps/head.js b/dom/tests/mochitest/webapps/head.js index 8d5e9c042ff3..ba5a995e46fd 100644 --- a/dom/tests/mochitest/webapps/head.js +++ b/dom/tests/mochitest/webapps/head.js @@ -117,6 +117,9 @@ var AlertsService = { "", ALERTS_SERVICE_CONTRACT_ID, null); }, + showAlert: function() { + }, + showAlertNotification: function() { }, }; diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java index e7de311fe61c..93f156e8dce2 100644 --- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java @@ -2170,13 +2170,21 @@ public class BrowserApp extends GeckoApp final Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { - final String userRequested = tab.getUserRequested(); + final String userSearchTerm = tab.getUserRequested(); // Check to see if there's a user-entered search term, // which we save whenever the user performs a search. - url = (TextUtils.isEmpty(userRequested) ? tab.getURL() : userRequested); - } + final String telemetryMsg; + if (!TextUtils.isEmpty(userSearchTerm)) { + url = userSearchTerm; + telemetryMsg = "urlbar-userentered"; + } else { + url = tab.getURL(); + telemetryMsg = url.isEmpty() ? "urlbar-empty" : "urlbar-url"; + } + Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.ACTIONBAR, telemetryMsg); + } enterEditingMode(url); } @@ -2210,7 +2218,12 @@ public class BrowserApp extends GeckoApp mBrowserToolbar.startEditing(url, animator); - showHomePagerWithAnimator(panelId, animator); + final boolean isUserSearchTerm = !TextUtils.isEmpty(selectedTab.getUserRequested()); + if (isUserSearchTerm && AppConstants.NIGHTLY_BUILD) { + showBrowserSearchAfterAnimation(animator); + } else { + showHomePagerWithAnimator(panelId, animator); + } animator.start(); Telemetry.startUISession(TelemetryContract.Session.AWESOMESCREEN); @@ -2622,6 +2635,24 @@ public class BrowserApp extends GeckoApp refreshToolbarHeight(); } + private void showBrowserSearchAfterAnimation(PropertyAnimator animator) { + if (animator == null) { + showBrowserSearch(); + return; + } + + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + @Override + public void onPropertyAnimationStart() { + } + + @Override + public void onPropertyAnimationEnd() { + showBrowserSearch(); + } + }); + } + private void showBrowserSearch() { if (mBrowserSearch.getUserVisibleHint()) { return; @@ -2672,7 +2703,7 @@ public class BrowserApp extends GeckoApp // To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed: // reverse that. - mHomePagerContainer.setVisibility(View.VISIBLE); + showHomePager(Tabs.getInstance().getSelectedTab().getMostRecentHomePanel()); mBrowserSearchContainer.setVisibility(View.INVISIBLE); diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java index ac8aedc86f9e..afa3898dabdf 100644 --- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java +++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java @@ -87,6 +87,11 @@ public class DownloadAction extends BaseAction { temporaryFile = createTemporaryFile(context, content); + if (!canWrite(temporaryFile, destinationFile)) { + throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO, + "Temporary or destination file not writeable"); + } + if (!hasEnoughDiskSpace(content, destinationFile, temporaryFile)) { Log.d(LOGTAG, "Not enough disk space to save content. Skipping download."); continue; @@ -125,7 +130,7 @@ public class DownloadAction extends BaseAction { temporaryFile.delete(); } } catch (RecoverableDownloadContentException e) { - Log.w(LOGTAG, "Downloading content failed (Recoverable): " + content , e); + Log.w(LOGTAG, "Downloading content failed (Recoverable): " + content, e); if (e.shouldBeCountedAsFailure()) { catalog.rememberFailure(content, e.getErrorType()); @@ -336,4 +341,14 @@ public class DownloadAction extends BaseAction { return true; } + + protected boolean canWrite(File... files) { + for (File file : files) { + if (!file.canWrite()) { + return false; + } + } + + return true; + } } diff --git a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java index 18cca87f6d59..6c206c067d1a 100644 --- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java +++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java @@ -168,6 +168,7 @@ public class TestDownloadAction { doReturn(file).when(action).createTemporaryFile(RuntimeEnvironment.application, content); doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content); + doReturn(true).when(action).canWrite(any(File.class), any(File.class)); doReturn(false).when(action).verify(eq(file), anyString()); doNothing().when(action).download(any(HttpClient.class), anyString(), eq(file)); doReturn(true).when(action).verify(eq(file), anyString()); @@ -202,6 +203,7 @@ public class TestDownloadAction { DownloadAction action = spy(new DownloadAction(null)); doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application); + doReturn(true).when(action).canWrite(any(File.class), any(File.class)); File temporaryFile = mockFileWithSize(1337L); doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content); @@ -301,6 +303,7 @@ public class TestDownloadAction { File destinationFile = mockNotExistingFile(); doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content); + doReturn(true).when(action).canWrite(any(File.class), any(File.class)); doReturn(true).when(action).verify(eq(temporaryFile), anyString()); doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString()); @@ -333,6 +336,7 @@ public class TestDownloadAction { DownloadAction action = spy(new DownloadAction(null)); doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application); + doReturn(true).when(action).canWrite(any(File.class), any(File.class)); doNothing().when(action).download(any(HttpClient.class), anyString(), any(File.class)); doReturn(false).when(action).verify(any(File.class), anyString()); @@ -443,6 +447,7 @@ public class TestDownloadAction { doReturn(mockNotExistingFile()).when(action).createTemporaryFile(RuntimeEnvironment.application, content); doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content); doReturn(true).when(action).hasEnoughDiskSpace(eq(content), any(File.class), any(File.class)); + doReturn(true).when(action).canWrite(any(File.class), any(File.class)); HttpClient client = mock(HttpClient.class); doThrow(IOException.class).when(client).execute(any(HttpUriRequest.class)); @@ -499,6 +504,32 @@ public class TestDownloadAction { verify(catalog, times(11)).rememberFailure(eq(content), anyInt()); } + /** + * Scenario: Temporary or destination file is not writable. + * + * Verify that: + * * No download is performed + * * Error is counted as failure + */ + @Test + public void testNoDownIsPerformedIfFilesAreNotWritable() throws Exception{ + DownloadContent content = createFont(); + DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content); + + DownloadAction action = spy(new DownloadAction(null)); + doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application); + doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application); + doReturn(mockNotExistingFile()).when(action).createTemporaryFile(RuntimeEnvironment.application, content); + doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content); + doReturn(false).when(action).canWrite(any(File.class), any(File.class)); + + action.perform(RuntimeEnvironment.application, catalog); + + verify(action).canWrite(any(File.class), any(File.class)); + verify(action, never()).download(any(HttpClient.class), anyString(), any(File.class)); + verify(catalog).rememberFailure(eq(content), anyInt()); + } + private DownloadContent createFont() { return createFontWithSize(102400L); } diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java index f40f34049ee1..17ccc1e4d040 100644 --- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java @@ -159,7 +159,7 @@ public class AppMenuComponent extends BaseComponent { * This method is dependent on not having two views with equivalent contentDescription / text. */ private View findAppMenuItemView(String text) { - mSolo.waitForText(text, 1, MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS); + mSolo.waitForText(String.format("^%s$", text), 1, MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS); final List views = mSolo.getViews(); @@ -298,7 +298,8 @@ public class AppMenuComponent extends BaseComponent { private boolean isLegacyMoreMenuOpen() { // Check if the first menu option is visible. - return mSolo.searchText(mSolo.getString(R.string.share), true); + final String shareTitle = mSolo.getString(R.string.share); + return mSolo.searchText(String.format("^%s$", shareTitle), true); } /** @@ -310,7 +311,7 @@ public class AppMenuComponent extends BaseComponent { */ private boolean isMenuOpen(String menuItemTitle) { final View menuItemView = findAppMenuItemView(menuItemTitle); - return isMenuOpen(menuItemView) ? true : mSolo.searchText(menuItemTitle, true); + return isMenuOpen(menuItemView) ? true : mSolo.searchText(String.format("^%s$", menuItemTitle), true); } /** diff --git a/toolkit/components/alerts/AlertNotification.cpp b/toolkit/components/alerts/AlertNotification.cpp new file mode 100644 index 000000000000..c3e1e85aca7f --- /dev/null +++ b/toolkit/components/alerts/AlertNotification.cpp @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Pub + * License, v. 2.0. If a copy of the MPL was not distributed with t + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AlertNotification.h" + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTION(AlertNotification, mPrincipal) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AlertNotification) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertNotification) + NS_INTERFACE_MAP_ENTRY(nsIAlertNotification) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(AlertNotification) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AlertNotification) + +AlertNotification::AlertNotification() + : mTextClickable(false) + , mPrincipal(nullptr) + , mInPrivateBrowsing(false) +{} + +AlertNotification::~AlertNotification() +{} + +NS_IMETHODIMP +AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL, + const nsAString& aTitle, const nsAString& aText, + bool aTextClickable, const nsAString& aCookie, + const nsAString& aDir, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing) +{ + mName = aName; + mImageURL = aImageURL; + mTitle = aTitle; + mText = aText; + mTextClickable = aTextClickable; + mCookie = aCookie; + mDir = aDir; + mLang = aLang; + mData = aData; + mPrincipal = aPrincipal; + mInPrivateBrowsing = aInPrivateBrowsing; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetImageURL(nsAString& aImageURL) +{ + aImageURL = mImageURL; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTitle(nsAString& aTitle) +{ + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetText(nsAString& aText) +{ + aText = mText; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTextClickable(bool* aTextClickable) +{ + *aTextClickable = mTextClickable; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetCookie(nsAString& aCookie) +{ + aCookie = mCookie; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetDir(nsAString& aDir) +{ + aDir = mDir; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetLang(nsAString& aLang) +{ + aLang = mLang; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetData(nsAString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal) +{ + NS_IF_ADDREF(*aPrincipal = mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing) +{ + *aInPrivateBrowsing = mInPrivateBrowsing; + return NS_OK; +} + +} // namespace mozilla diff --git a/toolkit/components/alerts/AlertNotification.h b/toolkit/components/alerts/AlertNotification.h new file mode 100644 index 000000000000..420e899c87f8 --- /dev/null +++ b/toolkit/components/alerts/AlertNotification.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AlertNotification_h__ +#define mozilla_AlertNotification_h__ + +#include "nsIAlertsService.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIPrincipal.h" +#include "nsString.h" + +namespace mozilla { + +class AlertNotification final : public nsIAlertNotification +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(AlertNotification, + nsIAlertNotification) + NS_DECL_NSIALERTNOTIFICATION + AlertNotification(); + +protected: + virtual ~AlertNotification(); + +private: + nsString mName; + nsString mImageURL; + nsString mTitle; + nsString mText; + bool mTextClickable; + nsString mCookie; + nsString mDir; + nsString mLang; + nsString mData; + nsCOMPtr mPrincipal; + bool mInPrivateBrowsing; +}; + +} // namespace mozilla + +#endif /* mozilla_AlertNotification_h__ */ diff --git a/toolkit/components/alerts/AlertNotificationIPCSerializer.h b/toolkit/components/alerts/AlertNotificationIPCSerializer.h new file mode 100644 index 000000000000..9eaefacda581 --- /dev/null +++ b/toolkit/components/alerts/AlertNotificationIPCSerializer.h @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AlertNotificationIPCSerializer_h__ +#define mozilla_AlertNotificationIPCSerializer_h__ + +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIAlertsService.h" +#include "nsIPrincipal.h" +#include "nsString.h" + +#include "ipc/IPCMessageUtils.h" + +#include "mozilla/dom/PermissionMessageUtils.h" + +typedef nsIAlertNotification* AlertNotificationType; + +namespace IPC { + +template <> +struct ParamTraits +{ + typedef AlertNotificationType paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + bool isNull = !aParam; + if (isNull) { + WriteParam(aMsg, isNull); + return; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing; + nsCOMPtr principal; + + if (NS_WARN_IF(NS_FAILED(aParam->GetName(name))) || + NS_WARN_IF(NS_FAILED(aParam->GetImageURL(imageURL))) || + NS_WARN_IF(NS_FAILED(aParam->GetTitle(title))) || + NS_WARN_IF(NS_FAILED(aParam->GetText(text))) || + NS_WARN_IF(NS_FAILED(aParam->GetTextClickable(&textClickable))) || + NS_WARN_IF(NS_FAILED(aParam->GetCookie(cookie))) || + NS_WARN_IF(NS_FAILED(aParam->GetDir(dir))) || + NS_WARN_IF(NS_FAILED(aParam->GetLang(lang))) || + NS_WARN_IF(NS_FAILED(aParam->GetData(data))) || + NS_WARN_IF(NS_FAILED(aParam->GetPrincipal(getter_AddRefs(principal)))) || + NS_WARN_IF(NS_FAILED(aParam->GetInPrivateBrowsing(&inPrivateBrowsing)))) { + + // Write a `null` object if any getter returns an error. Otherwise, the + // receiver will try to deserialize an incomplete object and crash. + WriteParam(aMsg, /* isNull */ true); + return; + } + + WriteParam(aMsg, isNull); + WriteParam(aMsg, name); + WriteParam(aMsg, imageURL); + WriteParam(aMsg, title); + WriteParam(aMsg, text); + WriteParam(aMsg, textClickable); + WriteParam(aMsg, cookie); + WriteParam(aMsg, dir); + WriteParam(aMsg, lang); + WriteParam(aMsg, data); + WriteParam(aMsg, IPC::Principal(principal)); + WriteParam(aMsg, inPrivateBrowsing); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + bool isNull; + NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &isNull), false); + if (isNull) { + *aResult = nullptr; + return true; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing; + IPC::Principal principal; + + if (!ReadParam(aMsg, aIter, &name) || + !ReadParam(aMsg, aIter, &imageURL) || + !ReadParam(aMsg, aIter, &title) || + !ReadParam(aMsg, aIter, &text) || + !ReadParam(aMsg, aIter, &textClickable) || + !ReadParam(aMsg, aIter, &cookie) || + !ReadParam(aMsg, aIter, &dir) || + !ReadParam(aMsg, aIter, &lang) || + !ReadParam(aMsg, aIter, &data) || + !ReadParam(aMsg, aIter, &principal) || + !ReadParam(aMsg, aIter, &inPrivateBrowsing)) { + + return false; + } + + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + if (NS_WARN_IF(!alert)) { + *aResult = nullptr; + return true; + } + nsresult rv = alert->Init(name, imageURL, title, text, textClickable, + cookie, dir, lang, data, principal, + inPrivateBrowsing); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aResult = nullptr; + return true; + } + alert.forget(aResult); + return true; + } +}; + +} // namespace IPC + +#endif /* mozilla_AlertNotificationIPCSerializer_h__ */ diff --git a/toolkit/components/alerts/moz.build b/toolkit/components/alerts/moz.build index ddcc6029126c..bf862d0901fc 100644 --- a/toolkit/components/alerts/moz.build +++ b/toolkit/components/alerts/moz.build @@ -16,7 +16,13 @@ EXPORTS += [ 'nsAlertsUtils.h', ] +EXPORTS.mozilla += [ + 'AlertNotification.h', + 'AlertNotificationIPCSerializer.h', +] + UNIFIED_SOURCES += [ + 'AlertNotification.cpp', 'nsAlertsService.cpp', 'nsAlertsUtils.cpp', 'nsXULAlerts.cpp', diff --git a/toolkit/components/alerts/nsAlertsService.cpp b/toolkit/components/alerts/nsAlertsService.cpp index b64fe48c9490..7506751ce22d 100644 --- a/toolkit/components/alerts/nsAlertsService.cpp +++ b/toolkit/components/alerts/nsAlertsService.cpp @@ -73,40 +73,66 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) { + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + + +NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification * aAlert, + nsIObserver * aAlertListener) +{ + NS_ENSURE_ARG(aAlert); + + nsAutoString cookie; + nsresult rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + if (XRE_IsContentProcess()) { ContentChild* cpc = ContentChild::GetSingleton(); if (aAlertListener) - cpc->AddRemoteAlertObserver(PromiseFlatString(aAlertCookie), aAlertListener); + cpc->AddRemoteAlertObserver(cookie, aAlertListener); - cpc->SendShowAlertNotification(PromiseFlatString(aImageUrl), - PromiseFlatString(aAlertTitle), - PromiseFlatString(aAlertText), - aAlertTextClickable, - PromiseFlatString(aAlertCookie), - PromiseFlatString(aAlertName), - PromiseFlatString(aBidi), - PromiseFlatString(aLang), - PromiseFlatString(aData), - IPC::Principal(aPrincipal), - aInPrivateBrowsing); + cpc->SendShowAlert(aAlert); return NS_OK; } + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal; + rv = aAlert->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + #ifdef MOZ_WIDGET_ANDROID - mozilla::AndroidBridge::Bridge()->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertCookie, - aAlertListener, aAlertName, aPrincipal); + mozilla::AndroidBridge::Bridge()->ShowAlertNotification(imageUrl, title, text, cookie, + aAlertListener, name, principal); return NS_OK; #else // Check if there is an optional service that handles system-level notifications nsCOMPtr sysAlerts(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID)); - nsresult rv; if (sysAlerts) { - rv = sysAlerts->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aAlertName, - aBidi, aLang, aData, - IPC::Principal(aPrincipal), - aInPrivateBrowsing); + rv = sysAlerts->ShowAlert(aAlert, aAlertListener); if (NS_SUCCEEDED(rv)) return NS_OK; } @@ -114,14 +140,30 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl if (!ShouldShowAlert()) { // Do not display the alert. Instead call alertfinished and get out. if (aAlertListener) - aAlertListener->Observe(nullptr, "alertfinished", PromiseFlatString(aAlertCookie).get()); + aAlertListener->Observe(nullptr, "alertfinished", cookie.get()); return NS_OK; } + bool textClickable; + rv = aAlert->GetTextClickable(&textClickable); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString bidi; + rv = aAlert->GetDir(bidi); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString lang; + rv = aAlert->GetLang(lang); + NS_ENSURE_SUCCESS(rv, rv); + + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + // Use XUL notifications as a fallback if above methods have failed. - rv = mXULAlerts.ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aAlertName, - aBidi, aLang, aPrincipal, aInPrivateBrowsing); + rv = mXULAlerts.ShowAlertNotification(imageUrl, title, text, textClickable, + cookie, aAlertListener, name, + bidi, lang, principal, inPrivateBrowsing); return rv; #endif // !MOZ_WIDGET_ANDROID } diff --git a/toolkit/components/alerts/nsIAlertsService.idl b/toolkit/components/alerts/nsIAlertsService.idl index 5ebc8586df91..ac703d019feb 100644 --- a/toolkit/components/alerts/nsIAlertsService.idl +++ b/toolkit/components/alerts/nsIAlertsService.idl @@ -8,9 +8,96 @@ interface nsIPrincipal; -[scriptable, uuid(9d0284bf-db40-42da-8f0d-c2769dbde7aa)] +%{C++ +#define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1" +%} + +[scriptable, uuid(b26b4a67-81b0-4270-8311-1e00a097ef92)] +interface nsIAlertNotification : nsISupports +{ + /** Initializes an alert notification. */ + void init([optional] in AString name, + [optional] in AString imageURL, + [optional] in AString title, + [optional] in AString text, + [optional] in boolean textClickable, + [optional] in AString cookie, + [optional] in AString dir, + [optional] in AString lang, + [optional] in AString data, + [optional] in nsIPrincipal principal, + [optional] in boolean inPrivateBrowsing); + + /** + * The name of the notification. This is currently only used on Android and + * OS X. On Android, the name is hashed and used as a notification ID. + * Notifications will replace previous notifications with the same name. + */ + readonly attribute AString name; + + /** + * A URL identifying the image to put in the alert. The OS X backend limits + * the amount of time it will wait for the image to load to six seconds. After + * that time, the alert will show without an image. + */ + readonly attribute AString imageURL; + + /** The title for the alert. */ + readonly attribute AString title; + + /** The contents of the alert. */ + readonly attribute AString text; + + /** + * Controls the click behavior. If true, the alert listener will be notified + * when the user clicks on the alert. + */ + readonly attribute boolean textClickable; + + /** + * An opaque cookie that will be passed to the alert listener for each + * callback. + */ + readonly attribute AString cookie; + + /** + * Bidi override for the title and contents. Valid values are "auto", "ltr", + * or "rtl". Ignored if the backend doesn't support localization. + */ + readonly attribute AString dir; + + /** + * Language of the title and text. Ignored if the backend doesn't support + * localization. + */ + readonly attribute AString lang; + + /** + * A Base64-encoded structured clone buffer containing data associated with + * this alert. Only used for web notifications. Chrome callers should use a + * cookie instead. + */ + readonly attribute AString data; + + /** + * The principal of the page that created the alert. Used for IPC security + * checks, and to determine whether the alert should show the source string + * and action buttons. + */ + readonly attribute nsIPrincipal principal; + + /** + * Controls the image loading behavior. If true, the image URL will be loaded + * in private browsing mode. + */ + readonly attribute boolean inPrivateBrowsing; +}; + +[scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)] interface nsIAlertsService : nsISupports { + void showAlert(in nsIAlertNotification alert, + [optional] in nsIObserver alertListener); /** * Displays a sliding notification window. * diff --git a/toolkit/components/build/nsToolkitCompsCID.h b/toolkit/components/build/nsToolkitCompsCID.h index 0f6d20d2f3a9..edfc26c09261 100644 --- a/toolkit/components/build/nsToolkitCompsCID.h +++ b/toolkit/components/build/nsToolkitCompsCID.h @@ -97,6 +97,9 @@ ///////////////////////////////////////////////////////////////////////////// +#define ALERT_NOTIFICATION_CID \ +{ 0x9a7b7a41, 0x0b47, 0x47f7, { 0xb6, 0x1b, 0x15, 0xa2, 0x10, 0xd6, 0xf0, 0x20 } } + // {A0CCAAF8-09DA-44D8-B250-9AC3E93C8117} #define NS_ALERTSSERVICE_CID \ { 0xa0ccaaf8, 0x9da, 0x44d8, { 0xb2, 0x50, 0x9a, 0xc3, 0xe9, 0x3c, 0x81, 0x17 } } diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index ecf2383ed971..20300c811b62 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -16,6 +16,7 @@ #include "nsParentalControlsService.h" #endif +#include "mozilla/AlertNotification.h" #include "nsAlertsService.h" #include "nsDownloadManager.h" @@ -82,6 +83,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService) NS_GENERIC_FACTORY_CONSTRUCTOR(nsParentalControlsService) #endif +NS_GENERIC_FACTORY_CONSTRUCTOR(AlertNotification) NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsService) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDownloadManager, @@ -138,6 +140,7 @@ NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID); #endif NS_DEFINE_NAMED_CID(NS_USERINFO_CID); +NS_DEFINE_NAMED_CID(ALERT_NOTIFICATION_CID); NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID); #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID); @@ -173,6 +176,7 @@ static const Module::CIDEntry kToolkitCIDs[] = { { &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor }, #endif // defined (MOZ_HAS_PERFSTATS) { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor }, + { &kALERT_NOTIFICATION_CID, false, nullptr, AlertNotificationConstructor }, { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor }, @@ -210,6 +214,7 @@ static const Module::ContractIDEntry kToolkitContracts[] = { { NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID }, #endif // defined (MOZ_HAS_PERFSTATS) { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID }, + { ALERT_NOTIFICATION_CONTRACTID, &kALERT_NOTIFICATION_CID }, { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) { NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID }, diff --git a/toolkit/system/gnome/nsAlertsIconListener.cpp b/toolkit/system/gnome/nsAlertsIconListener.cpp index 6f04e10e7f2c..97eca76347af 100644 --- a/toolkit/system/gnome/nsAlertsIconListener.cpp +++ b/toolkit/system/gnome/nsAlertsIconListener.cpp @@ -9,6 +9,7 @@ #include "imgIRequest.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" +#include "nsIAlertsService.h" #include "nsIImageToPixbuf.h" #include "nsIStringBundle.h" #include "nsIObserverService.h" @@ -186,6 +187,11 @@ nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) if (!mNotification) return NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr obsServ = + do_GetService("@mozilla.org/observer-service;1"); + if (obsServ) + obsServ->AddObserver(this, "quit-application", true); + if (aPixbuf) notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf); @@ -277,13 +283,8 @@ nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic, } nsresult -nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, - const nsAString & aAlertTitle, - const nsAString & aAlertText, - bool aAlertTextClickable, - const nsAString & aAlertCookie, - nsIObserver * aAlertListener, - bool aInPrivateBrowsing) +nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { if (!libNotifyHandle) return NS_ERROR_FAILURE; @@ -335,27 +336,38 @@ nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, return NS_ERROR_FAILURE; } - if (!gHasActions && aAlertTextClickable) + nsresult rv = aAlert->GetTextClickable(&mAlertHasAction); + NS_ENSURE_SUCCESS(rv, rv); + if (!gHasActions && mAlertHasAction) return NS_ERROR_FAILURE; // No good, fallback to XUL - nsCOMPtr obsServ = - do_GetService("@mozilla.org/observer-service;1"); - if (obsServ) - obsServ->AddObserver(this, "quit-application", true); - + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); // Workaround for a libnotify bug - blank titles aren't dealt with // properly so we use a space - if (aAlertTitle.IsEmpty()) { + if (title.IsEmpty()) { mAlertTitle = NS_LITERAL_CSTRING(" "); } else { - mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle); + mAlertTitle = NS_ConvertUTF16toUTF8(title); } - mAlertText = NS_ConvertUTF16toUTF8(aAlertText); - mAlertHasAction = aAlertTextClickable; + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + mAlertText = NS_ConvertUTF16toUTF8(text); mAlertListener = aAlertListener; - mAlertCookie = aAlertCookie; - return StartRequest(aImageUrl, aInPrivateBrowsing); + rv = aAlert->GetCookie(mAlertCookie); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + + return StartRequest(imageUrl, inPrivateBrowsing); } diff --git a/toolkit/system/gnome/nsAlertsIconListener.h b/toolkit/system/gnome/nsAlertsIconListener.h index 296a50ac230d..8848f0da0f7e 100644 --- a/toolkit/system/gnome/nsAlertsIconListener.h +++ b/toolkit/system/gnome/nsAlertsIconListener.h @@ -15,6 +15,7 @@ #include class imgIRequest; +class nsIAlertNotification; struct NotifyNotification; @@ -29,13 +30,8 @@ public: nsAlertsIconListener(); - nsresult InitAlertAsync(const nsAString & aImageUrl, - const nsAString & aAlertTitle, - const nsAString & aAlertText, - bool aAlertTextClickable, - const nsAString & aAlertCookie, - nsIObserver * aAlertListener, - bool aInPrivateBrowsing); + nsresult InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener); void SendCallback(); void SendClosed(); diff --git a/toolkit/system/gnome/nsSystemAlertsService.cpp b/toolkit/system/gnome/nsSystemAlertsService.cpp index fe7eca54159f..8b0bf6856fe2 100644 --- a/toolkit/system/gnome/nsSystemAlertsService.cpp +++ b/toolkit/system/gnome/nsSystemAlertsService.cpp @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsComponentManagerUtils.h" #include "nsXULAppAPI.h" #include "nsSystemAlertsService.h" #include "nsAlertsIconListener.h" @@ -40,12 +41,27 @@ NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification(const nsAString & aIm nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) { + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) +{ + NS_ENSURE_ARG(aAlert); + RefPtr alertListener = new nsAlertsIconListener(); if (!alertListener) return NS_ERROR_OUT_OF_MEMORY; - return alertListener->InitAlertAsync(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aInPrivateBrowsing); + return alertListener->InitAlertAsync(aAlert, aAlertListener); } NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName, diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm index 39d03da51d78..2657b7a1b8ef 100644 --- a/widget/cocoa/OSXNotificationCenter.mm +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -11,6 +11,7 @@ #include "nsNetUtil.h" #include "imgLoader.h" #import "nsCocoaUtils.h" +#include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsObjCExceptions.h" #include "nsString.h" @@ -240,15 +241,39 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const const nsAString & aData, nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) +{ + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP +OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + NS_ENSURE_ARG(aAlert); + Class unClass = NSClassFromString(@"NSUserNotification"); id notification = [[unClass alloc] init]; - notification.title = nsCocoaUtils::ToNSString(aAlertTitle); + nsAutoString title; + nsresult rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + notification.title = nsCocoaUtils::ToNSString(title); + + nsCOMPtr principal; + rv = aAlert->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); nsAutoString hostPort; - nsAlertsUtils::GetSourceHostPort(aPrincipal, hostPort); + nsAlertsUtils::GetSourceHostPort(principal, hostPort); nsCOMPtr bundle; nsCOMPtr sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID); sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle)); @@ -263,12 +288,16 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const notification.subtitle = nsCocoaUtils::ToNSString(notificationSource); } - notification.informativeText = nsCocoaUtils::ToNSString(aAlertText); + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + notification.informativeText = nsCocoaUtils::ToNSString(text); + notification.soundName = NSUserNotificationDefaultSoundName; notification.hasActionButton = NO; // If this is not an application/extension alert, show additional actions dealing with permissions. - if (nsAlertsUtils::IsActionablePrincipal(aPrincipal)) { + if (nsAlertsUtils::IsActionablePrincipal(principal)) { if (bundle) { nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle; bundle->GetStringFromName(MOZ_UTF16("closeButton.title"), @@ -307,24 +336,39 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const } } } - NSString *alertName = nsCocoaUtils::ToNSString(aAlertName); + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + NSString *alertName = nsCocoaUtils::ToNSString(name); if (!alertName) { return NS_ERROR_FAILURE; } notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil] forKeys:[NSArray arrayWithObjects:@"name", nil]]; - OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie); + nsAutoString cookie; + rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie); + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); // Show the notification without waiting for an image if there is no icon URL or // notification icons are not supported on this version of OS X. - if (aImageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) { + if (imageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) { CloseAlertCocoaString(alertName); mActiveAlerts.AppendElement(osxni); [GetNotificationCenter() deliverNotification:notification]; [notification release]; if (aAlertListener) { - aAlertListener->Observe(nullptr, "alertshow", PromiseFlatString(aAlertCookie).get()); + aAlertListener->Observe(nullptr, "alertshow", cookie.get()); } } else { mPendingAlerts.AppendElement(osxni); @@ -332,17 +376,17 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const RefPtr il = imgLoader::GetInstance(); if (il) { nsCOMPtr imageUri; - NS_NewURI(getter_AddRefs(imageUri), aImageUrl); + NS_NewURI(getter_AddRefs(imageUri), imageUrl); if (imageUri) { - nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, - mozilla::net::RP_Default, - aPrincipal, nullptr, - this, nullptr, - aInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS : - nsIRequest::LOAD_NORMAL, - nullptr, nsIContentPolicy::TYPE_INTERNAL_IMAGE, - EmptyString(), - getter_AddRefs(osxni->mIconRequest)); + rv = il->LoadImage(imageUri, nullptr, nullptr, + mozilla::net::RP_Default, + principal, nullptr, + this, nullptr, + inPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS : + nsIRequest::LOAD_NORMAL, + nullptr, nsIContentPolicy::TYPE_INTERNAL_IMAGE, + EmptyString(), + getter_AddRefs(osxni->mIconRequest)); if (NS_SUCCEEDED(rv)) { // Set a timer for six seconds. If we don't have an icon by the time this // goes off then we go ahead without an icon.