BrowserContentHandler.jsm 34.9 KB
Newer Older
1
2
3
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
6
7
8
9
10
11
12
13
14
15
16
var EXPORTED_SYMBOLS = [
  "nsBrowserContentHandler",
  "nsDefaultCommandLineHandler",
];

const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
  "resource://gre/modules/AppConstants.jsm"
);
17

18
19
20
XPCOMUtils.defineLazyModuleGetters(this, {
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
  HeadlessShell: "resource:///modules/HeadlessShell.jsm",
21
  HomePage: "resource:///modules/HomePage.jsm",
22
  FirstStartup: "resource://gre/modules/FirstStartup.jsm",
23
24
  LaterRun: "resource:///modules/LaterRun.jsm",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
25
  SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
26
  ShellService: "resource:///modules/ShellService.jsm",
27
  UpdatePing: "resource://gre/modules/UpdatePing.jsm",
28
});
29
30
31
32
33
34
XPCOMUtils.defineLazyServiceGetter(
  this,
  "WindowsUIUtils",
  "@mozilla.org/windows-ui-utils;1",
  "nsIWindowsUIUtils"
);
35
36
37
38
39
40
XPCOMUtils.defineLazyServiceGetter(
  this,
  "UpdateManager",
  "@mozilla.org/updates/update-manager;1",
  "nsIUpdateManager"
);
41
42
43
44

XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal", () =>
  Services.scriptSecurityManager.getSystemPrincipal()
);
45
XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
46

47
48
const NEWINSTALL_PAGE = "about:newinstall";

49
50
const kTBSavedVersionPref = "browser.startup.homepage_override.torbrowser.version";

51
52
53
54
// One-time startup homepage override configurations
const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
const ONCE_PREF = "browser.startup.homepage_override.once";

55
function shouldLoadURI(aURI) {
56
  if (aURI && !aURI.schemeIs("chrome")) {
57
    return true;
58
  }
59
60

  dump("*** Preventing external load of chrome: URI into browser window\n");
61
  dump("    Use --chrome <uri> instead\n");
62
63
64
65
  return false;
}

function resolveURIInternal(aCmdLine, aArgument) {
66
  var uri = aCmdLine.resolveURI(aArgument);
67
  var uriFixup = Services.uriFixup;
68

69
  if (!(uri instanceof Ci.nsIFileURL)) {
70
    return Services.uriFixup.getFixupURIInfo(
71
72
      aArgument,
      uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
73
    ).preferredURI;
74
75
76
  }

  try {
77
    if (uri.file.exists()) {
78
      return uri;
79
    }
80
  } catch (e) {
81
    Cu.reportError(e);
82
83
84
85
  }

  // We have interpreted the argument as a relative file URI, but the file
  // doesn't exist. Try URI fixup heuristics: see bug 290782.
86

87
  try {
88
    uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
89
  } catch (e) {
90
    Cu.reportError(e);
91
92
93
94
95
  }

  return uri;
}

96
97
let gKiosk = false;

98
99
var gFirstWindow = false;

100
const OVERRIDE_NONE = 0;
101
const OVERRIDE_NEW_PROFILE = 1;
102
const OVERRIDE_NEW_MSTONE = 2;
103
const OVERRIDE_NEW_BUILD_ID = 3;
104
const OVERRIDE_ALTERNATE_PROFILE = 4;
105
106
107
108
109
/**
 * Determines whether a home page override is needed.
 * Returns:
 *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
 *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
110
111
 *                      Gecko milestone or Tor Browser version (i.e. right
 *                      after an upgrade).
112
113
 *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
 *                        same Gecko milestone (i.e. after a nightly upgrade).
114
115
 *  OVERRIDE_NONE otherwise.
 */
116
function needHomepageOverride(prefb) {
117
118
119
  let pService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
    Ci.nsIToolkitProfileService
  );
120
121
122
  if (pService.createdAlternateProfile) {
    return OVERRIDE_ALTERNATE_PROFILE;
  }
123
124
125
126
  var savedmstone = prefb.getCharPref(
    "browser.startup.homepage_override.mstone",
    ""
  );
127

128
  if (savedmstone == "ignore") {
129
    return OVERRIDE_NONE;
130
  }
131

132
  var mstone = Services.appinfo.platformVersion;
133

134
135
136
137
138
  var savedTBVersion = null;
  try {
    savedTBVersion = prefb.getCharPref(kTBSavedVersionPref);
  } catch (e) {}

139
140
141
142
  var savedBuildID = prefb.getCharPref(
    "browser.startup.homepage_override.buildID",
    ""
  );
143

144
  var buildID = Services.appinfo.platformBuildID;
145

146
  if (mstone != savedmstone) {
147
148
149
150
    // Bug 462254. Previous releases had a default pref to suppress the EULA
    // agreement if the platform's installer had already shown one. Now with
    // about:rights we've removed the EULA stuff and default pref, but we need
    // a way to make existing profiles retain the default that we removed.
151
    if (savedmstone) {
152
      prefb.setBoolPref("browser.rights.3.shown", true);
153
    }
154

155
    prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
156
    prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
    prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION);

    // After an upgrade from an older release of Tor Browser (<= 5.5a1), the
    // savedmstone will be undefined because those releases included the
    // value "ignore" for the browser.startup.homepage_override.mstone pref.
    // To correctly detect an upgrade vs. a new profile, we check for the
    // presence of the "app.update.postupdate" pref.
    let updated = prefb.prefHasUserValue("app.update.postupdate");
    return (savedmstone || updated) ? OVERRIDE_NEW_MSTONE
                                    : OVERRIDE_NEW_PROFILE;
  }

  if (AppConstants.TOR_BROWSER_VERSION != savedTBVersion) {
    prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
    prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION);
    return OVERRIDE_NEW_MSTONE;
173
  }
174

175
176
177
178
179
  if (buildID != savedBuildID) {
    prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
    return OVERRIDE_NEW_BUILD_ID;
  }

180
  return OVERRIDE_NONE;
181
182
}

183
184
185
/**
 * Gets the override page for the first run after the application has been
 * updated.
186
187
 * @param  update
 *         The nsIUpdate for the update that has been applied.
188
189
190
191
 * @param  defaultOverridePage
 *         The default override page.
 * @return The override page.
 */
192
193
function getPostUpdateOverridePage(update, defaultOverridePage) {
  update = update.QueryInterface(Ci.nsIWritablePropertyBag);
194
195
196
  let actions = update.getProperty("actions");
  // When the update doesn't specify actions fallback to the original behavior
  // of displaying the default override page.
197
  if (!actions) {
198
    return defaultOverridePage;
199
  }
200
201
202

  // The existence of silent or the non-existence of showURL in the actions both
  // mean that an override page should not be displayed.
203
  if (actions.includes("silent") || !actions.includes("showURL")) {
204
    return "";
205
  }
206

207
208
209
210
211
212
213
  // If a policy was set to not allow the update.xml-provided
  // URL to be used, use the default fallback (which will also
  // be provided by the policy).
  if (!Services.policies.isAllowed("postUpdateCustomPage")) {
    return defaultOverridePage;
  }

214
215
216
  return update.getProperty("openURL") || defaultOverridePage;
}

217
218
/**
 * Open a browser window. If this is the initial launch, this function will
219
 * attempt to use the navigator:blank window opened by BrowserGlue.jsm during
220
221
222
223
224
225
 * early startup.
 *
 * @param cmdLine
 *        The nsICommandLine object given to nsICommandLineHandler's handle
 *        method.
 *        Used to check if we are processing the command line for the initial launch.
226
227
 * @param triggeringPrincipal
 *        The nsIPrincipal to use as triggering principal for the page load(s).
228
229
230
231
232
233
234
235
236
237
238
239
 * @param urlOrUrlList (optional)
 *        When omitted, the browser window will be opened with the default
 *        arguments, which will usually load the homepage.
 *        This can be a JS array of urls provided as strings, each url will be
 *        loaded in a tab. postData will be ignored in this case.
 *        This can be a single url to load in the new window, provided as a string.
 *        postData will be used in this case if provided.
 * @param postData (optional)
 *        An nsIInputStream object to use as POST data when loading the provided
 *        url, or null.
 * @param forcePrivate (optional)
 *        Boolean. If set to true, the new window will be a private browsing one.
240
241
242
 *
 * @returns {ChromeWindow}
 *          Returns the top level window opened.
243
 */
244
245
246
247
248
249
250
function openBrowserWindow(
  cmdLine,
  triggeringPrincipal,
  urlOrUrlList,
  postData = null,
  forcePrivate = false
) {
251
  let chromeURL = AppConstants.BROWSER_CHROME_URL;
252
253
  const isStartup =
    cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
254

255
256
  let args;
  if (!urlOrUrlList) {
257
    // Just pass in the defaultArgs directly. We'll use system principal on the other end.
258
    args = [gBrowserContentHandler.getArgs(isStartup)];
259
  } else {
260
261
262
    let pService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
      Ci.nsIToolkitProfileService
    );
263
    if (isStartup && pService.createdAlternateProfile) {
264
      let url = NEWINSTALL_PAGE;
265
266
267
268
269
270
271
272
273
274
      if (Array.isArray(urlOrUrlList)) {
        urlOrUrlList.unshift(url);
      } else {
        urlOrUrlList = [url, urlOrUrlList];
      }
    }

    if (Array.isArray(urlOrUrlList)) {
      // There isn't an explicit way to pass a principal here, so we load multiple URLs
      // with system principal when we get to actually loading them.
275
276
277
278
279
280
281
      if (
        !triggeringPrincipal ||
        !triggeringPrincipal.equals(gSystemPrincipal)
      ) {
        throw new Error(
          "Can't open multiple URLs with something other than system principal."
        );
282
283
      }
      // Passing an nsIArray for the url disables the "|"-splitting behavior.
284
285
286
      let uriArray = Cc["@mozilla.org/array;1"].createInstance(
        Ci.nsIMutableArray
      );
287
      urlOrUrlList.forEach(function(uri) {
288
289
290
        var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
          Ci.nsISupportsString
        );
291
292
293
294
295
296
297
298
        sstring.data = uri;
        uriArray.appendElement(sstring);
      });
      args = [uriArray];
    } else {
      // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
      // ie. avoid the loadOneOrMoreURIs function.
      // Also, we need to pass the triggering principal.
299
300
301
302
303
304
305
306
307
308
309
310
      args = [
        urlOrUrlList,
        null, // charset
        null, // refererInfo
        postData,
        undefined, // allowThirdPartyFixup; this would be `false` but that
        // needs a conversion. Hopefully bug 1485961 will fix.
        undefined, // user context id
        null, // origin principal
        null, // origin storage principal
        triggeringPrincipal,
      ];
311
    }
312
  }
313

314
  if (isStartup) {
315
316
317
318
319
320
321
    let win = Services.wm.getMostRecentWindow("navigator:blank");
    if (win) {
      // Remove the windowtype of our blank window so that we don't close it
      // later on when seeing cmdLine.preventDefault is true.
      win.document.documentElement.removeAttribute("windowtype");

      if (forcePrivate) {
322
323
324
        win.docShell.QueryInterface(
          Ci.nsILoadContext
        ).usePrivateBrowsing = true;
325
326
      }

327
      let openTime = win.openTime;
328
329
      win.location = chromeURL;
      win.arguments = args; // <-- needs to be a plain JS array here.
330

331
      ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
332
      return win;
333
334
335
336
337
338
339
340
    }
  }

  // We can't provide arguments to openWindow as a JS array.
  if (!urlOrUrlList) {
    // If we have a single string guaranteed to not contain '|' we can simply
    // wrap it in an nsISupportsString object.
    let [url] = args;
341
342
343
    args = Cc["@mozilla.org/supports-string;1"].createInstance(
      Ci.nsISupportsString
    );
344
345
346
347
    args.data = url;
  } else {
    // Otherwise, pass an nsIArray.
    if (args.length > 1) {
348
349
350
      let string = Cc["@mozilla.org/supports-string;1"].createInstance(
        Ci.nsISupportsString
      );
351
352
353
354
      string.data = args[0];
      args[0] = string;
    }
    let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
355
356
357
    args.forEach(a => {
      array.appendElement(a);
    });
358
359
360
    args = array;
  }

361
362
363
  let features =
    "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
  if (forcePrivate) {
364
    features += ",private";
365
  }
366

367
  return Services.ww.openWindow(null, chromeURL, "_blank", features, args);
368
369
}

370
function openPreferences(cmdLine, extraArgs) {
371
  openBrowserWindow(cmdLine, gSystemPrincipal, "about:preferences");
372
373
}

374
375
376
377
378
379
380
async function doSearch(searchTerm, cmdLine) {
  // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
  // preferences, but need nsIBrowserDOMWindow extensions
  // Open the window immediately as BrowserContentHandler needs to
  // be handled synchronously. Then load the search URI when the
  // SearchService has loaded.
  let win = openBrowserWindow(cmdLine, gSystemPrincipal, "about:blank");
381
382
383
384
385
386
387
388
389
390
  await new Promise(resolve => {
    Services.obs.addObserver(function observe(subject) {
      if (subject == win) {
        Services.obs.removeObserver(
          observe,
          "browser-delayed-startup-finished"
        );
        resolve();
      }
    }, "browser-delayed-startup-finished");
391
  });
392
393
394
395
396
397
398
399

  win.BrowserSearch.loadSearchFromCommandLine(
    searchTerm,
    PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
      PrivateBrowsingUtils.isWindowPrivate(win),
    gSystemPrincipal,
    win.gBrowser.selectedBrowser.csp
  ).catch(Cu.reportError);
400
401
}

402
function nsBrowserContentHandler() {
403
404
405
406
  if (!gBrowserContentHandler) {
    gBrowserContentHandler = this;
  }
  return gBrowserContentHandler;
407
408
}
nsBrowserContentHandler.prototype = {
409
  /* nsISupports */
410
  QueryInterface: ChromeUtils.generateQI([
411
412
413
414
    "nsICommandLineHandler",
    "nsIBrowserHandler",
    "nsIContentHandler",
    "nsICommandLineValidator",
415
  ]),
416
417

  /* nsICommandLineHandler */
418
  handle: function bch_handle(cmdLine) {
419
420
421
    if (cmdLine.handleFlag("kiosk", false)) {
      gKiosk = true;
    }
422
    if (cmdLine.handleFlag("browser", false)) {
423
      openBrowserWindow(cmdLine, gSystemPrincipal);
424
425
426
      cmdLine.preventDefault = true;
    }

427
428
429
430
431
432
433
434
    // In the past, when an instance was not already running, the -remote
    // option returned an error code. Any script or application invoking the
    // -remote option is expected to be handling this case, otherwise they
    // wouldn't be doing anything when there is no Firefox already running.
    // Making the -remote option always return an error code makes those
    // scripts or applications handle the situation as if Firefox was not
    // already running.
    if (cmdLine.handleFlag("remote", true)) {
435
      throw Components.Exception("", Cr.NS_ERROR_ABORT);
436
437
    }

438
    var uriparam;
439
    try {
440
      while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
441
        let uri = resolveURIInternal(cmdLine, uriparam);
442
        if (!shouldLoadURI(uri)) {
443
          continue;
444
        }
445
        openBrowserWindow(cmdLine, gSystemPrincipal, uri.spec);
446
447
        cmdLine.preventDefault = true;
      }
448
    } catch (e) {
449
      Cu.reportError(e);
450
451
    }

452
453
    try {
      while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
454
        let uri = resolveURIInternal(cmdLine, uriparam);
455
456
457
458
459
460
461
        handURIToExistingBrowser(
          uri,
          Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
          cmdLine,
          false,
          gSystemPrincipal
        );
462
463
        cmdLine.preventDefault = true;
      }
464
    } catch (e) {
465
      Cu.reportError(e);
466
467
    }

468
469
    var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
    if (chromeParam) {
470
      // Handle old preference dialog URLs.
471
472
473
474
      if (
        chromeParam == "chrome://browser/content/pref/pref.xul" ||
        chromeParam == "chrome://browser/content/preferences/preferences.xul"
      ) {
475
        openPreferences(cmdLine);
476
        cmdLine.preventDefault = true;
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
      } else {
        try {
          let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
          let isLocal = uri => {
            let localSchemes = new Set(["chrome", "file", "resource"]);
            if (uri instanceof Ci.nsINestedURI) {
              uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
            }
            return localSchemes.has(uri.scheme);
          };
          if (isLocal(resolvedURI)) {
            // If the URI is local, we are sure it won't wrongly inherit chrome privs
            let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
            // Provide 1 null argument, as openWindow has a different behavior
            // when the arg count is 0.
492
493
494
            let argArray = Cc["@mozilla.org/array;1"].createInstance(
              Ci.nsIMutableArray
            );
495
            argArray.appendElement(null);
496
497
498
499
500
501
502
            Services.ww.openWindow(
              null,
              resolvedURI.spec,
              "_blank",
              features,
              argArray
            );
503
504
505
            cmdLine.preventDefault = true;
          } else {
            dump("*** Preventing load of web URI as chrome\n");
506
507
508
            dump(
              "    If you're trying to load a webpage, do not pass --chrome.\n"
            );
509
          }
510
511
        } catch (e) {
          Cu.reportError(e);
512
        }
513
514
515
      }
    }
    if (cmdLine.handleFlag("preferences", false)) {
516
      openPreferences(cmdLine);
517
518
      cmdLine.preventDefault = true;
    }
519
    if (cmdLine.handleFlag("silent", false)) {
520
      cmdLine.preventDefault = true;
521
    }
522
523

    try {
524
525
526
527
      var privateWindowParam = cmdLine.handleFlagWithParam(
        "private-window",
        false
      );
528
      if (privateWindowParam) {
529
530
531
532
533
534
535
536
537
538
        let forcePrivate = true;
        let resolvedURI;
        if (!PrivateBrowsingUtils.enabled) {
          // Load about:privatebrowsing in a normal tab, which will display an error indicating
          // access to private browsing has been disabled.
          forcePrivate = false;
          resolvedURI = Services.io.newURI("about:privatebrowsing");
        } else {
          resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
        }
539
540
541
542
543
544
545
        handURIToExistingBrowser(
          resolvedURI,
          Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
          cmdLine,
          forcePrivate,
          gSystemPrincipal
        );
546
        cmdLine.preventDefault = true;
547
      }
548
    } catch (e) {
549
      if (e.result != Cr.NS_ERROR_INVALID_ARG) {
550
551
        throw e;
      }
552
553
      // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
      if (cmdLine.handleFlag("private-window", false)) {
554
555
556
557
558
559
560
        openBrowserWindow(
          cmdLine,
          gSystemPrincipal,
          "about:privatebrowsing",
          null,
          PrivateBrowsingUtils.enabled
        );
561
562
        cmdLine.preventDefault = true;
      }
563
    }
564
565
566
567
568
569
570

    var searchParam = cmdLine.handleFlagWithParam("search", false);
    if (searchParam) {
      doSearch(searchParam, cmdLine);
      cmdLine.preventDefault = true;
    }

571
572
    // The global PB Service consumes this flag, so only eat it in per-window
    // PB builds.
573
    if (cmdLine.handleFlag("private", false) && PrivateBrowsingUtils.enabled) {
574
      PrivateBrowsingUtils.enterTemporaryAutoStartMode();
575
      if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
576
577
        let win = Services.wm.getMostRecentWindow("navigator:blank");
        if (win) {
578
579
580
          win.docShell.QueryInterface(
            Ci.nsILoadContext
          ).usePrivateBrowsing = true;
581
582
        }
      }
583
    }
584
585
586
    if (cmdLine.handleFlag("setDefaultBrowser", false)) {
      ShellService.setDefaultBrowser(true, true);
    }
587

588
589
590
591
    if (cmdLine.handleFlag("first-startup", false)) {
      FirstStartup.init();
    }

592
593
594
    var fileParam = cmdLine.handleFlagWithParam("file", false);
    if (fileParam) {
      var file = cmdLine.resolveFile(fileParam);
595
      var fileURI = Services.io.newFileURI(file);
596
      openBrowserWindow(cmdLine, gSystemPrincipal, fileURI.spec);
597
598
599
      cmdLine.preventDefault = true;
    }

600
    if (AppConstants.platform == "win") {
601
602
603
604
605
606
      // Handle "? searchterm" for Windows Vista start menu integration
      for (var i = cmdLine.length - 1; i >= 0; --i) {
        var param = cmdLine.getArgument(i);
        if (param.match(/^\? /)) {
          cmdLine.removeArguments(i, i);
          cmdLine.preventDefault = true;
607

608
609
610
          searchParam = param.substr(2);
          doSearch(searchParam, cmdLine);
        }
611
612
      }
    }
613
614
  },

615
616
  get helpInfo() {
    let info =
617
618
619
620
      "  --browser          Open a browser window.\n" +
      "  --new-window <url> Open <url> in a new window.\n" +
      "  --new-tab <url>    Open <url> in a new tab.\n" +
      "  --private-window <url> Open <url> in a new private window.\n";
621
622
623
624
625
    if (AppConstants.platform == "win") {
      info += "  --preferences      Open Options dialog.\n";
    } else {
      info += "  --preferences      Open Preferences dialog.\n";
    }
626
627
628
629
630
631
    info +=
      "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
    info +=
      "  --window-size width[,height] Width and optionally height of screenshot.\n";
    info +=
      "  --search <term>    Search <term> with your default search engine.\n";
632
    info += "  --setDefaultBrowser Set this app as the default browser.\n";
633
634
    info +=
      "  --first-startup    Run post-install actions before opening a new window.\n";
635
    info += "  --kiosk Start the browser in kiosk mode.\n";
636
637
    return info;
  },
638
639
640

  /* nsIBrowserHandler */

641
  get defaultArgs() {
642
643
644
645
    return this.getArgs();
  },

  getArgs(isStartup = false) {
646
    var prefb = Services.prefs;
647

648
649
650
651
652
653
654
    if (!gFirstWindow) {
      gFirstWindow = true;
      if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
        return "about:privatebrowsing";
      }
    }

655
    var override;
656
    var overridePage = "";
657
    var additionalPage = "";
658
    var willRestoreSession = false;
659
    try {
660
661
662
663
664
      // Read the old value of homepage_override.mstone before
      // needHomepageOverride updates it, so that we can later add it to the
      // URL if we do end up showing an overridePage. This makes it possible
      // to have the overridePage's content vary depending on the version we're
      // upgrading from.
665
666
667
668
669
670
671
672
      let old_mstone = Services.prefs.getCharPref(
        "browser.startup.homepage_override.mstone",
        "unknown"
      );
      let old_buildId = Services.prefs.getCharPref(
        "browser.startup.homepage_override.buildID",
        "unknown"
      );
673
674
675
676
677
678
679

      // We do the same for the Tor Browser version.
      let old_tbversion = null;
      try {
        old_tbversion = prefb.getCharPref(kTBSavedVersionPref);
      } catch (e) {}

680
      override = needHomepageOverride(prefb);
681
682
      if (override != OVERRIDE_NONE) {
        switch (override) {
683
684
685
686
          case OVERRIDE_ALTERNATE_PROFILE:
            // Override the welcome page to explain why the user has a new
            // profile. nsBrowserGlue.css will be responsible for showing the
            // modal dialog.
687
            overridePage = NEWINSTALL_PAGE;
688
            break;
689
690
          case OVERRIDE_NEW_PROFILE:
            // New profile.
691
692
693
694
695
696
            overridePage = Services.urlFormatter.formatURLPref(
              "startup.homepage_welcome_url"
            );
            additionalPage = Services.urlFormatter.formatURLPref(
              "startup.homepage_welcome_url.additional"
            );
697
698
            // Turn on 'later run' pages for new profiles.
            LaterRun.enabled = true;
699
700
            break;
          case OVERRIDE_NEW_MSTONE:
701
702
703
704
705
            // Check whether we will restore a session. If we will, we assume
            // that this is an "update" session. This does not take crashes
            // into account because that requires waiting for the session file
            // to be read. If a crash occurs after updating, before restarting,
            // we may open the startPage in addition to restoring the session.
706
            willRestoreSession = SessionStartup.isAutomaticRestoreEnabled();
707

708
709
710
            overridePage = Services.urlFormatter.formatURLPref(
              "startup.homepage_override_url"
            );
711
            let update = UpdateManager.readyUpdate;
712
            let old_version = old_tbversion ? old_tbversion: old_mstone;
713
714
            if (
              update &&
715
              Services.vc.compare(update.appVersion, old_version) > 0
716
717
            ) {
              overridePage = getPostUpdateOverridePage(update, overridePage);
718
719
720
              // Send the update ping to signal that the update was successful.
              UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
            }
721
722

            overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
723
724
            overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%",
                                                old_tbversion);
725
            break;
726
          case OVERRIDE_NEW_BUILD_ID:
727
            if (UpdateManager.readyUpdate) {
728
729
730
731
              // Send the update ping to signal that the update was successful.
              UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
            }
            break;
732
        }
733
      }
734
    } catch (ex) {}
735

736
    // formatURLPref might return "about:blank" if getting the pref fails
737
    if (overridePage == "about:blank") {
738
      overridePage = "";
739
    }
740

741
742
743
744
745
746
747
    // Allow showing a one-time startup override if we're not showing one
    if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
      try {
        // Show if we haven't passed the expiration or there's no expiration
        const { expire, url } = JSON.parse(
          Services.urlFormatter.formatURLPref(ONCE_PREF)
        );
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
        if (!(Date.now() > expire)) {
          // Only set allowed urls as override pages
          overridePage = url
            .split("|")
            .map(val => {
              try {
                return new URL(val);
              } catch (ex) {
                // Invalid URL, so filter out below
                Cu.reportError(`Invalid once url: ${ex}`);
                return null;
              }
            })
            .filter(
              parsed =>
                parsed &&
                parsed.protocol == "https:" &&
                // Only accept exact hostname or subdomain; without port
                ONCE_DOMAINS.includes(
                  Services.eTLD.getBaseDomainFromHost(parsed.host)
                )
            )
            .join("|");

          // Be noisy as properly configured urls should be unchanged
          if (overridePage != url) {
            Cu.reportError(`Mismatched once urls: ${url}`);
          }
776
777
778
        }
      } catch (ex) {
        // Invalid json pref, so ignore (and clear below)
779
        Cu.reportError(`Invalid once pref: ${ex}`);
780
781
782
783
784
      } finally {
        prefb.clearUserPref(ONCE_PREF);
      }
    }

785
786
    if (!additionalPage) {
      additionalPage = LaterRun.getURL() || "";
787
788
    }

789
790
791
792
793
794
795
796
    if (additionalPage && additionalPage != "about:blank") {
      if (overridePage) {
        overridePage += "|" + additionalPage;
      } else {
        overridePage = additionalPage;
      }
    }

797
    var startPage = "";
798
799
    try {
      var choice = prefb.getIntPref("browser.startup.page");
800
      if (choice == 1 || choice == 3) {
801
        startPage = HomePage.get();
802
      }
803
    } catch (e) {
804
      Cu.reportError(e);
805
    }
806

807
    if (startPage == "about:blank") {
808
      startPage = "";
809
    }
810

811
812
813
    let skipStartPage =
      (override == OVERRIDE_NEW_PROFILE ||
        override == OVERRIDE_ALTERNATE_PROFILE) &&
814
815
816
      prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
    // Only show the startPage if we're not restoring an update session and are
    // not set to skip the start page on this profile
817
    if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
818
      return overridePage + "|" + startPage;
819
    }
820

821
    return overridePage || startPage || "about:blank";
822
823
  },

824
  mFeatures: null,
825

826
  getFeatures: function bch_features(cmdLine) {
827
828
829
    if (this.mFeatures === null) {
      this.mFeatures = "";

830
831
832
833
      if (cmdLine) {
        try {
          var width = cmdLine.handleFlagWithParam("width", false);
          var height = cmdLine.handleFlagWithParam("height", false);
834
835
          var left = cmdLine.handleFlagWithParam("left", false);
          var top = cmdLine.handleFlagWithParam("top", false);
836

837
          if (width) {
838
            this.mFeatures += ",width=" + width;
839
840
          }
          if (height) {
841
            this.mFeatures += ",height=" + height;
842
          }
843
844
845
846
847
848
          if (left) {
            this.mFeatures += ",left=" + left;
          }
          if (top) {
            this.mFeatures += ",top=" + top;
          }
849
        } catch (e) {}
850
      }
851
852
853
854

      // The global PB Service consumes this flag, so only eat it in per-window
      // PB builds.
      if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
855
856
857
        this.mFeatures += ",private";
      }

858
859
860
861
      if (
        Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
        !Services.wm.getMostRecentWindow("navigator:browser")
      ) {
862
        this.mFeatures += ",suppressanimation";
863
      }
864
865
866
867
868
    }

    return this.mFeatures;
  },

869
870
871
872
  get kiosk() {
    return gKiosk;
  },

873
874
  /* nsIContentHandler */

875
  handleContent: function bch_handleContent(contentType, context, request) {
876
877
    const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;

878
    try {
879
880
881
      var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
        Ci.nsIWebNavigationInfo
      );
882
883
884
      if (!webNavInfo.isTypeSupported(contentType, null)) {
        throw NS_ERROR_WONT_HANDLE_CONTENT;
      }
885
886
887
888
    } catch (e) {
      throw NS_ERROR_WONT_HANDLE_CONTENT;
    }

889
    request.QueryInterface(Ci.nsIChannel);
890
891
892
893
894
895
896
    handURIToExistingBrowser(
      request.URI,
      Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
      null,
      false,
      request.loadInfo.triggeringPrincipal
    );
897
    request.cancel(Cr.NS_BINDING_ABORTED);
898
899
  },

900
  /* nsICommandLineValidator */
901
  validate: function bch_validate(cmdLine) {
902
    var urlFlagIdx = cmdLine.findFlag("url", false);
903
904
    if (
      urlFlagIdx > -1 &&
905
      cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
906
    ) {
907
      var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
908
909
910
911
      if (
        cmdLine.length != urlFlagIdx + 2 ||
        /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
      ) {
912
        throw Components.Exception("", Cr.NS_ERROR_ABORT);
913
      }
914
915
      var isDefault = false;
      try {
916
917
918
        var url =
          Services.urlFormatter.formatURLPref("app.support.baseURL") +
          "win10-default-browser";
919
        if (urlParam == url) {
920
          isDefault = ShellService.isDefaultBrowser(false, false);
921
922
923
924
925
        }
      } catch (ex) {}
      if (isDefault) {
        // Firefox is already the default HTTP handler.
        // We don't have to show the instruction page.
926
        throw Components.Exception("", Cr.NS_ERROR_ABORT);
927
      }
928
929
    }
  },
930
};
931
var gBrowserContentHandler = new nsBrowserContentHandler();
932

933
934
935
936
937
938
939
940
function handURIToExistingBrowser(
  uri,
  location,
  cmdLine,
  forcePrivate,
  triggeringPrincipal
) {
  if (!shouldLoadURI(uri)) {
941
    return;
942
  }
943

944
945
  // Unless using a private window is forced, open external links in private
  // windows only if we're in perma-private mode.
946
947
948
  var allowPrivate =
    forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
  var navWin = BrowserWindowTracker.getTopWindow({ private: allowPrivate });
949
950
  if (!navWin) {
    // if we couldn't load it in an existing window, open a new one
951
952
953
954
955
956
957
    openBrowserWindow(
      cmdLine,
      triggeringPrincipal,
      uri.spec,
      null,
      forcePrivate
    );
958
959
960
    return;
  }

961
  var bwin = navWin.browserDOMWindow;
962
963
964
965
966
967
968
  bwin.openURI(
    uri,
    null,
    location,
    Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
    triggeringPrincipal
  );
969
970
}

971
function nsDefaultCommandLineHandler() {}
972
973

nsDefaultCommandLineHandler.prototype = {
974
  /* nsISupports */
975
  QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
976

977
  _haveProfile: false,
978

979
  /* nsICommandLineHandler */
980
  handle: function dch_handle(cmdLine) {
981
982
    var urilist = [];

983
984