Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | let Cc = Components.classes; |
michael@0 | 7 | let Ci = Components.interfaces; |
michael@0 | 8 | let Cu = Components.utils; |
michael@0 | 9 | let Cr = Components.results; |
michael@0 | 10 | |
michael@0 | 11 | Cu.import("resource://gre/modules/PageThumbs.jsm"); |
michael@0 | 12 | |
michael@0 | 13 | // Page for which the start UI is shown |
michael@0 | 14 | const kStartURI = "about:newtab"; |
michael@0 | 15 | |
michael@0 | 16 | // allow panning after this timeout on pages with registered touch listeners |
michael@0 | 17 | const kTouchTimeout = 300; |
michael@0 | 18 | const kSetInactiveStateTimeout = 100; |
michael@0 | 19 | |
michael@0 | 20 | const kTabThumbnailDelayCapture = 500; |
michael@0 | 21 | |
michael@0 | 22 | const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
michael@0 | 23 | |
michael@0 | 24 | // See grid.xml, we use this to cache style info across loads of the startui. |
michael@0 | 25 | var _richgridTileSizes = {}; |
michael@0 | 26 | |
michael@0 | 27 | // Override sizeToContent in the main window. It breaks things (bug 565887) |
michael@0 | 28 | window.sizeToContent = function() { |
michael@0 | 29 | Cu.reportError("window.sizeToContent is not allowed in this window"); |
michael@0 | 30 | } |
michael@0 | 31 | |
michael@0 | 32 | function getTabModalPromptBox(aWindow) { |
michael@0 | 33 | let browser = Browser.getBrowserForWindow(aWindow); |
michael@0 | 34 | return Browser.getTabModalPromptBox(browser); |
michael@0 | 35 | } |
michael@0 | 36 | |
michael@0 | 37 | /* |
michael@0 | 38 | * Returns the browser for the currently displayed tab. |
michael@0 | 39 | */ |
michael@0 | 40 | function getBrowser() { |
michael@0 | 41 | return Browser.selectedBrowser; |
michael@0 | 42 | } |
michael@0 | 43 | |
michael@0 | 44 | var Browser = { |
michael@0 | 45 | _debugEvents: false, |
michael@0 | 46 | _tabs: [], |
michael@0 | 47 | _selectedTab: null, |
michael@0 | 48 | _tabId: 0, |
michael@0 | 49 | windowUtils: window.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 50 | .getInterface(Ci.nsIDOMWindowUtils), |
michael@0 | 51 | |
michael@0 | 52 | get defaultBrowserWidth() { |
michael@0 | 53 | return window.innerWidth; |
michael@0 | 54 | }, |
michael@0 | 55 | |
michael@0 | 56 | startup: function startup() { |
michael@0 | 57 | var self = this; |
michael@0 | 58 | |
michael@0 | 59 | try { |
michael@0 | 60 | messageManager.loadFrameScript("chrome://browser/content/Util.js", true); |
michael@0 | 61 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/Content.js", true); |
michael@0 | 62 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FormHelper.js", true); |
michael@0 | 63 | messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true); |
michael@0 | 64 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true); |
michael@0 | 65 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true); |
michael@0 | 66 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true); |
michael@0 | 67 | messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginHelper.js", true); |
michael@0 | 68 | } catch (e) { |
michael@0 | 69 | // XXX whatever is calling startup needs to dump errors! |
michael@0 | 70 | dump("###########" + e + "\n"); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | if (!Services.metro) { |
michael@0 | 74 | // Services.metro is only available on Windows Metro. We want to be able |
michael@0 | 75 | // to test metro on other platforms, too, so we provide a minimal shim. |
michael@0 | 76 | Services.metro = { |
michael@0 | 77 | activationURI: "", |
michael@0 | 78 | pinTileAsync: function () {}, |
michael@0 | 79 | unpinTileAsync: function () {} |
michael@0 | 80 | }; |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | /* handles dispatching clicks on browser into clicks in content or zooms */ |
michael@0 | 84 | Elements.browsers.customDragger = new Browser.MainDragger(); |
michael@0 | 85 | |
michael@0 | 86 | /* handles web progress management for open browsers */ |
michael@0 | 87 | Elements.browsers.webProgress = WebProgress.init(); |
michael@0 | 88 | |
michael@0 | 89 | // Call InputSourceHelper first so global listeners get called before |
michael@0 | 90 | // we start processing input in TouchModule. |
michael@0 | 91 | InputSourceHelper.init(); |
michael@0 | 92 | ClickEventHandler.init(); |
michael@0 | 93 | |
michael@0 | 94 | TouchModule.init(); |
michael@0 | 95 | GestureModule.init(); |
michael@0 | 96 | BrowserTouchHandler.init(); |
michael@0 | 97 | PopupBlockerObserver.init(); |
michael@0 | 98 | APZCObserver.init(); |
michael@0 | 99 | |
michael@0 | 100 | // Init the touch scrollbox |
michael@0 | 101 | this.contentScrollbox = Elements.browsers; |
michael@0 | 102 | this.contentScrollboxScroller = { |
michael@0 | 103 | scrollBy: function(aDx, aDy) { |
michael@0 | 104 | let view = getBrowser().getRootView(); |
michael@0 | 105 | view.scrollBy(aDx, aDy); |
michael@0 | 106 | }, |
michael@0 | 107 | |
michael@0 | 108 | scrollTo: function(aX, aY) { |
michael@0 | 109 | let view = getBrowser().getRootView(); |
michael@0 | 110 | view.scrollTo(aX, aY); |
michael@0 | 111 | }, |
michael@0 | 112 | |
michael@0 | 113 | getPosition: function(aScrollX, aScrollY) { |
michael@0 | 114 | let view = getBrowser().getRootView(); |
michael@0 | 115 | let scroll = view.getPosition(); |
michael@0 | 116 | aScrollX.value = scroll.x; |
michael@0 | 117 | aScrollY.value = scroll.y; |
michael@0 | 118 | } |
michael@0 | 119 | }; |
michael@0 | 120 | |
michael@0 | 121 | ContentAreaObserver.init(); |
michael@0 | 122 | |
michael@0 | 123 | function fullscreenHandler() { |
michael@0 | 124 | if (Browser.selectedBrowser.contentWindow.document.mozFullScreenElement) |
michael@0 | 125 | Elements.stack.setAttribute("fullscreen", "true"); |
michael@0 | 126 | else |
michael@0 | 127 | Elements.stack.removeAttribute("fullscreen"); |
michael@0 | 128 | } |
michael@0 | 129 | window.addEventListener("mozfullscreenchange", fullscreenHandler, true); |
michael@0 | 130 | |
michael@0 | 131 | BrowserUI.init(); |
michael@0 | 132 | |
michael@0 | 133 | window.controllers.appendController(this); |
michael@0 | 134 | window.controllers.appendController(BrowserUI); |
michael@0 | 135 | |
michael@0 | 136 | Services.obs.addObserver(SessionHistoryObserver, "browser:purge-session-history", false); |
michael@0 | 137 | |
michael@0 | 138 | window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); |
michael@0 | 139 | |
michael@0 | 140 | Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false); |
michael@0 | 141 | |
michael@0 | 142 | // Make sure we're online before attempting to load |
michael@0 | 143 | Util.forceOnline(); |
michael@0 | 144 | |
michael@0 | 145 | // If this is an intial window launch the commandline handler passes us the default |
michael@0 | 146 | // page as an argument. |
michael@0 | 147 | let commandURL = null; |
michael@0 | 148 | try { |
michael@0 | 149 | let argsObj = window.arguments[0].wrappedJSObject; |
michael@0 | 150 | if (argsObj && argsObj.pageloadURL) { |
michael@0 | 151 | // Talos tp-cmdline parameter |
michael@0 | 152 | commandURL = argsObj.pageloadURL; |
michael@0 | 153 | } else if (window.arguments && window.arguments[0]) { |
michael@0 | 154 | // BrowserCLH paramerter |
michael@0 | 155 | commandURL = window.arguments[0]; |
michael@0 | 156 | } |
michael@0 | 157 | } catch (ex) { |
michael@0 | 158 | Util.dumpLn(ex); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | messageManager.addMessageListener("DOMLinkAdded", this); |
michael@0 | 162 | messageManager.addMessageListener("Browser:FormSubmit", this); |
michael@0 | 163 | messageManager.addMessageListener("Browser:CanUnload:Return", this); |
michael@0 | 164 | messageManager.addMessageListener("scroll", this); |
michael@0 | 165 | messageManager.addMessageListener("Browser:CertException", this); |
michael@0 | 166 | messageManager.addMessageListener("Browser:BlockedSite", this); |
michael@0 | 167 | |
michael@0 | 168 | Task.spawn(function() { |
michael@0 | 169 | // Activation URIs come from protocol activations, secondary tiles, and file activations |
michael@0 | 170 | let activationURI = yield this.getShortcutOrURI(Services.metro.activationURI); |
michael@0 | 171 | |
michael@0 | 172 | let self = this; |
michael@0 | 173 | function loadStartupURI() { |
michael@0 | 174 | if (activationURI) { |
michael@0 | 175 | let webNav = Ci.nsIWebNavigation; |
michael@0 | 176 | let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | |
michael@0 | 177 | webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; |
michael@0 | 178 | self.addTab(activationURI, true, null, { flags: flags }); |
michael@0 | 179 | } else { |
michael@0 | 180 | let uri = commandURL || Browser.getHomePage(); |
michael@0 | 181 | self.addTab(uri, true); |
michael@0 | 182 | } |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | // Should we restore the previous session (crash or some other event) |
michael@0 | 186 | let ss = Cc["@mozilla.org/browser/sessionstore;1"] |
michael@0 | 187 | .getService(Ci.nsISessionStore); |
michael@0 | 188 | let shouldRestore = ss.shouldRestore(); |
michael@0 | 189 | if (shouldRestore) { |
michael@0 | 190 | let bringFront = false; |
michael@0 | 191 | // First open any commandline URLs, except the homepage |
michael@0 | 192 | if (activationURI && activationURI != kStartURI) { |
michael@0 | 193 | this.addTab(activationURI, true, null, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP }); |
michael@0 | 194 | } else if (commandURL && commandURL != kStartURI) { |
michael@0 | 195 | this.addTab(commandURL, true); |
michael@0 | 196 | } else { |
michael@0 | 197 | bringFront = true; |
michael@0 | 198 | // Initial window resizes call functions that assume a tab is in the tab list |
michael@0 | 199 | // and restored tabs are added too late. We add a dummy to to satisfy the resize |
michael@0 | 200 | // code and then remove the dummy after the session has been restored. |
michael@0 | 201 | let dummy = this.addTab("about:blank", true); |
michael@0 | 202 | let dummyCleanup = { |
michael@0 | 203 | observe: function(aSubject, aTopic, aData) { |
michael@0 | 204 | Services.obs.removeObserver(dummyCleanup, "sessionstore-windows-restored"); |
michael@0 | 205 | if (aData == "fail") |
michael@0 | 206 | loadStartupURI(); |
michael@0 | 207 | dummy.chromeTab.ignoreUndo = true; |
michael@0 | 208 | Browser.closeTab(dummy, { forceClose: true }); |
michael@0 | 209 | } |
michael@0 | 210 | }; |
michael@0 | 211 | Services.obs.addObserver(dummyCleanup, "sessionstore-windows-restored", false); |
michael@0 | 212 | } |
michael@0 | 213 | ss.restoreLastSession(bringFront); |
michael@0 | 214 | } else { |
michael@0 | 215 | loadStartupURI(); |
michael@0 | 216 | } |
michael@0 | 217 | |
michael@0 | 218 | // Notify about our input type |
michael@0 | 219 | InputSourceHelper.fireUpdate(); |
michael@0 | 220 | |
michael@0 | 221 | // Broadcast a UIReady message so add-ons know we are finished with startup |
michael@0 | 222 | let event = document.createEvent("Events"); |
michael@0 | 223 | event.initEvent("UIReady", true, false); |
michael@0 | 224 | window.dispatchEvent(event); |
michael@0 | 225 | }.bind(this)); |
michael@0 | 226 | }, |
michael@0 | 227 | |
michael@0 | 228 | shutdown: function shutdown() { |
michael@0 | 229 | APZCObserver.shutdown(); |
michael@0 | 230 | BrowserUI.uninit(); |
michael@0 | 231 | ClickEventHandler.uninit(); |
michael@0 | 232 | ContentAreaObserver.shutdown(); |
michael@0 | 233 | Appbar.shutdown(); |
michael@0 | 234 | |
michael@0 | 235 | messageManager.removeMessageListener("Browser:FormSubmit", this); |
michael@0 | 236 | messageManager.removeMessageListener("scroll", this); |
michael@0 | 237 | messageManager.removeMessageListener("Browser:CertException", this); |
michael@0 | 238 | messageManager.removeMessageListener("Browser:BlockedSite", this); |
michael@0 | 239 | |
michael@0 | 240 | Services.obs.removeObserver(SessionHistoryObserver, "browser:purge-session-history"); |
michael@0 | 241 | |
michael@0 | 242 | window.controllers.removeController(this); |
michael@0 | 243 | window.controllers.removeController(BrowserUI); |
michael@0 | 244 | }, |
michael@0 | 245 | |
michael@0 | 246 | getHomePage: function getHomePage(aOptions) { |
michael@0 | 247 | aOptions = aOptions || { useDefault: false }; |
michael@0 | 248 | |
michael@0 | 249 | let url = kStartURI; |
michael@0 | 250 | try { |
michael@0 | 251 | let prefs = aOptions.useDefault ? Services.prefs.getDefaultBranch(null) : Services.prefs; |
michael@0 | 252 | url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data; |
michael@0 | 253 | } |
michael@0 | 254 | catch(e) { } |
michael@0 | 255 | |
michael@0 | 256 | return url; |
michael@0 | 257 | }, |
michael@0 | 258 | |
michael@0 | 259 | get browsers() { |
michael@0 | 260 | return this._tabs.map(function(tab) { return tab.browser; }); |
michael@0 | 261 | }, |
michael@0 | 262 | |
michael@0 | 263 | /** |
michael@0 | 264 | * Load a URI in the current tab, or a new tab if necessary. |
michael@0 | 265 | * @param aURI String |
michael@0 | 266 | * @param aParams Object with optional properties that will be passed to loadURIWithFlags: |
michael@0 | 267 | * flags, referrerURI, charset, postData. |
michael@0 | 268 | */ |
michael@0 | 269 | loadURI: function loadURI(aURI, aParams) { |
michael@0 | 270 | let browser = this.selectedBrowser; |
michael@0 | 271 | |
michael@0 | 272 | // We need to keep about: pages opening in new "local" tabs. We also want to spawn |
michael@0 | 273 | // new "remote" tabs if opening web pages from a "local" about: page. |
michael@0 | 274 | dump("loadURI=" + aURI + "\ncurrentURI=" + browser.currentURI.spec + "\n"); |
michael@0 | 275 | |
michael@0 | 276 | let params = aParams || {}; |
michael@0 | 277 | try { |
michael@0 | 278 | let flags = params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; |
michael@0 | 279 | let postData = ("postData" in params && params.postData) ? params.postData.value : null; |
michael@0 | 280 | let referrerURI = "referrerURI" in params ? params.referrerURI : null; |
michael@0 | 281 | let charset = "charset" in params ? params.charset : null; |
michael@0 | 282 | dump("loading tab: " + aURI + "\n"); |
michael@0 | 283 | browser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData); |
michael@0 | 284 | } catch(e) { |
michael@0 | 285 | dump("Error: " + e + "\n"); |
michael@0 | 286 | } |
michael@0 | 287 | }, |
michael@0 | 288 | |
michael@0 | 289 | /** |
michael@0 | 290 | * Determine if the given URL is a shortcut/keyword and, if so, expand it |
michael@0 | 291 | * @param aURL String |
michael@0 | 292 | * @param aPostDataRef Out param contains any required post data for a search |
michael@0 | 293 | * @return {Promise} |
michael@0 | 294 | * @result the expanded shortcut, or the original URL if not a shortcut |
michael@0 | 295 | */ |
michael@0 | 296 | getShortcutOrURI: function getShortcutOrURI(aURL, aPostDataRef) { |
michael@0 | 297 | return Task.spawn(function() { |
michael@0 | 298 | if (!aURL) |
michael@0 | 299 | throw new Task.Result(aURL); |
michael@0 | 300 | |
michael@0 | 301 | let shortcutURL = null; |
michael@0 | 302 | let keyword = aURL; |
michael@0 | 303 | let param = ""; |
michael@0 | 304 | |
michael@0 | 305 | let offset = aURL.indexOf(" "); |
michael@0 | 306 | if (offset > 0) { |
michael@0 | 307 | keyword = aURL.substr(0, offset); |
michael@0 | 308 | param = aURL.substr(offset + 1); |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | if (!aPostDataRef) |
michael@0 | 312 | aPostDataRef = {}; |
michael@0 | 313 | |
michael@0 | 314 | let engine = Services.search.getEngineByAlias(keyword); |
michael@0 | 315 | if (engine) { |
michael@0 | 316 | let submission = engine.getSubmission(param); |
michael@0 | 317 | aPostDataRef.value = submission.postData; |
michael@0 | 318 | throw new Task.Result(submission.uri.spec); |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | try { |
michael@0 | 322 | [shortcutURL, aPostDataRef.value] = PlacesUtils.getURLAndPostDataForKeyword(keyword); |
michael@0 | 323 | } catch (e) {} |
michael@0 | 324 | |
michael@0 | 325 | if (!shortcutURL) |
michael@0 | 326 | throw new Task.Result(aURL); |
michael@0 | 327 | |
michael@0 | 328 | let postData = ""; |
michael@0 | 329 | if (aPostDataRef.value) |
michael@0 | 330 | postData = unescape(aPostDataRef.value); |
michael@0 | 331 | |
michael@0 | 332 | if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) { |
michael@0 | 333 | let charset = ""; |
michael@0 | 334 | const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; |
michael@0 | 335 | let matches = shortcutURL.match(re); |
michael@0 | 336 | if (matches) |
michael@0 | 337 | [, shortcutURL, charset] = matches; |
michael@0 | 338 | else { |
michael@0 | 339 | // Try to get the saved character-set. |
michael@0 | 340 | try { |
michael@0 | 341 | // makeURI throws if URI is invalid. |
michael@0 | 342 | // Will return an empty string if character-set is not found. |
michael@0 | 343 | charset = yield PlacesUtils.getCharsetForURI(Util.makeURI(shortcutURL)); |
michael@0 | 344 | } catch (e) { dump("--- error " + e + "\n"); } |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | let encodedParam = ""; |
michael@0 | 348 | if (charset) |
michael@0 | 349 | encodedParam = escape(convertFromUnicode(charset, param)); |
michael@0 | 350 | else // Default charset is UTF-8 |
michael@0 | 351 | encodedParam = encodeURIComponent(param); |
michael@0 | 352 | |
michael@0 | 353 | shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); |
michael@0 | 354 | |
michael@0 | 355 | if (/%s/i.test(postData)) // POST keyword |
michael@0 | 356 | aPostDataRef.value = getPostDataStream(postData, param, encodedParam, "application/x-www-form-urlencoded"); |
michael@0 | 357 | } else if (param) { |
michael@0 | 358 | // This keyword doesn't take a parameter, but one was provided. Just return |
michael@0 | 359 | // the original URL. |
michael@0 | 360 | aPostDataRef.value = null; |
michael@0 | 361 | |
michael@0 | 362 | throw new Task.Result(aURL); |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | throw new Task.Result(shortcutURL); |
michael@0 | 366 | }); |
michael@0 | 367 | }, |
michael@0 | 368 | |
michael@0 | 369 | /** |
michael@0 | 370 | * Return the currently active <browser> object |
michael@0 | 371 | */ |
michael@0 | 372 | get selectedBrowser() { |
michael@0 | 373 | return (this._selectedTab && this._selectedTab.browser); |
michael@0 | 374 | }, |
michael@0 | 375 | |
michael@0 | 376 | get tabs() { |
michael@0 | 377 | return this._tabs; |
michael@0 | 378 | }, |
michael@0 | 379 | |
michael@0 | 380 | getTabModalPromptBox: function(aBrowser) { |
michael@0 | 381 | let browser = (aBrowser || getBrowser()); |
michael@0 | 382 | let stack = browser.parentNode; |
michael@0 | 383 | let self = this; |
michael@0 | 384 | |
michael@0 | 385 | let promptBox = { |
michael@0 | 386 | appendPrompt : function(args, onCloseCallback) { |
michael@0 | 387 | let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt"); |
michael@0 | 388 | newPrompt.setAttribute("promptType", args.promptType); |
michael@0 | 389 | stack.appendChild(newPrompt); |
michael@0 | 390 | browser.setAttribute("tabmodalPromptShowing", true); |
michael@0 | 391 | newPrompt.clientTop; // style flush to assure binding is attached |
michael@0 | 392 | |
michael@0 | 393 | let tab = self.getTabForBrowser(browser); |
michael@0 | 394 | tab = tab.chromeTab; |
michael@0 | 395 | |
michael@0 | 396 | newPrompt.metroInit(args, tab, onCloseCallback); |
michael@0 | 397 | return newPrompt; |
michael@0 | 398 | }, |
michael@0 | 399 | |
michael@0 | 400 | removePrompt : function(aPrompt) { |
michael@0 | 401 | stack.removeChild(aPrompt); |
michael@0 | 402 | |
michael@0 | 403 | let prompts = this.listPrompts(); |
michael@0 | 404 | if (prompts.length) { |
michael@0 | 405 | let prompt = prompts[prompts.length - 1]; |
michael@0 | 406 | prompt.Dialog.setDefaultFocus(); |
michael@0 | 407 | } else { |
michael@0 | 408 | browser.removeAttribute("tabmodalPromptShowing"); |
michael@0 | 409 | browser.focus(); |
michael@0 | 410 | } |
michael@0 | 411 | }, |
michael@0 | 412 | |
michael@0 | 413 | listPrompts : function(aPrompt) { |
michael@0 | 414 | let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt"); |
michael@0 | 415 | // NodeList --> real JS array |
michael@0 | 416 | let prompts = Array.slice(els); |
michael@0 | 417 | return prompts; |
michael@0 | 418 | }, |
michael@0 | 419 | }; |
michael@0 | 420 | |
michael@0 | 421 | return promptBox; |
michael@0 | 422 | }, |
michael@0 | 423 | |
michael@0 | 424 | getBrowserForWindowId: function getBrowserForWindowId(aWindowId) { |
michael@0 | 425 | for (let i = 0; i < this.browsers.length; i++) { |
michael@0 | 426 | if (this.browsers[i].contentWindowId == aWindowId) |
michael@0 | 427 | return this.browsers[i]; |
michael@0 | 428 | } |
michael@0 | 429 | return null; |
michael@0 | 430 | }, |
michael@0 | 431 | |
michael@0 | 432 | getBrowserForWindow: function(aWindow) { |
michael@0 | 433 | let windowID = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 434 | .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; |
michael@0 | 435 | return this.getBrowserForWindowId(windowID); |
michael@0 | 436 | }, |
michael@0 | 437 | |
michael@0 | 438 | getTabForBrowser: function getTabForBrowser(aBrowser) { |
michael@0 | 439 | let tabs = this._tabs; |
michael@0 | 440 | for (let i = 0; i < tabs.length; i++) { |
michael@0 | 441 | if (tabs[i].browser == aBrowser) |
michael@0 | 442 | return tabs[i]; |
michael@0 | 443 | } |
michael@0 | 444 | return null; |
michael@0 | 445 | }, |
michael@0 | 446 | |
michael@0 | 447 | getTabAtIndex: function getTabAtIndex(index) { |
michael@0 | 448 | if (index >= this._tabs.length || index < 0) |
michael@0 | 449 | return null; |
michael@0 | 450 | return this._tabs[index]; |
michael@0 | 451 | }, |
michael@0 | 452 | |
michael@0 | 453 | getTabFromChrome: function getTabFromChrome(chromeTab) { |
michael@0 | 454 | for (var t = 0; t < this._tabs.length; t++) { |
michael@0 | 455 | if (this._tabs[t].chromeTab == chromeTab) |
michael@0 | 456 | return this._tabs[t]; |
michael@0 | 457 | } |
michael@0 | 458 | return null; |
michael@0 | 459 | }, |
michael@0 | 460 | |
michael@0 | 461 | createTabId: function createTabId() { |
michael@0 | 462 | return this._tabId++; |
michael@0 | 463 | }, |
michael@0 | 464 | |
michael@0 | 465 | /** |
michael@0 | 466 | * Create a new tab and add it to the tab list. |
michael@0 | 467 | * |
michael@0 | 468 | * If you are opening a new foreground tab in response to a user action, use |
michael@0 | 469 | * BrowserUI.addAndShowTab which will also show the tab strip. |
michael@0 | 470 | * |
michael@0 | 471 | * @param aURI String specifying the URL to load. |
michael@0 | 472 | * @param aBringFront Boolean (optional) Open the new tab in the foreground? |
michael@0 | 473 | * @param aOwner Tab object (optional) The "parent" of the new tab. |
michael@0 | 474 | * This is the tab responsible for opening the new tab. When the new tab |
michael@0 | 475 | * is closed, we will return to a parent or "sibling" tab if possible. |
michael@0 | 476 | * @param aParams Object (optional) with optional properties: |
michael@0 | 477 | * index: Number specifying where in the tab list to insert the new tab. |
michael@0 | 478 | * private: If true, the new tab should be have Private Browsing active. |
michael@0 | 479 | * flags, postData, charset, referrerURI: See loadURIWithFlags. |
michael@0 | 480 | */ |
michael@0 | 481 | addTab: function browser_addTab(aURI, aBringFront, aOwner, aParams) { |
michael@0 | 482 | let params = aParams || {}; |
michael@0 | 483 | |
michael@0 | 484 | if (aOwner && !('index' in params)) { |
michael@0 | 485 | // Position the new tab to the right of its owner... |
michael@0 | 486 | params.index = this._tabs.indexOf(aOwner) + 1; |
michael@0 | 487 | // ...and to the right of any siblings. |
michael@0 | 488 | while (this._tabs[params.index] && this._tabs[params.index].owner == aOwner) { |
michael@0 | 489 | params.index++; |
michael@0 | 490 | } |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | let newTab = new Tab(aURI, params, aOwner); |
michael@0 | 494 | |
michael@0 | 495 | if (params.index >= 0) { |
michael@0 | 496 | this._tabs.splice(params.index, 0, newTab); |
michael@0 | 497 | } else { |
michael@0 | 498 | this._tabs.push(newTab); |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | if (aBringFront) |
michael@0 | 502 | this.selectedTab = newTab; |
michael@0 | 503 | |
michael@0 | 504 | this._announceNewTab(newTab); |
michael@0 | 505 | return newTab; |
michael@0 | 506 | }, |
michael@0 | 507 | |
michael@0 | 508 | closeTab: function closeTab(aTab, aOptions) { |
michael@0 | 509 | let tab = aTab instanceof XULElement ? this.getTabFromChrome(aTab) : aTab; |
michael@0 | 510 | if (!tab) { |
michael@0 | 511 | return; |
michael@0 | 512 | } |
michael@0 | 513 | |
michael@0 | 514 | if (aOptions && "forceClose" in aOptions && aOptions.forceClose) { |
michael@0 | 515 | this._doCloseTab(tab); |
michael@0 | 516 | return; |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | tab.browser.messageManager.sendAsyncMessage("Browser:CanUnload", {}); |
michael@0 | 520 | }, |
michael@0 | 521 | |
michael@0 | 522 | savePage: function() { |
michael@0 | 523 | ContentAreaUtils.saveDocument(this.selectedBrowser.contentWindow.document); |
michael@0 | 524 | }, |
michael@0 | 525 | |
michael@0 | 526 | /* |
michael@0 | 527 | * helper for addTab related methods. Fires events related to |
michael@0 | 528 | * new tab creation. |
michael@0 | 529 | */ |
michael@0 | 530 | _announceNewTab: function (aTab) { |
michael@0 | 531 | let event = document.createEvent("UIEvents"); |
michael@0 | 532 | event.initUIEvent("TabOpen", true, false, window, 0); |
michael@0 | 533 | aTab.chromeTab.dispatchEvent(event); |
michael@0 | 534 | aTab.browser.messageManager.sendAsyncMessage("Browser:TabOpen"); |
michael@0 | 535 | }, |
michael@0 | 536 | |
michael@0 | 537 | _doCloseTab: function _doCloseTab(aTab) { |
michael@0 | 538 | if (this._tabs.length === 1) { |
michael@0 | 539 | Browser.addTab(this.getHomePage()); |
michael@0 | 540 | } |
michael@0 | 541 | |
michael@0 | 542 | let nextTab = this.getNextTab(aTab); |
michael@0 | 543 | |
michael@0 | 544 | // Tabs owned by the closed tab are now orphaned. |
michael@0 | 545 | this._tabs.forEach(function(item, index, array) { |
michael@0 | 546 | if (item.owner == aTab) |
michael@0 | 547 | item.owner = null; |
michael@0 | 548 | }); |
michael@0 | 549 | |
michael@0 | 550 | // tray tab |
michael@0 | 551 | let event = document.createEvent("Events"); |
michael@0 | 552 | event.initEvent("TabClose", true, false); |
michael@0 | 553 | aTab.chromeTab.dispatchEvent(event); |
michael@0 | 554 | |
michael@0 | 555 | // tab window |
michael@0 | 556 | event = document.createEvent("Events"); |
michael@0 | 557 | event.initEvent("TabClose", true, false); |
michael@0 | 558 | aTab.browser.contentWindow.dispatchEvent(event); |
michael@0 | 559 | |
michael@0 | 560 | aTab.browser.messageManager.sendAsyncMessage("Browser:TabClose"); |
michael@0 | 561 | |
michael@0 | 562 | let container = aTab.chromeTab.parentNode; |
michael@0 | 563 | aTab.destroy(); |
michael@0 | 564 | this._tabs.splice(this._tabs.indexOf(aTab), 1); |
michael@0 | 565 | |
michael@0 | 566 | this.selectedTab = nextTab; |
michael@0 | 567 | |
michael@0 | 568 | event = document.createEvent("Events"); |
michael@0 | 569 | event.initEvent("TabRemove", true, false); |
michael@0 | 570 | container.dispatchEvent(event); |
michael@0 | 571 | }, |
michael@0 | 572 | |
michael@0 | 573 | getNextTab: function getNextTab(aTab) { |
michael@0 | 574 | let tabIndex = this._tabs.indexOf(aTab); |
michael@0 | 575 | if (tabIndex == -1) |
michael@0 | 576 | return null; |
michael@0 | 577 | |
michael@0 | 578 | if (this._selectedTab == aTab || this._selectedTab.chromeTab.hasAttribute("closing")) { |
michael@0 | 579 | let nextTabIndex = tabIndex + 1; |
michael@0 | 580 | let nextTab = null; |
michael@0 | 581 | |
michael@0 | 582 | while (nextTabIndex < this._tabs.length && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) { |
michael@0 | 583 | nextTab = this.getTabAtIndex(nextTabIndex); |
michael@0 | 584 | nextTabIndex++; |
michael@0 | 585 | } |
michael@0 | 586 | |
michael@0 | 587 | nextTabIndex = tabIndex - 1; |
michael@0 | 588 | while (nextTabIndex >= 0 && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) { |
michael@0 | 589 | nextTab = this.getTabAtIndex(nextTabIndex); |
michael@0 | 590 | nextTabIndex--; |
michael@0 | 591 | } |
michael@0 | 592 | |
michael@0 | 593 | if (!nextTab || nextTab.chromeTab.hasAttribute("closing")) |
michael@0 | 594 | return null; |
michael@0 | 595 | |
michael@0 | 596 | // If the next tab is not a sibling, switch back to the parent. |
michael@0 | 597 | if (aTab.owner && nextTab.owner != aTab.owner) |
michael@0 | 598 | nextTab = aTab.owner; |
michael@0 | 599 | |
michael@0 | 600 | if (!nextTab) |
michael@0 | 601 | return null; |
michael@0 | 602 | |
michael@0 | 603 | return nextTab; |
michael@0 | 604 | } |
michael@0 | 605 | |
michael@0 | 606 | return this._selectedTab; |
michael@0 | 607 | }, |
michael@0 | 608 | |
michael@0 | 609 | get selectedTab() { |
michael@0 | 610 | return this._selectedTab; |
michael@0 | 611 | }, |
michael@0 | 612 | |
michael@0 | 613 | set selectedTab(tab) { |
michael@0 | 614 | if (tab instanceof XULElement) |
michael@0 | 615 | tab = this.getTabFromChrome(tab); |
michael@0 | 616 | |
michael@0 | 617 | if (!tab) |
michael@0 | 618 | return; |
michael@0 | 619 | |
michael@0 | 620 | if (this._selectedTab == tab) { |
michael@0 | 621 | // Deck does not update its selectedIndex when children |
michael@0 | 622 | // are removed. See bug 602708 |
michael@0 | 623 | Elements.browsers.selectedPanel = tab.notification; |
michael@0 | 624 | return; |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | let isFirstTab = this._selectedTab == null; |
michael@0 | 628 | let lastTab = this._selectedTab; |
michael@0 | 629 | let browser = tab.browser; |
michael@0 | 630 | |
michael@0 | 631 | this._selectedTab = tab; |
michael@0 | 632 | |
michael@0 | 633 | if (lastTab) |
michael@0 | 634 | lastTab.active = false; |
michael@0 | 635 | |
michael@0 | 636 | if (tab) |
michael@0 | 637 | tab.active = true; |
michael@0 | 638 | |
michael@0 | 639 | BrowserUI.update(); |
michael@0 | 640 | |
michael@0 | 641 | if (isFirstTab) { |
michael@0 | 642 | BrowserUI._titleChanged(browser); |
michael@0 | 643 | } else { |
michael@0 | 644 | // Update all of our UI to reflect the new tab's location |
michael@0 | 645 | BrowserUI.updateURI(); |
michael@0 | 646 | |
michael@0 | 647 | let event = document.createEvent("Events"); |
michael@0 | 648 | event.initEvent("TabSelect", true, false); |
michael@0 | 649 | event.lastTab = lastTab; |
michael@0 | 650 | tab.chromeTab.dispatchEvent(event); |
michael@0 | 651 | } |
michael@0 | 652 | |
michael@0 | 653 | tab.lastSelected = Date.now(); |
michael@0 | 654 | }, |
michael@0 | 655 | |
michael@0 | 656 | supportsCommand: function(cmd) { |
michael@0 | 657 | return false; |
michael@0 | 658 | }, |
michael@0 | 659 | |
michael@0 | 660 | isCommandEnabled: function(cmd) { |
michael@0 | 661 | return false; |
michael@0 | 662 | }, |
michael@0 | 663 | |
michael@0 | 664 | doCommand: function(cmd) { |
michael@0 | 665 | }, |
michael@0 | 666 | |
michael@0 | 667 | getNotificationBox: function getNotificationBox(aBrowser) { |
michael@0 | 668 | let browser = aBrowser || this.selectedBrowser; |
michael@0 | 669 | return browser.parentNode.parentNode; |
michael@0 | 670 | }, |
michael@0 | 671 | |
michael@0 | 672 | /** |
michael@0 | 673 | * Handle cert exception message from content. |
michael@0 | 674 | */ |
michael@0 | 675 | _handleCertException: function _handleCertException(aMessage) { |
michael@0 | 676 | let json = aMessage.json; |
michael@0 | 677 | if (json.action == "leave") { |
michael@0 | 678 | // Get the start page from the *default* pref branch, not the user's |
michael@0 | 679 | let url = Browser.getHomePage({ useDefault: true }); |
michael@0 | 680 | this.loadURI(url); |
michael@0 | 681 | } else { |
michael@0 | 682 | // Handle setting an cert exception and reloading the page |
michael@0 | 683 | try { |
michael@0 | 684 | // Add a new SSL exception for this URL |
michael@0 | 685 | let uri = Services.io.newURI(json.url, null, null); |
michael@0 | 686 | let sslExceptions = new SSLExceptions(); |
michael@0 | 687 | |
michael@0 | 688 | if (json.action == "permanent") |
michael@0 | 689 | sslExceptions.addPermanentException(uri, window); |
michael@0 | 690 | else |
michael@0 | 691 | sslExceptions.addTemporaryException(uri, window); |
michael@0 | 692 | } catch (e) { |
michael@0 | 693 | dump("EXCEPTION handle content command: " + e + "\n" ); |
michael@0 | 694 | } |
michael@0 | 695 | |
michael@0 | 696 | // Automatically reload after the exception was added |
michael@0 | 697 | aMessage.target.reload(); |
michael@0 | 698 | } |
michael@0 | 699 | }, |
michael@0 | 700 | |
michael@0 | 701 | /** |
michael@0 | 702 | * Handle blocked site message from content. |
michael@0 | 703 | */ |
michael@0 | 704 | _handleBlockedSite: function _handleBlockedSite(aMessage) { |
michael@0 | 705 | let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); |
michael@0 | 706 | let json = aMessage.json; |
michael@0 | 707 | switch (json.action) { |
michael@0 | 708 | case "leave": { |
michael@0 | 709 | // Get the start page from the *default* pref branch, not the user's |
michael@0 | 710 | let url = Browser.getHomePage({ useDefault: true }); |
michael@0 | 711 | this.loadURI(url); |
michael@0 | 712 | break; |
michael@0 | 713 | } |
michael@0 | 714 | case "report-malware": { |
michael@0 | 715 | // Get the stop badware "why is this blocked" report url, append the current url, and go there. |
michael@0 | 716 | try { |
michael@0 | 717 | let reportURL = formatter.formatURLPref("browser.safebrowsing.malware.reportURL"); |
michael@0 | 718 | reportURL += json.url; |
michael@0 | 719 | this.loadURI(reportURL); |
michael@0 | 720 | } catch (e) { |
michael@0 | 721 | Cu.reportError("Couldn't get malware report URL: " + e); |
michael@0 | 722 | } |
michael@0 | 723 | break; |
michael@0 | 724 | } |
michael@0 | 725 | case "report-phishing": { |
michael@0 | 726 | // It's a phishing site, just link to the generic information page |
michael@0 | 727 | let url = Services.urlFormatter.formatURLPref("app.support.baseURL"); |
michael@0 | 728 | this.loadURI(url + "phishing-malware"); |
michael@0 | 729 | break; |
michael@0 | 730 | } |
michael@0 | 731 | } |
michael@0 | 732 | }, |
michael@0 | 733 | |
michael@0 | 734 | pinSite: function browser_pinSite() { |
michael@0 | 735 | // Get a path to our app tile |
michael@0 | 736 | var file = Components.classes["@mozilla.org/file/directory_service;1"]. |
michael@0 | 737 | getService(Components.interfaces.nsIProperties). |
michael@0 | 738 | get("CurProcD", Components.interfaces.nsIFile); |
michael@0 | 739 | // Get rid of the current working directory's metro subidr |
michael@0 | 740 | file = file.parent; |
michael@0 | 741 | file.append("tileresources"); |
michael@0 | 742 | file.append("VisualElements_logo.png"); |
michael@0 | 743 | var ios = Components.classes["@mozilla.org/network/io-service;1"]. |
michael@0 | 744 | getService(Components.interfaces.nsIIOService); |
michael@0 | 745 | var uriSpec = ios.newFileURI(file).spec; |
michael@0 | 746 | Services.metro.pinTileAsync(this._currentPageTileID, |
michael@0 | 747 | Browser.selectedBrowser.contentTitle, // short name |
michael@0 | 748 | Browser.selectedBrowser.contentTitle, // display name |
michael@0 | 749 | "-url " + Browser.selectedBrowser.currentURI.spec, |
michael@0 | 750 | uriSpec, uriSpec); |
michael@0 | 751 | }, |
michael@0 | 752 | |
michael@0 | 753 | get _currentPageTileID() { |
michael@0 | 754 | // We use a unique ID per URL, so just use an MD5 hash of the URL as the ID. |
michael@0 | 755 | let hasher = Cc["@mozilla.org/security/hash;1"]. |
michael@0 | 756 | createInstance(Ci.nsICryptoHash); |
michael@0 | 757 | hasher.init(Ci.nsICryptoHash.MD5); |
michael@0 | 758 | let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]. |
michael@0 | 759 | createInstance(Ci.nsIStringInputStream); |
michael@0 | 760 | stringStream.data = Browser.selectedBrowser.currentURI.spec; |
michael@0 | 761 | hasher.updateFromStream(stringStream, -1); |
michael@0 | 762 | let hashASCII = hasher.finish(true); |
michael@0 | 763 | // Replace '/' with a valid filesystem character |
michael@0 | 764 | return ("FFTileID_" + hashASCII).replace('/', '_', 'g'); |
michael@0 | 765 | }, |
michael@0 | 766 | |
michael@0 | 767 | unpinSite: function browser_unpinSite() { |
michael@0 | 768 | if (!Services.metro.immersive) |
michael@0 | 769 | return; |
michael@0 | 770 | |
michael@0 | 771 | Services.metro.unpinTileAsync(this._currentPageTileID); |
michael@0 | 772 | }, |
michael@0 | 773 | |
michael@0 | 774 | isSitePinned: function browser_isSitePinned() { |
michael@0 | 775 | if (!Services.metro.immersive) |
michael@0 | 776 | return false; |
michael@0 | 777 | |
michael@0 | 778 | return Services.metro.isTilePinned(this._currentPageTileID); |
michael@0 | 779 | }, |
michael@0 | 780 | |
michael@0 | 781 | starSite: function browser_starSite(callback) { |
michael@0 | 782 | let uri = this.selectedBrowser.currentURI; |
michael@0 | 783 | let title = this.selectedBrowser.contentTitle; |
michael@0 | 784 | |
michael@0 | 785 | Bookmarks.addForURI(uri, title, callback); |
michael@0 | 786 | }, |
michael@0 | 787 | |
michael@0 | 788 | unstarSite: function browser_unstarSite(callback) { |
michael@0 | 789 | let uri = this.selectedBrowser.currentURI; |
michael@0 | 790 | Bookmarks.removeForURI(uri, callback); |
michael@0 | 791 | }, |
michael@0 | 792 | |
michael@0 | 793 | isSiteStarredAsync: function browser_isSiteStarredAsync(callback) { |
michael@0 | 794 | let uri = this.selectedBrowser.currentURI; |
michael@0 | 795 | Bookmarks.isURIBookmarked(uri, callback); |
michael@0 | 796 | }, |
michael@0 | 797 | |
michael@0 | 798 | /** |
michael@0 | 799 | * Convenience function for getting the scrollbox position off of a |
michael@0 | 800 | * scrollBoxObject interface. Returns the actual values instead of the |
michael@0 | 801 | * wrapping objects. |
michael@0 | 802 | * |
michael@0 | 803 | * @param scroller a scrollBoxObject on which to call scroller.getPosition() |
michael@0 | 804 | */ |
michael@0 | 805 | getScrollboxPosition: function getScrollboxPosition(scroller) { |
michael@0 | 806 | let x = {}; |
michael@0 | 807 | let y = {}; |
michael@0 | 808 | scroller.getPosition(x, y); |
michael@0 | 809 | return new Point(x.value, y.value); |
michael@0 | 810 | }, |
michael@0 | 811 | |
michael@0 | 812 | forceChromeReflow: function forceChromeReflow() { |
michael@0 | 813 | let dummy = getComputedStyle(document.documentElement, "").width; |
michael@0 | 814 | }, |
michael@0 | 815 | |
michael@0 | 816 | receiveMessage: function receiveMessage(aMessage) { |
michael@0 | 817 | let json = aMessage.json; |
michael@0 | 818 | let browser = aMessage.target; |
michael@0 | 819 | |
michael@0 | 820 | switch (aMessage.name) { |
michael@0 | 821 | case "DOMLinkAdded": { |
michael@0 | 822 | // checks for an icon to use for a web app |
michael@0 | 823 | // apple-touch-icon size is 57px and default size is 16px |
michael@0 | 824 | let rel = json.rel.toLowerCase().split(" "); |
michael@0 | 825 | if (rel.indexOf("icon") != -1) { |
michael@0 | 826 | // We use the sizes attribute if available |
michael@0 | 827 | // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon |
michael@0 | 828 | let size = 16; |
michael@0 | 829 | if (json.sizes) { |
michael@0 | 830 | let sizes = json.sizes.toLowerCase().split(" "); |
michael@0 | 831 | sizes.forEach(function(item) { |
michael@0 | 832 | if (item != "any") { |
michael@0 | 833 | let [w, h] = item.split("x"); |
michael@0 | 834 | size = Math.max(Math.min(w, h), size); |
michael@0 | 835 | } |
michael@0 | 836 | }); |
michael@0 | 837 | } |
michael@0 | 838 | if (size > browser.appIcon.size) { |
michael@0 | 839 | browser.appIcon.href = json.href; |
michael@0 | 840 | browser.appIcon.size = size; |
michael@0 | 841 | } |
michael@0 | 842 | } |
michael@0 | 843 | else if ((rel.indexOf("apple-touch-icon") != -1) && (browser.appIcon.size < 57)) { |
michael@0 | 844 | // XXX should we support apple-touch-icon-precomposed ? |
michael@0 | 845 | // see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html |
michael@0 | 846 | browser.appIcon.href = json.href; |
michael@0 | 847 | browser.appIcon.size = 57; |
michael@0 | 848 | } |
michael@0 | 849 | break; |
michael@0 | 850 | } |
michael@0 | 851 | case "Browser:FormSubmit": |
michael@0 | 852 | browser.lastLocation = null; |
michael@0 | 853 | break; |
michael@0 | 854 | |
michael@0 | 855 | case "Browser:CanUnload:Return": { |
michael@0 | 856 | if (json.permit) { |
michael@0 | 857 | let tab = this.getTabForBrowser(browser); |
michael@0 | 858 | BrowserUI.animateClosingTab(tab); |
michael@0 | 859 | } |
michael@0 | 860 | break; |
michael@0 | 861 | } |
michael@0 | 862 | case "Browser:CertException": |
michael@0 | 863 | this._handleCertException(aMessage); |
michael@0 | 864 | break; |
michael@0 | 865 | case "Browser:BlockedSite": |
michael@0 | 866 | this._handleBlockedSite(aMessage); |
michael@0 | 867 | break; |
michael@0 | 868 | } |
michael@0 | 869 | }, |
michael@0 | 870 | }; |
michael@0 | 871 | |
michael@0 | 872 | Browser.MainDragger = function MainDragger() { |
michael@0 | 873 | this._horizontalScrollbar = document.getElementById("horizontal-scroller"); |
michael@0 | 874 | this._verticalScrollbar = document.getElementById("vertical-scroller"); |
michael@0 | 875 | this._scrollScales = { x: 0, y: 0 }; |
michael@0 | 876 | |
michael@0 | 877 | Elements.browsers.addEventListener("PanBegin", this, false); |
michael@0 | 878 | Elements.browsers.addEventListener("PanFinished", this, false); |
michael@0 | 879 | }; |
michael@0 | 880 | |
michael@0 | 881 | Browser.MainDragger.prototype = { |
michael@0 | 882 | isDraggable: function isDraggable(target, scroller) { |
michael@0 | 883 | return { x: true, y: true }; |
michael@0 | 884 | }, |
michael@0 | 885 | |
michael@0 | 886 | dragStart: function dragStart(clientX, clientY, target, scroller) { |
michael@0 | 887 | let browser = getBrowser(); |
michael@0 | 888 | let bcr = browser.getBoundingClientRect(); |
michael@0 | 889 | this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top); |
michael@0 | 890 | }, |
michael@0 | 891 | |
michael@0 | 892 | dragStop: function dragStop(dx, dy, scroller) { |
michael@0 | 893 | if (this._contentView && this._contentView._updateCacheViewport) |
michael@0 | 894 | this._contentView._updateCacheViewport(); |
michael@0 | 895 | this._contentView = null; |
michael@0 | 896 | }, |
michael@0 | 897 | |
michael@0 | 898 | dragMove: function dragMove(dx, dy, scroller, aIsKinetic) { |
michael@0 | 899 | let doffset = new Point(dx, dy); |
michael@0 | 900 | |
michael@0 | 901 | this._panContent(doffset); |
michael@0 | 902 | |
michael@0 | 903 | if (aIsKinetic && doffset.x != 0) |
michael@0 | 904 | return false; |
michael@0 | 905 | |
michael@0 | 906 | this._updateScrollbars(); |
michael@0 | 907 | |
michael@0 | 908 | return !doffset.equals(dx, dy); |
michael@0 | 909 | }, |
michael@0 | 910 | |
michael@0 | 911 | handleEvent: function handleEvent(aEvent) { |
michael@0 | 912 | let browser = getBrowser(); |
michael@0 | 913 | switch (aEvent.type) { |
michael@0 | 914 | case "PanBegin": { |
michael@0 | 915 | let width = ContentAreaObserver.width, height = ContentAreaObserver.height; |
michael@0 | 916 | let contentWidth = browser.contentDocumentWidth * browser.scale; |
michael@0 | 917 | let contentHeight = browser.contentDocumentHeight * browser.scale; |
michael@0 | 918 | |
michael@0 | 919 | // Allow a small margin on both sides to prevent adding scrollbars |
michael@0 | 920 | // on small viewport approximation |
michael@0 | 921 | const ALLOWED_MARGIN = 5; |
michael@0 | 922 | const SCROLL_CORNER_SIZE = 8; |
michael@0 | 923 | this._scrollScales = { |
michael@0 | 924 | x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0, |
michael@0 | 925 | y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0 |
michael@0 | 926 | } |
michael@0 | 927 | |
michael@0 | 928 | this._showScrollbars(); |
michael@0 | 929 | break; |
michael@0 | 930 | } |
michael@0 | 931 | case "PanFinished": |
michael@0 | 932 | this._hideScrollbars(); |
michael@0 | 933 | |
michael@0 | 934 | // Update the scroll position of the content |
michael@0 | 935 | browser._updateCSSViewport(); |
michael@0 | 936 | break; |
michael@0 | 937 | } |
michael@0 | 938 | }, |
michael@0 | 939 | |
michael@0 | 940 | _panContent: function md_panContent(aOffset) { |
michael@0 | 941 | if (this._contentView && !this._contentView.isRoot()) { |
michael@0 | 942 | this._panContentView(this._contentView, aOffset); |
michael@0 | 943 | // XXX we may need to have "escape borders" for iframe panning |
michael@0 | 944 | // XXX does not deal with scrollables within scrollables |
michael@0 | 945 | } |
michael@0 | 946 | // Do content panning |
michael@0 | 947 | this._panContentView(getBrowser().getRootView(), aOffset); |
michael@0 | 948 | }, |
michael@0 | 949 | |
michael@0 | 950 | /** Pan scroller by the given amount. Updates doffset with leftovers. */ |
michael@0 | 951 | _panContentView: function _panContentView(contentView, doffset) { |
michael@0 | 952 | let pos0 = contentView.getPosition(); |
michael@0 | 953 | contentView.scrollBy(doffset.x, doffset.y); |
michael@0 | 954 | let pos1 = contentView.getPosition(); |
michael@0 | 955 | doffset.subtract(pos1.x - pos0.x, pos1.y - pos0.y); |
michael@0 | 956 | }, |
michael@0 | 957 | |
michael@0 | 958 | _updateScrollbars: function _updateScrollbars() { |
michael@0 | 959 | let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y; |
michael@0 | 960 | let contentScroll = Browser.getScrollboxPosition(Browser.contentScrollboxScroller); |
michael@0 | 961 | |
michael@0 | 962 | if (scaleX) |
michael@0 | 963 | this._horizontalScrollbar.style.MozTransform = |
michael@0 | 964 | "translateX(" + Math.round(contentScroll.x * scaleX) + "px)"; |
michael@0 | 965 | |
michael@0 | 966 | if (scaleY) { |
michael@0 | 967 | let y = Math.round(contentScroll.y * scaleY); |
michael@0 | 968 | let x = 0; |
michael@0 | 969 | |
michael@0 | 970 | this._verticalScrollbar.style.MozTransform = |
michael@0 | 971 | "translate(" + x + "px," + y + "px)"; |
michael@0 | 972 | } |
michael@0 | 973 | }, |
michael@0 | 974 | |
michael@0 | 975 | _showScrollbars: function _showScrollbars() { |
michael@0 | 976 | this._updateScrollbars(); |
michael@0 | 977 | let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y; |
michael@0 | 978 | if (scaleX) { |
michael@0 | 979 | this._horizontalScrollbar.width = ContentAreaObserver.width * scaleX; |
michael@0 | 980 | this._horizontalScrollbar.setAttribute("panning", "true"); |
michael@0 | 981 | } |
michael@0 | 982 | |
michael@0 | 983 | if (scaleY) { |
michael@0 | 984 | this._verticalScrollbar.height = ContentAreaObserver.height * scaleY; |
michael@0 | 985 | this._verticalScrollbar.setAttribute("panning", "true"); |
michael@0 | 986 | } |
michael@0 | 987 | }, |
michael@0 | 988 | |
michael@0 | 989 | _hideScrollbars: function _hideScrollbars() { |
michael@0 | 990 | this._scrollScales.x = 0; |
michael@0 | 991 | this._scrollScales.y = 0; |
michael@0 | 992 | this._horizontalScrollbar.removeAttribute("panning"); |
michael@0 | 993 | this._verticalScrollbar.removeAttribute("panning"); |
michael@0 | 994 | this._horizontalScrollbar.removeAttribute("width"); |
michael@0 | 995 | this._verticalScrollbar.removeAttribute("height"); |
michael@0 | 996 | this._horizontalScrollbar.style.MozTransform = ""; |
michael@0 | 997 | this._verticalScrollbar.style.MozTransform = ""; |
michael@0 | 998 | } |
michael@0 | 999 | }; |
michael@0 | 1000 | |
michael@0 | 1001 | |
michael@0 | 1002 | function nsBrowserAccess() { } |
michael@0 | 1003 | |
michael@0 | 1004 | nsBrowserAccess.prototype = { |
michael@0 | 1005 | QueryInterface: function(aIID) { |
michael@0 | 1006 | if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports)) |
michael@0 | 1007 | return this; |
michael@0 | 1008 | throw Cr.NS_NOINTERFACE; |
michael@0 | 1009 | }, |
michael@0 | 1010 | |
michael@0 | 1011 | _getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) { |
michael@0 | 1012 | let where = aWhere; |
michael@0 | 1013 | /* |
michael@0 | 1014 | * aWhere: |
michael@0 | 1015 | * OPEN_DEFAULTWINDOW: default action |
michael@0 | 1016 | * OPEN_CURRENTWINDOW: current window/tab |
michael@0 | 1017 | * OPEN_NEWWINDOW: not allowed, converted to newtab below |
michael@0 | 1018 | * OPEN_NEWTAB: open a new tab |
michael@0 | 1019 | * OPEN_SWITCHTAB: open in an existing tab if it matches, otherwise open |
michael@0 | 1020 | * a new tab. afaict we always open these in the current tab. |
michael@0 | 1021 | */ |
michael@0 | 1022 | if (where == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { |
michael@0 | 1023 | // query standard browser prefs indicating what to do for default action |
michael@0 | 1024 | switch (aContext) { |
michael@0 | 1025 | // indicates this is an open request from a 3rd party app. |
michael@0 | 1026 | case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL : |
michael@0 | 1027 | where = Services.prefs.getIntPref("browser.link.open_external"); |
michael@0 | 1028 | break; |
michael@0 | 1029 | // internal request |
michael@0 | 1030 | default : |
michael@0 | 1031 | where = Services.prefs.getIntPref("browser.link.open_newwindow"); |
michael@0 | 1032 | } |
michael@0 | 1033 | } |
michael@0 | 1034 | if (where == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) { |
michael@0 | 1035 | Util.dumpLn("Invalid request - we can't open links in new windows."); |
michael@0 | 1036 | where = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB; |
michael@0 | 1037 | } |
michael@0 | 1038 | return where; |
michael@0 | 1039 | }, |
michael@0 | 1040 | |
michael@0 | 1041 | _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) { |
michael@0 | 1042 | let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); |
michael@0 | 1043 | // We don't allow externals apps opening chrome docs |
michael@0 | 1044 | if (isExternal && aURI && aURI.schemeIs("chrome")) |
michael@0 | 1045 | return null; |
michael@0 | 1046 | |
michael@0 | 1047 | let location; |
michael@0 | 1048 | let browser; |
michael@0 | 1049 | let loadflags = isExternal ? |
michael@0 | 1050 | Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : |
michael@0 | 1051 | Ci.nsIWebNavigation.LOAD_FLAGS_NONE; |
michael@0 | 1052 | let openAction = this._getOpenAction(aURI, aOpener, aWhere, aContext); |
michael@0 | 1053 | |
michael@0 | 1054 | if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) { |
michael@0 | 1055 | let owner = isExternal ? null : Browser.selectedTab; |
michael@0 | 1056 | let tab = BrowserUI.openLinkInNewTab("about:blank", true, owner); |
michael@0 | 1057 | browser = tab.browser; |
michael@0 | 1058 | } else { |
michael@0 | 1059 | browser = Browser.selectedBrowser; |
michael@0 | 1060 | } |
michael@0 | 1061 | |
michael@0 | 1062 | try { |
michael@0 | 1063 | let referrer; |
michael@0 | 1064 | if (aURI && browser) { |
michael@0 | 1065 | if (aOpener) { |
michael@0 | 1066 | location = aOpener.location; |
michael@0 | 1067 | referrer = Services.io.newURI(location, null, null); |
michael@0 | 1068 | } |
michael@0 | 1069 | browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); |
michael@0 | 1070 | } |
michael@0 | 1071 | browser.focus(); |
michael@0 | 1072 | } catch(e) { } |
michael@0 | 1073 | |
michael@0 | 1074 | return browser; |
michael@0 | 1075 | }, |
michael@0 | 1076 | |
michael@0 | 1077 | openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) { |
michael@0 | 1078 | let browser = this._getBrowser(aURI, aOpener, aWhere, aContext); |
michael@0 | 1079 | return browser ? browser.contentWindow : null; |
michael@0 | 1080 | }, |
michael@0 | 1081 | |
michael@0 | 1082 | openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) { |
michael@0 | 1083 | let browser = this._getBrowser(aURI, aOpener, aWhere, aContext); |
michael@0 | 1084 | return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null; |
michael@0 | 1085 | }, |
michael@0 | 1086 | |
michael@0 | 1087 | isTabContentWindow: function(aWindow) { |
michael@0 | 1088 | return Browser.browsers.some(function (browser) browser.contentWindow == aWindow); |
michael@0 | 1089 | }, |
michael@0 | 1090 | |
michael@0 | 1091 | get contentWindow() { |
michael@0 | 1092 | return Browser.selectedBrowser.contentWindow; |
michael@0 | 1093 | } |
michael@0 | 1094 | }; |
michael@0 | 1095 | |
michael@0 | 1096 | /** |
michael@0 | 1097 | * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml |
michael@0 | 1098 | */ |
michael@0 | 1099 | var PopupBlockerObserver = { |
michael@0 | 1100 | init: function init() { |
michael@0 | 1101 | Elements.browsers.addEventListener("mousedown", this, true); |
michael@0 | 1102 | }, |
michael@0 | 1103 | |
michael@0 | 1104 | handleEvent: function handleEvent(aEvent) { |
michael@0 | 1105 | switch (aEvent.type) { |
michael@0 | 1106 | case "mousedown": |
michael@0 | 1107 | let box = Browser.getNotificationBox(); |
michael@0 | 1108 | let notification = box.getNotificationWithValue("popup-blocked"); |
michael@0 | 1109 | if (notification && !notification.contains(aEvent.target)) |
michael@0 | 1110 | box.removeNotification(notification); |
michael@0 | 1111 | break; |
michael@0 | 1112 | } |
michael@0 | 1113 | }, |
michael@0 | 1114 | |
michael@0 | 1115 | onUpdatePageReport: function onUpdatePageReport(aEvent) { |
michael@0 | 1116 | var cBrowser = Browser.selectedBrowser; |
michael@0 | 1117 | if (aEvent.originalTarget != cBrowser) |
michael@0 | 1118 | return; |
michael@0 | 1119 | |
michael@0 | 1120 | if (!cBrowser.pageReport) |
michael@0 | 1121 | return; |
michael@0 | 1122 | |
michael@0 | 1123 | let result = Services.perms.testExactPermission(Browser.selectedBrowser.currentURI, "popup"); |
michael@0 | 1124 | if (result == Ci.nsIPermissionManager.DENY_ACTION) |
michael@0 | 1125 | return; |
michael@0 | 1126 | |
michael@0 | 1127 | // Only show the notification again if we've not already shown it. Since |
michael@0 | 1128 | // notifications are per-browser, we don't need to worry about re-adding |
michael@0 | 1129 | // it. |
michael@0 | 1130 | if (!cBrowser.pageReport.reported) { |
michael@0 | 1131 | if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) { |
michael@0 | 1132 | var brandShortName = Strings.brand.GetStringFromName("brandShortName"); |
michael@0 | 1133 | var popupCount = cBrowser.pageReport.length; |
michael@0 | 1134 | |
michael@0 | 1135 | let strings = Strings.browser; |
michael@0 | 1136 | let message = PluralForm.get(popupCount, strings.GetStringFromName("popupWarning.message")) |
michael@0 | 1137 | .replace("#1", brandShortName) |
michael@0 | 1138 | .replace("#2", popupCount); |
michael@0 | 1139 | |
michael@0 | 1140 | var notificationBox = Browser.getNotificationBox(); |
michael@0 | 1141 | var notification = notificationBox.getNotificationWithValue("popup-blocked"); |
michael@0 | 1142 | if (notification) { |
michael@0 | 1143 | notification.label = message; |
michael@0 | 1144 | } |
michael@0 | 1145 | else { |
michael@0 | 1146 | var buttons = [ |
michael@0 | 1147 | { |
michael@0 | 1148 | isDefault: false, |
michael@0 | 1149 | label: strings.GetStringFromName("popupButtonAllowOnce2"), |
michael@0 | 1150 | accessKey: "", |
michael@0 | 1151 | callback: function() { PopupBlockerObserver.showPopupsForSite(); } |
michael@0 | 1152 | }, |
michael@0 | 1153 | { |
michael@0 | 1154 | label: strings.GetStringFromName("popupButtonAlwaysAllow3"), |
michael@0 | 1155 | accessKey: "", |
michael@0 | 1156 | callback: function() { PopupBlockerObserver.allowPopupsForSite(true); } |
michael@0 | 1157 | }, |
michael@0 | 1158 | { |
michael@0 | 1159 | label: strings.GetStringFromName("popupButtonNeverWarn3"), |
michael@0 | 1160 | accessKey: "", |
michael@0 | 1161 | callback: function() { PopupBlockerObserver.allowPopupsForSite(false); } |
michael@0 | 1162 | } |
michael@0 | 1163 | ]; |
michael@0 | 1164 | |
michael@0 | 1165 | const priority = notificationBox.PRIORITY_WARNING_MEDIUM; |
michael@0 | 1166 | notificationBox.appendNotification(message, "popup-blocked", |
michael@0 | 1167 | "chrome://browser/skin/images/infobar-popup.png", |
michael@0 | 1168 | priority, buttons); |
michael@0 | 1169 | } |
michael@0 | 1170 | } |
michael@0 | 1171 | // Record the fact that we've reported this blocked popup, so we don't |
michael@0 | 1172 | // show it again. |
michael@0 | 1173 | cBrowser.pageReport.reported = true; |
michael@0 | 1174 | } |
michael@0 | 1175 | }, |
michael@0 | 1176 | |
michael@0 | 1177 | allowPopupsForSite: function allowPopupsForSite(aAllow) { |
michael@0 | 1178 | var currentURI = Browser.selectedBrowser.currentURI; |
michael@0 | 1179 | Services.perms.add(currentURI, "popup", aAllow |
michael@0 | 1180 | ? Ci.nsIPermissionManager.ALLOW_ACTION |
michael@0 | 1181 | : Ci.nsIPermissionManager.DENY_ACTION); |
michael@0 | 1182 | |
michael@0 | 1183 | Browser.getNotificationBox().removeCurrentNotification(); |
michael@0 | 1184 | }, |
michael@0 | 1185 | |
michael@0 | 1186 | showPopupsForSite: function showPopupsForSite() { |
michael@0 | 1187 | let uri = Browser.selectedBrowser.currentURI; |
michael@0 | 1188 | let pageReport = Browser.selectedBrowser.pageReport; |
michael@0 | 1189 | if (pageReport) { |
michael@0 | 1190 | for (let i = 0; i < pageReport.length; ++i) { |
michael@0 | 1191 | var popupURIspec = pageReport[i].popupWindowURI.spec; |
michael@0 | 1192 | |
michael@0 | 1193 | // Sometimes the popup URI that we get back from the pageReport |
michael@0 | 1194 | // isn't useful (for instance, netscape.com's popup URI ends up |
michael@0 | 1195 | // being "http://www.netscape.com", which isn't really the URI of |
michael@0 | 1196 | // the popup they're trying to show). This isn't going to be |
michael@0 | 1197 | // useful to the user, so we won't create a menu item for it. |
michael@0 | 1198 | if (popupURIspec == "" || !Util.isURLMemorable(popupURIspec) || popupURIspec == uri.spec) |
michael@0 | 1199 | continue; |
michael@0 | 1200 | |
michael@0 | 1201 | let popupFeatures = pageReport[i].popupWindowFeatures; |
michael@0 | 1202 | let popupName = pageReport[i].popupWindowName; |
michael@0 | 1203 | |
michael@0 | 1204 | Browser.addTab(popupURIspec, false, Browser.selectedTab); |
michael@0 | 1205 | } |
michael@0 | 1206 | } |
michael@0 | 1207 | } |
michael@0 | 1208 | }; |
michael@0 | 1209 | |
michael@0 | 1210 | var SessionHistoryObserver = { |
michael@0 | 1211 | observe: function sho_observe(aSubject, aTopic, aData) { |
michael@0 | 1212 | if (aTopic != "browser:purge-session-history") |
michael@0 | 1213 | return; |
michael@0 | 1214 | |
michael@0 | 1215 | let newTab = Browser.addTab("about:start", true); |
michael@0 | 1216 | let tab = Browser._tabs[0]; |
michael@0 | 1217 | while(tab != newTab) { |
michael@0 | 1218 | Browser.closeTab(tab, { forceClose: true } ); |
michael@0 | 1219 | tab = Browser._tabs[0]; |
michael@0 | 1220 | } |
michael@0 | 1221 | |
michael@0 | 1222 | PlacesUtils.history.removeAllPages(); |
michael@0 | 1223 | |
michael@0 | 1224 | // Clear undo history of the URL bar |
michael@0 | 1225 | BrowserUI._edit.editor.transactionManager.clear(); |
michael@0 | 1226 | } |
michael@0 | 1227 | }; |
michael@0 | 1228 | |
michael@0 | 1229 | function getNotificationBox(aBrowser) { |
michael@0 | 1230 | return Browser.getNotificationBox(aBrowser); |
michael@0 | 1231 | } |
michael@0 | 1232 | |
michael@0 | 1233 | function showDownloadManager(aWindowContext, aID, aReason) { |
michael@0 | 1234 | // TODO: Bug 883962: Toggle the downloads infobar as our current "download manager". |
michael@0 | 1235 | } |
michael@0 | 1236 | |
michael@0 | 1237 | function Tab(aURI, aParams, aOwner) { |
michael@0 | 1238 | this._id = null; |
michael@0 | 1239 | this._browser = null; |
michael@0 | 1240 | this._notification = null; |
michael@0 | 1241 | this._loading = false; |
michael@0 | 1242 | this._progressActive = false; |
michael@0 | 1243 | this._progressCount = 0; |
michael@0 | 1244 | this._chromeTab = null; |
michael@0 | 1245 | this._eventDeferred = null; |
michael@0 | 1246 | this._updateThumbnailTimeout = null; |
michael@0 | 1247 | |
michael@0 | 1248 | this._private = false; |
michael@0 | 1249 | if ("private" in aParams) { |
michael@0 | 1250 | this._private = aParams.private; |
michael@0 | 1251 | } else if (aOwner) { |
michael@0 | 1252 | this._private = aOwner._private; |
michael@0 | 1253 | } |
michael@0 | 1254 | |
michael@0 | 1255 | this.owner = aOwner || null; |
michael@0 | 1256 | |
michael@0 | 1257 | // Set to 0 since new tabs that have not been viewed yet are good tabs to |
michael@0 | 1258 | // toss if app needs more memory. |
michael@0 | 1259 | this.lastSelected = 0; |
michael@0 | 1260 | |
michael@0 | 1261 | // aParams is an object that contains some properties for the initial tab |
michael@0 | 1262 | // loading like flags, a referrerURI, a charset or even a postData. |
michael@0 | 1263 | this.create(aURI, aParams || {}, aOwner); |
michael@0 | 1264 | |
michael@0 | 1265 | // default tabs to inactive (i.e. no display port) |
michael@0 | 1266 | this.active = false; |
michael@0 | 1267 | } |
michael@0 | 1268 | |
michael@0 | 1269 | Tab.prototype = { |
michael@0 | 1270 | get browser() { |
michael@0 | 1271 | return this._browser; |
michael@0 | 1272 | }, |
michael@0 | 1273 | |
michael@0 | 1274 | get notification() { |
michael@0 | 1275 | return this._notification; |
michael@0 | 1276 | }, |
michael@0 | 1277 | |
michael@0 | 1278 | get chromeTab() { |
michael@0 | 1279 | return this._chromeTab; |
michael@0 | 1280 | }, |
michael@0 | 1281 | |
michael@0 | 1282 | get isPrivate() { |
michael@0 | 1283 | return this._private; |
michael@0 | 1284 | }, |
michael@0 | 1285 | |
michael@0 | 1286 | get pageShowPromise() { |
michael@0 | 1287 | return this._eventDeferred ? this._eventDeferred.promise : null; |
michael@0 | 1288 | }, |
michael@0 | 1289 | |
michael@0 | 1290 | startLoading: function startLoading() { |
michael@0 | 1291 | if (this._loading) { |
michael@0 | 1292 | let stack = new Error().stack; |
michael@0 | 1293 | throw "Already Loading!\n" + stack; |
michael@0 | 1294 | } |
michael@0 | 1295 | this._loading = true; |
michael@0 | 1296 | }, |
michael@0 | 1297 | |
michael@0 | 1298 | endLoading: function endLoading() { |
michael@0 | 1299 | this._loading = false; |
michael@0 | 1300 | this.updateFavicon(); |
michael@0 | 1301 | }, |
michael@0 | 1302 | |
michael@0 | 1303 | isLoading: function isLoading() { |
michael@0 | 1304 | return this._loading; |
michael@0 | 1305 | }, |
michael@0 | 1306 | |
michael@0 | 1307 | create: function create(aURI, aParams, aOwner) { |
michael@0 | 1308 | this._eventDeferred = Promise.defer(); |
michael@0 | 1309 | |
michael@0 | 1310 | this._chromeTab = Elements.tabList.addTab(aParams.index); |
michael@0 | 1311 | if (this.isPrivate) { |
michael@0 | 1312 | this._chromeTab.setAttribute("private", "true"); |
michael@0 | 1313 | } |
michael@0 | 1314 | |
michael@0 | 1315 | this._id = Browser.createTabId(); |
michael@0 | 1316 | let browser = this._createBrowser(aURI, null); |
michael@0 | 1317 | |
michael@0 | 1318 | let self = this; |
michael@0 | 1319 | function onPageShowEvent(aEvent) { |
michael@0 | 1320 | browser.removeEventListener("pageshow", onPageShowEvent); |
michael@0 | 1321 | if (self._eventDeferred) { |
michael@0 | 1322 | self._eventDeferred.resolve(self); |
michael@0 | 1323 | } |
michael@0 | 1324 | self._eventDeferred = null; |
michael@0 | 1325 | } |
michael@0 | 1326 | browser.addEventListener("pageshow", onPageShowEvent, true); |
michael@0 | 1327 | browser.addEventListener("DOMWindowCreated", this, false); |
michael@0 | 1328 | browser.addEventListener("StartUIChange", this, false); |
michael@0 | 1329 | Elements.browsers.addEventListener("SizeChanged", this, false); |
michael@0 | 1330 | |
michael@0 | 1331 | browser.messageManager.addMessageListener("Content:StateChange", this); |
michael@0 | 1332 | |
michael@0 | 1333 | if (aOwner) |
michael@0 | 1334 | this._copyHistoryFrom(aOwner); |
michael@0 | 1335 | this._loadUsingParams(browser, aURI, aParams); |
michael@0 | 1336 | }, |
michael@0 | 1337 | |
michael@0 | 1338 | updateViewport: function (aEvent) { |
michael@0 | 1339 | // <meta name=viewport> is not yet supported; just use the browser size. |
michael@0 | 1340 | let browser = this.browser; |
michael@0 | 1341 | |
michael@0 | 1342 | // On the start page we add padding to keep the browser above the navbar. |
michael@0 | 1343 | let paddingBottom = parseInt(getComputedStyle(browser).paddingBottom, 10); |
michael@0 | 1344 | let height = browser.clientHeight - paddingBottom; |
michael@0 | 1345 | |
michael@0 | 1346 | browser.setWindowSize(browser.clientWidth, height); |
michael@0 | 1347 | }, |
michael@0 | 1348 | |
michael@0 | 1349 | handleEvent: function (aEvent) { |
michael@0 | 1350 | switch (aEvent.type) { |
michael@0 | 1351 | case "DOMWindowCreated": |
michael@0 | 1352 | case "StartUIChange": |
michael@0 | 1353 | this.updateViewport(); |
michael@0 | 1354 | break; |
michael@0 | 1355 | case "SizeChanged": |
michael@0 | 1356 | this.updateViewport(); |
michael@0 | 1357 | this._delayUpdateThumbnail(); |
michael@0 | 1358 | break; |
michael@0 | 1359 | case "AlertClose": { |
michael@0 | 1360 | if (this == Browser.selectedTab) { |
michael@0 | 1361 | this.updateViewport(); |
michael@0 | 1362 | } |
michael@0 | 1363 | break; |
michael@0 | 1364 | } |
michael@0 | 1365 | } |
michael@0 | 1366 | }, |
michael@0 | 1367 | |
michael@0 | 1368 | receiveMessage: function(aMessage) { |
michael@0 | 1369 | switch (aMessage.name) { |
michael@0 | 1370 | case "Content:StateChange": |
michael@0 | 1371 | // update the thumbnail now... |
michael@0 | 1372 | this.updateThumbnail(); |
michael@0 | 1373 | // ...and in a little while to capture page after load. |
michael@0 | 1374 | if (aMessage.json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { |
michael@0 | 1375 | this._delayUpdateThumbnail(); |
michael@0 | 1376 | } |
michael@0 | 1377 | break; |
michael@0 | 1378 | } |
michael@0 | 1379 | }, |
michael@0 | 1380 | |
michael@0 | 1381 | _delayUpdateThumbnail: function() { |
michael@0 | 1382 | clearTimeout(this._updateThumbnailTimeout); |
michael@0 | 1383 | this._updateThumbnailTimeout = setTimeout(() => { |
michael@0 | 1384 | this.updateThumbnail(); |
michael@0 | 1385 | }, kTabThumbnailDelayCapture); |
michael@0 | 1386 | }, |
michael@0 | 1387 | |
michael@0 | 1388 | destroy: function destroy() { |
michael@0 | 1389 | this._browser.messageManager.removeMessageListener("Content:StateChange", this); |
michael@0 | 1390 | this._browser.removeEventListener("DOMWindowCreated", this, false); |
michael@0 | 1391 | this._browser.removeEventListener("StartUIChange", this, false); |
michael@0 | 1392 | Elements.browsers.removeEventListener("SizeChanged", this, false); |
michael@0 | 1393 | clearTimeout(this._updateThumbnailTimeout); |
michael@0 | 1394 | |
michael@0 | 1395 | Elements.tabList.removeTab(this._chromeTab); |
michael@0 | 1396 | this._chromeTab = null; |
michael@0 | 1397 | this._destroyBrowser(); |
michael@0 | 1398 | }, |
michael@0 | 1399 | |
michael@0 | 1400 | resurrect: function resurrect() { |
michael@0 | 1401 | let dead = this._browser; |
michael@0 | 1402 | let active = this.active; |
michael@0 | 1403 | |
michael@0 | 1404 | // Hold onto the session store data |
michael@0 | 1405 | let session = { data: dead.__SS_data, extra: dead.__SS_extdata }; |
michael@0 | 1406 | |
michael@0 | 1407 | // We need this data to correctly create and position the new browser |
michael@0 | 1408 | // If this browser is already a zombie, fallback to the session data |
michael@0 | 1409 | let currentURL = dead.__SS_restore ? session.data.entries[0].url : dead.currentURI.spec; |
michael@0 | 1410 | let sibling = dead.nextSibling; |
michael@0 | 1411 | |
michael@0 | 1412 | // Destory and re-create the browser |
michael@0 | 1413 | this._destroyBrowser(); |
michael@0 | 1414 | let browser = this._createBrowser(currentURL, sibling); |
michael@0 | 1415 | if (active) |
michael@0 | 1416 | this.active = true; |
michael@0 | 1417 | |
michael@0 | 1418 | // Reattach session store data and flag this browser so it is restored on select |
michael@0 | 1419 | browser.__SS_data = session.data; |
michael@0 | 1420 | browser.__SS_extdata = session.extra; |
michael@0 | 1421 | browser.__SS_restore = true; |
michael@0 | 1422 | }, |
michael@0 | 1423 | |
michael@0 | 1424 | _copyHistoryFrom: function _copyHistoryFrom(tab) { |
michael@0 | 1425 | let otherHistory = tab._browser._webNavigation.sessionHistory; |
michael@0 | 1426 | let history = this._browser._webNavigation.sessionHistory; |
michael@0 | 1427 | |
michael@0 | 1428 | // Ensure that history is initialized |
michael@0 | 1429 | history.QueryInterface(Ci.nsISHistoryInternal); |
michael@0 | 1430 | |
michael@0 | 1431 | for (let i = 0, length = otherHistory.index; i <= length; i++) |
michael@0 | 1432 | history.addEntry(otherHistory.getEntryAtIndex(i, false), true); |
michael@0 | 1433 | }, |
michael@0 | 1434 | |
michael@0 | 1435 | _loadUsingParams: function _loadUsingParams(aBrowser, aURI, aParams) { |
michael@0 | 1436 | let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; |
michael@0 | 1437 | let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null; |
michael@0 | 1438 | let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null; |
michael@0 | 1439 | let charset = "charset" in aParams ? aParams.charset : null; |
michael@0 | 1440 | aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData); |
michael@0 | 1441 | }, |
michael@0 | 1442 | |
michael@0 | 1443 | _createBrowser: function _createBrowser(aURI, aInsertBefore) { |
michael@0 | 1444 | if (this._browser) |
michael@0 | 1445 | throw "Browser already exists"; |
michael@0 | 1446 | |
michael@0 | 1447 | // Create a notification box around the browser. Note this includes |
michael@0 | 1448 | // the input overlay we use to shade content from input events when |
michael@0 | 1449 | // we're intercepting touch input. |
michael@0 | 1450 | let notification = this._notification = document.createElement("notificationbox"); |
michael@0 | 1451 | |
michael@0 | 1452 | let browser = this._browser = document.createElement("browser"); |
michael@0 | 1453 | browser.id = "browser-" + this._id; |
michael@0 | 1454 | this._chromeTab.linkedBrowser = browser; |
michael@0 | 1455 | |
michael@0 | 1456 | browser.setAttribute("type", "content-targetable"); |
michael@0 | 1457 | |
michael@0 | 1458 | let useRemote = Services.appinfo.browserTabsRemote; |
michael@0 | 1459 | let useLocal = Util.isLocalScheme(aURI); |
michael@0 | 1460 | browser.setAttribute("remote", (!useLocal && useRemote) ? "true" : "false"); |
michael@0 | 1461 | |
michael@0 | 1462 | // Append the browser to the document, which should start the page load |
michael@0 | 1463 | let stack = document.createElementNS(XUL_NS, "stack"); |
michael@0 | 1464 | stack.className = "browserStack"; |
michael@0 | 1465 | stack.appendChild(browser); |
michael@0 | 1466 | stack.setAttribute("flex", "1"); |
michael@0 | 1467 | notification.appendChild(stack); |
michael@0 | 1468 | Elements.browsers.insertBefore(notification, aInsertBefore); |
michael@0 | 1469 | |
michael@0 | 1470 | notification.dir = "reverse"; |
michael@0 | 1471 | notification.addEventListener("AlertClose", this); |
michael@0 | 1472 | |
michael@0 | 1473 | // let the content area manager know about this browser. |
michael@0 | 1474 | ContentAreaObserver.onBrowserCreated(browser); |
michael@0 | 1475 | |
michael@0 | 1476 | if (this.isPrivate) { |
michael@0 | 1477 | let ctx = browser.docShell.QueryInterface(Ci.nsILoadContext); |
michael@0 | 1478 | ctx.usePrivateBrowsing = true; |
michael@0 | 1479 | } |
michael@0 | 1480 | |
michael@0 | 1481 | // stop about:blank from loading |
michael@0 | 1482 | browser.stop(); |
michael@0 | 1483 | |
michael@0 | 1484 | let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; |
michael@0 | 1485 | fl.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL; |
michael@0 | 1486 | |
michael@0 | 1487 | return browser; |
michael@0 | 1488 | }, |
michael@0 | 1489 | |
michael@0 | 1490 | _destroyBrowser: function _destroyBrowser() { |
michael@0 | 1491 | if (this._browser) { |
michael@0 | 1492 | let notification = this._notification; |
michael@0 | 1493 | notification.removeEventListener("AlertClose", this); |
michael@0 | 1494 | let browser = this._browser; |
michael@0 | 1495 | browser.active = false; |
michael@0 | 1496 | |
michael@0 | 1497 | this._notification = null; |
michael@0 | 1498 | this._browser = null; |
michael@0 | 1499 | this._loading = false; |
michael@0 | 1500 | |
michael@0 | 1501 | Elements.browsers.removeChild(notification); |
michael@0 | 1502 | } |
michael@0 | 1503 | }, |
michael@0 | 1504 | |
michael@0 | 1505 | updateThumbnail: function updateThumbnail() { |
michael@0 | 1506 | if (!this.isPrivate) { |
michael@0 | 1507 | PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas); |
michael@0 | 1508 | } |
michael@0 | 1509 | }, |
michael@0 | 1510 | |
michael@0 | 1511 | updateFavicon: function updateFavicon() { |
michael@0 | 1512 | this._chromeTab.updateFavicon(this._browser.mIconURL); |
michael@0 | 1513 | }, |
michael@0 | 1514 | |
michael@0 | 1515 | set active(aActive) { |
michael@0 | 1516 | if (!this._browser) |
michael@0 | 1517 | return; |
michael@0 | 1518 | |
michael@0 | 1519 | let notification = this._notification; |
michael@0 | 1520 | let browser = this._browser; |
michael@0 | 1521 | |
michael@0 | 1522 | if (aActive) { |
michael@0 | 1523 | notification.classList.add("active-tab-notificationbox"); |
michael@0 | 1524 | browser.setAttribute("type", "content-primary"); |
michael@0 | 1525 | Elements.browsers.selectedPanel = notification; |
michael@0 | 1526 | browser.active = true; |
michael@0 | 1527 | Elements.tabList.selectedTab = this._chromeTab; |
michael@0 | 1528 | browser.focus(); |
michael@0 | 1529 | } else { |
michael@0 | 1530 | notification.classList.remove("active-tab-notificationbox"); |
michael@0 | 1531 | browser.messageManager.sendAsyncMessage("Browser:Blur", { }); |
michael@0 | 1532 | browser.setAttribute("type", "content-targetable"); |
michael@0 | 1533 | browser.active = false; |
michael@0 | 1534 | } |
michael@0 | 1535 | }, |
michael@0 | 1536 | |
michael@0 | 1537 | get active() { |
michael@0 | 1538 | if (!this._browser) |
michael@0 | 1539 | return false; |
michael@0 | 1540 | return this._browser.getAttribute("type") == "content-primary"; |
michael@0 | 1541 | }, |
michael@0 | 1542 | |
michael@0 | 1543 | toString: function() { |
michael@0 | 1544 | return "[Tab " + (this._browser ? this._browser.currentURI.spec : "(no browser)") + "]"; |
michael@0 | 1545 | } |
michael@0 | 1546 | }; |
michael@0 | 1547 | |
michael@0 | 1548 | // Helper used to hide IPC / non-IPC differences for rendering to a canvas |
michael@0 | 1549 | function rendererFactory(aBrowser, aCanvas) { |
michael@0 | 1550 | let wrapper = {}; |
michael@0 | 1551 | |
michael@0 | 1552 | if (aBrowser.contentWindow) { |
michael@0 | 1553 | let ctx = aCanvas.getContext("2d"); |
michael@0 | 1554 | let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) { |
michael@0 | 1555 | ctx.drawWindow(browser.contentWindow, aLeft, aTop, aWidth, aHeight, aColor, aFlags); |
michael@0 | 1556 | let e = document.createEvent("HTMLEvents"); |
michael@0 | 1557 | e.initEvent("MozAsyncCanvasRender", true, true); |
michael@0 | 1558 | aCanvas.dispatchEvent(e); |
michael@0 | 1559 | }; |
michael@0 | 1560 | wrapper.checkBrowser = function(browser) { |
michael@0 | 1561 | return browser.contentWindow; |
michael@0 | 1562 | }; |
michael@0 | 1563 | wrapper.drawContent = function(callback) { |
michael@0 | 1564 | callback(ctx, draw); |
michael@0 | 1565 | }; |
michael@0 | 1566 | } |
michael@0 | 1567 | else { |
michael@0 | 1568 | let ctx = aCanvas.MozGetIPCContext("2d"); |
michael@0 | 1569 | let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) { |
michael@0 | 1570 | ctx.asyncDrawXULElement(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags); |
michael@0 | 1571 | }; |
michael@0 | 1572 | wrapper.checkBrowser = function(browser) { |
michael@0 | 1573 | return !browser.contentWindow; |
michael@0 | 1574 | }; |
michael@0 | 1575 | wrapper.drawContent = function(callback) { |
michael@0 | 1576 | callback(ctx, draw); |
michael@0 | 1577 | }; |
michael@0 | 1578 | } |
michael@0 | 1579 | |
michael@0 | 1580 | return wrapper; |
michael@0 | 1581 | }; |
michael@0 | 1582 | |
michael@0 | 1583 | // Based on ClickEventHandler from /browser/base/content/content.js |
michael@0 | 1584 | let ClickEventHandler = { |
michael@0 | 1585 | init: function () { |
michael@0 | 1586 | gEventListenerService.addSystemEventListener(Elements.browsers, "click", this, true); |
michael@0 | 1587 | }, |
michael@0 | 1588 | |
michael@0 | 1589 | uninit: function () { |
michael@0 | 1590 | gEventListenerService.removeSystemEventListener(Elements.browsers, "click", this, true); |
michael@0 | 1591 | }, |
michael@0 | 1592 | |
michael@0 | 1593 | handleEvent: function (aEvent) { |
michael@0 | 1594 | if (!aEvent.isTrusted || aEvent.defaultPrevented) { |
michael@0 | 1595 | return; |
michael@0 | 1596 | } |
michael@0 | 1597 | let [href, node] = this._hrefAndLinkNodeForClickEvent(aEvent); |
michael@0 | 1598 | if (href && (aEvent.button == 1 || aEvent.ctrlKey)) { |
michael@0 | 1599 | // Open link in a new tab for middle-click or ctrl-click |
michael@0 | 1600 | BrowserUI.openLinkInNewTab(href, aEvent.shiftKey, Browser.selectedTab); |
michael@0 | 1601 | } |
michael@0 | 1602 | }, |
michael@0 | 1603 | |
michael@0 | 1604 | /** |
michael@0 | 1605 | * Extracts linkNode and href for the current click target. |
michael@0 | 1606 | * |
michael@0 | 1607 | * @param event |
michael@0 | 1608 | * The click event. |
michael@0 | 1609 | * @return [href, linkNode]. |
michael@0 | 1610 | * |
michael@0 | 1611 | * @note linkNode will be null if the click wasn't on an anchor |
michael@0 | 1612 | * element (or XLink). |
michael@0 | 1613 | */ |
michael@0 | 1614 | _hrefAndLinkNodeForClickEvent: function(event) { |
michael@0 | 1615 | function isHTMLLink(aNode) { |
michael@0 | 1616 | return ((aNode instanceof content.HTMLAnchorElement && aNode.href) || |
michael@0 | 1617 | (aNode instanceof content.HTMLAreaElement && aNode.href) || |
michael@0 | 1618 | aNode instanceof content.HTMLLinkElement); |
michael@0 | 1619 | } |
michael@0 | 1620 | |
michael@0 | 1621 | let node = event.target; |
michael@0 | 1622 | while (node && !isHTMLLink(node)) { |
michael@0 | 1623 | node = node.parentNode; |
michael@0 | 1624 | } |
michael@0 | 1625 | |
michael@0 | 1626 | if (node) |
michael@0 | 1627 | return [node.href, node]; |
michael@0 | 1628 | |
michael@0 | 1629 | // If there is no linkNode, try simple XLink. |
michael@0 | 1630 | let href, baseURI; |
michael@0 | 1631 | node = event.target; |
michael@0 | 1632 | while (node && !href) { |
michael@0 | 1633 | if (node.nodeType == content.Node.ELEMENT_NODE) { |
michael@0 | 1634 | href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); |
michael@0 | 1635 | if (href) |
michael@0 | 1636 | baseURI = node.ownerDocument.baseURIObject; |
michael@0 | 1637 | } |
michael@0 | 1638 | node = node.parentNode; |
michael@0 | 1639 | } |
michael@0 | 1640 | |
michael@0 | 1641 | // In case of XLink, we don't return the node we got href from since |
michael@0 | 1642 | // callers expect <a>-like elements. |
michael@0 | 1643 | // Note: makeURI() will throw if aUri is not a valid URI. |
michael@0 | 1644 | return [href ? Services.io.newURI(href, null, baseURI).spec : null, null]; |
michael@0 | 1645 | } |
michael@0 | 1646 | }; |