Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const Cc = Components.classes; |
michael@0 | 6 | const Ci = Components.interfaces; |
michael@0 | 7 | const Cu = Components.utils; |
michael@0 | 8 | |
michael@0 | 9 | Cu.import("resource://webapprt/modules/WebappRT.jsm"); |
michael@0 | 10 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 11 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 12 | |
michael@0 | 13 | XPCOMUtils.defineLazyGetter(this, "gAppBrowser", |
michael@0 | 14 | function() document.getElementById("content")); |
michael@0 | 15 | |
michael@0 | 16 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 17 | XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", |
michael@0 | 18 | "@mozilla.org/toolkit/crash-reporter;1", |
michael@0 | 19 | "nsICrashReporter"); |
michael@0 | 20 | #endif |
michael@0 | 21 | |
michael@0 | 22 | function isSameOrigin(url) { |
michael@0 | 23 | let origin = Services.io.newURI(url, null, null).prePath; |
michael@0 | 24 | return (origin == WebappRT.config.app.origin); |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | let progressListener = { |
michael@0 | 28 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, |
michael@0 | 29 | Ci.nsISupportsWeakReference]), |
michael@0 | 30 | onLocationChange: function onLocationChange(progress, request, location, |
michael@0 | 31 | flags) { |
michael@0 | 32 | |
michael@0 | 33 | // Close tooltip (code adapted from /browser/base/content/browser.js) |
michael@0 | 34 | let pageTooltip = document.getElementById("contentAreaTooltip"); |
michael@0 | 35 | let tooltipNode = pageTooltip.triggerNode; |
michael@0 | 36 | if (tooltipNode) { |
michael@0 | 37 | // Optimise for the common case |
michael@0 | 38 | if (progress.isTopLevel) { |
michael@0 | 39 | pageTooltip.hidePopup(); |
michael@0 | 40 | } |
michael@0 | 41 | else { |
michael@0 | 42 | for (let tooltipWindow = tooltipNode.ownerDocument.defaultView; |
michael@0 | 43 | tooltipWindow != tooltipWindow.parent; |
michael@0 | 44 | tooltipWindow = tooltipWindow.parent) { |
michael@0 | 45 | if (tooltipWindow == progress.DOMWindow) { |
michael@0 | 46 | pageTooltip.hidePopup(); |
michael@0 | 47 | break; |
michael@0 | 48 | } |
michael@0 | 49 | } |
michael@0 | 50 | } |
michael@0 | 51 | } |
michael@0 | 52 | |
michael@0 | 53 | // Set the title of the window to the name of the webapp, adding the origin |
michael@0 | 54 | // of the page being loaded if it's from a different origin than the app |
michael@0 | 55 | // (per security bug 741955, which specifies that other-origin pages loaded |
michael@0 | 56 | // in runtime windows must be identified in chrome). |
michael@0 | 57 | let title = WebappRT.config.app.manifest.name; |
michael@0 | 58 | if (!isSameOrigin(location.spec)) { |
michael@0 | 59 | title = location.prePath + " - " + title; |
michael@0 | 60 | } |
michael@0 | 61 | document.documentElement.setAttribute("title", title); |
michael@0 | 62 | }, |
michael@0 | 63 | |
michael@0 | 64 | onStateChange: function onStateChange(aProgress, aRequest, aFlags, aStatus) { |
michael@0 | 65 | if (aRequest instanceof Ci.nsIChannel && |
michael@0 | 66 | aFlags & Ci.nsIWebProgressListener.STATE_START && |
michael@0 | 67 | aFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) { |
michael@0 | 68 | updateCrashReportURL(aRequest.URI); |
michael@0 | 69 | } |
michael@0 | 70 | } |
michael@0 | 71 | }; |
michael@0 | 72 | |
michael@0 | 73 | function onOpenWindow(event) { |
michael@0 | 74 | let name = event.detail.name; |
michael@0 | 75 | |
michael@0 | 76 | if (name == "_blank") { |
michael@0 | 77 | let uri = Services.io.newURI(event.detail.url, null, null); |
michael@0 | 78 | |
michael@0 | 79 | // Direct the URL to the browser. |
michael@0 | 80 | Cc["@mozilla.org/uriloader/external-protocol-service;1"]. |
michael@0 | 81 | getService(Ci.nsIExternalProtocolService). |
michael@0 | 82 | getProtocolHandlerInfo(uri.scheme). |
michael@0 | 83 | launchWithURI(uri); |
michael@0 | 84 | } else { |
michael@0 | 85 | let win = window.openDialog("chrome://webapprt/content/webapp.xul", |
michael@0 | 86 | name, |
michael@0 | 87 | "chrome,dialog=no,resizable," + event.detail.features); |
michael@0 | 88 | |
michael@0 | 89 | win.addEventListener("load", function onLoad() { |
michael@0 | 90 | win.removeEventListener("load", onLoad, false); |
michael@0 | 91 | |
michael@0 | 92 | #ifndef XP_WIN |
michael@0 | 93 | #ifndef XP_MACOSX |
michael@0 | 94 | if (isSameOrigin(event.detail.url)) { |
michael@0 | 95 | // On non-Windows platforms, we open new windows in fullscreen mode |
michael@0 | 96 | // if the opener window is in fullscreen mode, so we hide the menubar; |
michael@0 | 97 | // but on Mac we don't need to hide the menubar. |
michael@0 | 98 | if (document.mozFullScreenElement) { |
michael@0 | 99 | win.document.getElementById("main-menubar").style.display = "none"; |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | #endif |
michael@0 | 103 | #endif |
michael@0 | 104 | |
michael@0 | 105 | win.document.getElementById("content").docShell.setIsApp(WebappRT.appID); |
michael@0 | 106 | win.document.getElementById("content").setAttribute("src", event.detail.url); |
michael@0 | 107 | }, false); |
michael@0 | 108 | } |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | function onLoad() { |
michael@0 | 112 | window.removeEventListener("load", onLoad, false); |
michael@0 | 113 | |
michael@0 | 114 | gAppBrowser.addProgressListener(progressListener, |
michael@0 | 115 | Ci.nsIWebProgress.NOTIFY_LOCATION | |
michael@0 | 116 | Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); |
michael@0 | 117 | |
michael@0 | 118 | updateMenuItems(); |
michael@0 | 119 | |
michael@0 | 120 | gAppBrowser.addEventListener("mozbrowseropenwindow", onOpenWindow); |
michael@0 | 121 | } |
michael@0 | 122 | window.addEventListener("load", onLoad, false); |
michael@0 | 123 | |
michael@0 | 124 | function onUnload() { |
michael@0 | 125 | gAppBrowser.removeProgressListener(progressListener); |
michael@0 | 126 | gAppBrowser.removeEventListener("mozbrowseropenwindow", onOpenWindow); |
michael@0 | 127 | } |
michael@0 | 128 | window.addEventListener("unload", onUnload, false); |
michael@0 | 129 | |
michael@0 | 130 | // Fullscreen handling. |
michael@0 | 131 | |
michael@0 | 132 | #ifndef XP_MACOSX |
michael@0 | 133 | document.addEventListener('mozfullscreenchange', function() { |
michael@0 | 134 | if (document.mozFullScreenElement) { |
michael@0 | 135 | document.getElementById("main-menubar").style.display = "none"; |
michael@0 | 136 | } else { |
michael@0 | 137 | document.getElementById("main-menubar").style.display = ""; |
michael@0 | 138 | } |
michael@0 | 139 | }, false); |
michael@0 | 140 | #endif |
michael@0 | 141 | |
michael@0 | 142 | // On Mac, we dynamically create the label for the Quit menuitem, using |
michael@0 | 143 | // a string property to inject the name of the webapp into it. |
michael@0 | 144 | function updateMenuItems() { |
michael@0 | 145 | #ifdef XP_MACOSX |
michael@0 | 146 | let installRecord = WebappRT.config.app; |
michael@0 | 147 | let manifest = WebappRT.config.app.manifest; |
michael@0 | 148 | let bundle = |
michael@0 | 149 | Services.strings.createBundle("chrome://webapprt/locale/webapp.properties"); |
michael@0 | 150 | let quitLabel = bundle.formatStringFromName("quitApplicationCmdMac.label", |
michael@0 | 151 | [manifest.name], 1); |
michael@0 | 152 | let hideLabel = bundle.formatStringFromName("hideApplicationCmdMac.label", |
michael@0 | 153 | [manifest.name], 1); |
michael@0 | 154 | document.getElementById("menu_FileQuitItem").setAttribute("label", quitLabel); |
michael@0 | 155 | document.getElementById("menu_mac_hide_app").setAttribute("label", hideLabel); |
michael@0 | 156 | #endif |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | #ifndef XP_MACOSX |
michael@0 | 160 | let gEditUIVisible = true; |
michael@0 | 161 | #endif |
michael@0 | 162 | |
michael@0 | 163 | function updateEditUIVisibility() { |
michael@0 | 164 | #ifndef XP_MACOSX |
michael@0 | 165 | let editMenuPopupState = document.getElementById("menu_EditPopup").state; |
michael@0 | 166 | let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state; |
michael@0 | 167 | |
michael@0 | 168 | // The UI is visible if the Edit menu is opening or open, if the context menu |
michael@0 | 169 | // is open, or if the toolbar has been customized to include the Cut, Copy, |
michael@0 | 170 | // or Paste toolbar buttons. |
michael@0 | 171 | gEditUIVisible = editMenuPopupState == "showing" || |
michael@0 | 172 | editMenuPopupState == "open" || |
michael@0 | 173 | contextMenuPopupState == "showing" || |
michael@0 | 174 | contextMenuPopupState == "open"; |
michael@0 | 175 | |
michael@0 | 176 | // If UI is visible, update the edit commands' enabled state to reflect |
michael@0 | 177 | // whether or not they are actually enabled for the current focus/selection. |
michael@0 | 178 | if (gEditUIVisible) { |
michael@0 | 179 | goUpdateGlobalEditMenuItems(); |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | // Otherwise, enable all commands, so that keyboard shortcuts still work, |
michael@0 | 183 | // then lazily determine their actual enabled state when the user presses |
michael@0 | 184 | // a keyboard shortcut. |
michael@0 | 185 | else { |
michael@0 | 186 | goSetCommandEnabled("cmd_undo", true); |
michael@0 | 187 | goSetCommandEnabled("cmd_redo", true); |
michael@0 | 188 | goSetCommandEnabled("cmd_cut", true); |
michael@0 | 189 | goSetCommandEnabled("cmd_copy", true); |
michael@0 | 190 | goSetCommandEnabled("cmd_paste", true); |
michael@0 | 191 | goSetCommandEnabled("cmd_selectAll", true); |
michael@0 | 192 | goSetCommandEnabled("cmd_delete", true); |
michael@0 | 193 | goSetCommandEnabled("cmd_switchTextDirection", true); |
michael@0 | 194 | } |
michael@0 | 195 | #endif |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | function updateCrashReportURL(aURI) { |
michael@0 | 199 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 200 | if (!gCrashReporter.enabled) |
michael@0 | 201 | return; |
michael@0 | 202 | |
michael@0 | 203 | let uri = aURI.clone(); |
michael@0 | 204 | // uri.userPass throws on protocols without the concept of authentication, |
michael@0 | 205 | // like about:, which tests can load, so we catch and ignore an exception. |
michael@0 | 206 | try { |
michael@0 | 207 | if (uri.userPass != "") { |
michael@0 | 208 | uri.userPass = ""; |
michael@0 | 209 | } |
michael@0 | 210 | } catch (e) {} |
michael@0 | 211 | |
michael@0 | 212 | gCrashReporter.annotateCrashReport("URL", uri.spec); |
michael@0 | 213 | #endif |
michael@0 | 214 | } |
michael@0 | 215 | |
michael@0 | 216 | // Context menu handling code. |
michael@0 | 217 | // At the moment there isn't any built-in menu, we only support HTML5 custom |
michael@0 | 218 | // menus. |
michael@0 | 219 | |
michael@0 | 220 | let gContextMenu = null; |
michael@0 | 221 | |
michael@0 | 222 | XPCOMUtils.defineLazyGetter(this, "PageMenu", function() { |
michael@0 | 223 | let tmp = {}; |
michael@0 | 224 | Cu.import("resource://gre/modules/PageMenu.jsm", tmp); |
michael@0 | 225 | return new tmp.PageMenu(); |
michael@0 | 226 | }); |
michael@0 | 227 | |
michael@0 | 228 | function showContextMenu(aEvent, aXULMenu) { |
michael@0 | 229 | if (aEvent.target != aXULMenu) { |
michael@0 | 230 | return true; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | gContextMenu = new nsContextMenu(aXULMenu); |
michael@0 | 234 | if (gContextMenu.shouldDisplay) { |
michael@0 | 235 | updateEditUIVisibility(); |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | return gContextMenu.shouldDisplay; |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | function hideContextMenu(aEvent, aXULMenu) { |
michael@0 | 242 | if (aEvent.target != aXULMenu) { |
michael@0 | 243 | return; |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | gContextMenu = null; |
michael@0 | 247 | |
michael@0 | 248 | updateEditUIVisibility(); |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | function nsContextMenu(aXULMenu) { |
michael@0 | 252 | this.initMenu(aXULMenu); |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | nsContextMenu.prototype = { |
michael@0 | 256 | initMenu: function(aXULMenu) { |
michael@0 | 257 | this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(document.popupNode, |
michael@0 | 258 | aXULMenu); |
michael@0 | 259 | this.shouldDisplay = this.hasPageMenu; |
michael@0 | 260 | }, |
michael@0 | 261 | }; |