browser/metro/base/content/browser.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial