summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/tab-content.js
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-02 03:32:58 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-02 03:32:58 -0500
commite72ef92b5bdc43cd2584198e2e54e951b70299e8 (patch)
tree01ceb4a897c33eca9e7ccf2bc3aefbe530169fe5 /application/basilisk/base/content/tab-content.js
parent0d19b77d3eaa5b8d837bf52c19759e68e42a1c4c (diff)
downloadUXP-e72ef92b5bdc43cd2584198e2e54e951b70299e8.tar
UXP-e72ef92b5bdc43cd2584198e2e54e951b70299e8.tar.gz
UXP-e72ef92b5bdc43cd2584198e2e54e951b70299e8.tar.lz
UXP-e72ef92b5bdc43cd2584198e2e54e951b70299e8.tar.xz
UXP-e72ef92b5bdc43cd2584198e2e54e951b70299e8.zip
Add Basilisk
Diffstat (limited to 'application/basilisk/base/content/tab-content.js')
-rw-r--r--application/basilisk/base/content/tab-content.js1061
1 files changed, 1061 insertions, 0 deletions
diff --git a/application/basilisk/base/content/tab-content.js b/application/basilisk/base/content/tab-content.js
new file mode 100644
index 000000000..062694fd6
--- /dev/null
+++ b/application/basilisk/base/content/tab-content.js
@@ -0,0 +1,1061 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+/* This content script contains code that requires a tab browser. */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ExtensionContent.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+ "resource:///modules/E10SUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutReader",
+ "resource://gre/modules/AboutReader.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
+ let ssdp = Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm", {}).SimpleServiceDiscovery;
+ // Register targets
+ ssdp.registerDevice({
+ id: "roku:ecp",
+ target: "roku:ecp",
+ factory(aService) {
+ Cu.import("resource://gre/modules/RokuApp.jsm");
+ return new RokuApp(aService);
+ },
+ types: ["video/mp4"],
+ extensions: ["mp4"]
+ });
+ return ssdp;
+});
+
+// TabChildGlobal
+var global = this;
+
+
+addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
+ sendAsyncMessage("PointerLock:Entered", {
+ originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
+ });
+});
+
+addEventListener("MozDOMPointerLock:Exited", function(aEvent) {
+ sendAsyncMessage("PointerLock:Exited");
+});
+
+
+addMessageListener("Browser:HideSessionRestoreButton", function(message) {
+ // Hide session restore button on about:home
+ let doc = content.document;
+ let container;
+ if (doc.documentURI.toLowerCase() == "about:home" &&
+ (container = doc.getElementById("sessionRestoreContainer"))) {
+ container.hidden = true;
+ }
+});
+
+
+addMessageListener("Browser:Reload", function(message) {
+ /* First, we'll try to use the session history object to reload so
+ * that framesets are handled properly. If we're in a special
+ * window (such as view-source) that has no session history, fall
+ * back on using the web navigation's reload method.
+ */
+
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ try {
+ let sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(Ci.nsIWebNavigation);
+ } catch (e) {
+ }
+
+ let reloadFlags = message.data.flags;
+ try {
+ E10SUtils.wrapHandlingUserInput(content, message.data.handlingUserInput,
+ () => webNav.reload(reloadFlags));
+ } catch (e) {
+ }
+});
+
+addMessageListener("MixedContent:ReenableProtection", function() {
+ docShell.mixedContentChannel = null;
+});
+
+addMessageListener("SecondScreen:tab-mirror", function(message) {
+ if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
+ return;
+ }
+ let app = SimpleServiceDiscovery.findAppForService(message.data.service);
+ if (app) {
+ let width = content.innerWidth;
+ let height = content.innerHeight;
+ let viewport = {cssWidth: width, cssHeight: height, width, height};
+ app.mirror(function() {}, content, viewport, function() {}, content);
+ }
+});
+
+var AboutHomeListener = {
+ init(chromeGlobal) {
+ chromeGlobal.addEventListener("AboutHomeLoad", this, false, true);
+ },
+
+ get isAboutHome() {
+ return content.document.documentURI.toLowerCase() == "about:home";
+ },
+
+ handleEvent(aEvent) {
+ if (!this.isAboutHome) {
+ return;
+ }
+ switch (aEvent.type) {
+ case "AboutHomeLoad":
+ this.onPageLoad();
+ break;
+ case "click":
+ this.onClick(aEvent);
+ break;
+ case "pagehide":
+ this.onPageHide(aEvent);
+ break;
+ }
+ },
+
+ receiveMessage(aMessage) {
+ if (!this.isAboutHome) {
+ return;
+ }
+ switch (aMessage.name) {
+ case "AboutHome:Update":
+ this.onUpdate(aMessage.data);
+ break;
+ }
+ },
+
+ onUpdate(aData) {
+ let doc = content.document;
+ if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content))
+ doc.getElementById("launcher").setAttribute("session", "true");
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ // Set snippetsVersion last, which triggers to show the snippets when it's set.
+ docElt.setAttribute("snippetsURL", aData.snippetsURL);
+ if (aData.showKnowYourRights)
+ docElt.setAttribute("showKnowYourRights", "true");
+ docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
+ },
+
+ onPageLoad() {
+ addMessageListener("AboutHome:Update", this);
+ addEventListener("click", this, true);
+ addEventListener("pagehide", this, true);
+
+ sendAsyncMessage("AboutHome:MaybeShowAutoMigrationUndoNotification");
+ sendAsyncMessage("AboutHome:RequestUpdate");
+ },
+
+ onClick(aEvent) {
+ if (!aEvent.isTrusted || // Don't trust synthetic events
+ aEvent.button == 2 || aEvent.target.localName != "button") {
+ return;
+ }
+
+ let originalTarget = aEvent.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+ if (ownerDoc.documentURI != "about:home") {
+ // This shouldn't happen, but we're being defensive.
+ return;
+ }
+
+ let elmId = originalTarget.getAttribute("id");
+
+ switch (elmId) {
+ case "restorePreviousSession":
+ sendAsyncMessage("AboutHome:RestorePreviousSession");
+ ownerDoc.getElementById("launcher").removeAttribute("session");
+ break;
+
+ case "downloads":
+ sendAsyncMessage("AboutHome:Downloads");
+ break;
+
+ case "bookmarks":
+ sendAsyncMessage("AboutHome:Bookmarks");
+ break;
+
+ case "history":
+ sendAsyncMessage("AboutHome:History");
+ break;
+
+ case "addons":
+ sendAsyncMessage("AboutHome:Addons");
+ break;
+
+ case "sync":
+ sendAsyncMessage("AboutHome:Sync");
+ break;
+
+ case "settings":
+ sendAsyncMessage("AboutHome:Settings");
+ break;
+ }
+ },
+
+ onPageHide(aEvent) {
+ if (aEvent.target.defaultView.frameElement) {
+ return;
+ }
+ removeMessageListener("AboutHome:Update", this);
+ removeEventListener("click", this, true);
+ removeEventListener("pagehide", this, true);
+ },
+};
+AboutHomeListener.init(this);
+
+var AboutPrivateBrowsingListener = {
+ init(chromeGlobal) {
+ chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
+ false, true);
+ chromeGlobal.addEventListener("AboutPrivateBrowsingToggleTrackingProtection", this,
+ false, true);
+ chromeGlobal.addEventListener("AboutPrivateBrowsingDontShowIntroPanelAgain", this,
+ false, true);
+ },
+
+ get isAboutPrivateBrowsing() {
+ return content.document.documentURI.toLowerCase() == "about:privatebrowsing";
+ },
+
+ handleEvent(aEvent) {
+ if (!this.isAboutPrivateBrowsing) {
+ return;
+ }
+ switch (aEvent.type) {
+ case "AboutPrivateBrowsingOpenWindow":
+ sendAsyncMessage("AboutPrivateBrowsing:OpenPrivateWindow");
+ break;
+ case "AboutPrivateBrowsingToggleTrackingProtection":
+ sendAsyncMessage("AboutPrivateBrowsing:ToggleTrackingProtection");
+ break;
+ case "AboutPrivateBrowsingDontShowIntroPanelAgain":
+ sendAsyncMessage("AboutPrivateBrowsing:DontShowIntroPanelAgain");
+ break;
+ }
+ },
+};
+AboutPrivateBrowsingListener.init(this);
+
+var AboutReaderListener = {
+
+ _articlePromise: null,
+
+ _isLeavingReaderableReaderMode: false,
+
+ init() {
+ addEventListener("AboutReaderContentLoaded", this, false, true);
+ addEventListener("DOMContentLoaded", this, false);
+ addEventListener("pageshow", this, false);
+ addEventListener("pagehide", this, false);
+ addMessageListener("Reader:ToggleReaderMode", this);
+ addMessageListener("Reader:PushState", this);
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "Reader:ToggleReaderMode":
+ if (!this.isAboutReader) {
+ this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
+ ReaderMode.enterReaderMode(docShell, content);
+ } else {
+ this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
+ ReaderMode.leaveReaderMode(docShell, content);
+ }
+ break;
+
+ case "Reader:PushState":
+ this.updateReaderButton(!!(message.data && message.data.isArticle));
+ break;
+ }
+ },
+
+ get isAboutReader() {
+ if (!content) {
+ return false;
+ }
+ return content.document.documentURI.startsWith("about:reader");
+ },
+
+ get isReaderableAboutReader() {
+ return this.isAboutReader &&
+ !content.document.documentElement.dataset.isError;
+ },
+
+ handleEvent(aEvent) {
+ if (aEvent.originalTarget.defaultView != content) {
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "AboutReaderContentLoaded":
+ if (!this.isAboutReader) {
+ return;
+ }
+
+ if (content.document.body) {
+ // Update the toolbar icon to show the "reader active" icon.
+ sendAsyncMessage("Reader:UpdateReaderButton");
+ new AboutReader(global, content, this._articlePromise);
+ this._articlePromise = null;
+ }
+ break;
+
+ case "pagehide":
+ this.cancelPotentialPendingReadabilityCheck();
+ // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
+ // visible in the location bar when transitioning from reader-mode page
+ // back to the readable source page.
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode });
+ if (this._isLeavingReaderableReaderMode) {
+ this._isLeavingReaderableReaderMode = false;
+ }
+ break;
+
+ case "pageshow":
+ // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
+ // event, so we need to rely on "pageshow" in this case.
+ if (aEvent.persisted) {
+ this.updateReaderButton();
+ }
+ break;
+ case "DOMContentLoaded":
+ this.updateReaderButton();
+ break;
+
+ }
+ },
+
+ /**
+ * NB: this function will update the state of the reader button asynchronously
+ * after the next mozAfterPaint call (assuming reader mode is enabled and
+ * this is a suitable document). Calling it on things which won't be
+ * painted is not going to work.
+ */
+ updateReaderButton(forceNonArticle) {
+ if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
+ !content || !(content.document instanceof content.HTMLDocument) ||
+ content.document.mozSyntheticDocument) {
+ return;
+ }
+
+ this.scheduleReadabilityCheckPostPaint(forceNonArticle);
+ },
+
+ cancelPotentialPendingReadabilityCheck() {
+ if (this._pendingReadabilityCheck) {
+ removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
+ delete this._pendingReadabilityCheck;
+ }
+ },
+
+ scheduleReadabilityCheckPostPaint(forceNonArticle) {
+ if (this._pendingReadabilityCheck) {
+ // We need to stop this check before we re-add one because we don't know
+ // if forceNonArticle was true or false last time.
+ this.cancelPotentialPendingReadabilityCheck();
+ }
+ this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(this, forceNonArticle);
+ addEventListener("MozAfterPaint", this._pendingReadabilityCheck);
+ },
+
+ onPaintWhenWaitedFor(forceNonArticle, event) {
+ // In non-e10s, we'll get called for paints other than ours, and so it's
+ // possible that this page hasn't been laid out yet, in which case we
+ // should wait until we get an event that does relate to our layout. We
+ // determine whether any of our content got painted by checking if there
+ // are any painted rects.
+ if (!event.clientRects.length) {
+ return;
+ }
+
+ this.cancelPotentialPendingReadabilityCheck();
+ // Only send updates when there are articles; there's no point updating with
+ // |false| all the time.
+ if (ReaderMode.isProbablyReaderable(content.document)) {
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
+ } else if (forceNonArticle) {
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
+ }
+ },
+};
+AboutReaderListener.init();
+
+
+var ContentSearchMediator = {
+
+ whitelist: new Set([
+ "about:home",
+ "about:newtab",
+ ]),
+
+ init(chromeGlobal) {
+ chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
+ addMessageListener("ContentSearch", this);
+ },
+
+ handleEvent(event) {
+ if (this._contentWhitelisted) {
+ this._sendMsg(event.detail.type, event.detail.data);
+ }
+ },
+
+ receiveMessage(msg) {
+ if (msg.data.type == "AddToWhitelist") {
+ for (let uri of msg.data.data) {
+ this.whitelist.add(uri);
+ }
+ this._sendMsg("AddToWhitelistAck");
+ return;
+ }
+ if (this._contentWhitelisted) {
+ this._fireEvent(msg.data.type, msg.data.data);
+ }
+ },
+
+ get _contentWhitelisted() {
+ return this.whitelist.has(content.document.documentURI);
+ },
+
+ _sendMsg(type, data = null) {
+ sendAsyncMessage("ContentSearch", {
+ type,
+ data,
+ });
+ },
+
+ _fireEvent(type, data = null) {
+ let event = Cu.cloneInto({
+ detail: {
+ type,
+ data,
+ },
+ }, content);
+ content.dispatchEvent(new content.CustomEvent("ContentSearchService",
+ event));
+ },
+};
+ContentSearchMediator.init(this);
+
+var PageStyleHandler = {
+ init() {
+ addMessageListener("PageStyle:Switch", this);
+ addMessageListener("PageStyle:Disable", this);
+ addEventListener("pageshow", () => this.sendStyleSheetInfo());
+ },
+
+ get markupDocumentViewer() {
+ return docShell.contentViewer;
+ },
+
+ sendStyleSheetInfo() {
+ let filteredStyleSheets = this._filterStyleSheets(this.getAllStyleSheets());
+
+ sendAsyncMessage("PageStyle:StyleSheets", {
+ filteredStyleSheets,
+ authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
+ preferredStyleSheetSet: content.document.preferredStyleSheetSet
+ });
+ },
+
+ getAllStyleSheets(frameset = content) {
+ let selfSheets = Array.slice(frameset.document.styleSheets);
+ let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
+ return selfSheets.concat(...subSheets);
+ },
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "PageStyle:Switch":
+ this.markupDocumentViewer.authorStyleDisabled = false;
+ this._stylesheetSwitchAll(content, msg.data.title);
+ break;
+
+ case "PageStyle:Disable":
+ this.markupDocumentViewer.authorStyleDisabled = true;
+ break;
+ }
+
+ this.sendStyleSheetInfo();
+ },
+
+ _stylesheetSwitchAll(frameset, title) {
+ if (!title || this._stylesheetInFrame(frameset, title)) {
+ this._stylesheetSwitchFrame(frameset, title);
+ }
+
+ for (let i = 0; i < frameset.frames.length; i++) {
+ // Recurse into sub-frames.
+ this._stylesheetSwitchAll(frameset.frames[i], title);
+ }
+ },
+
+ _stylesheetSwitchFrame(frame, title) {
+ var docStyleSheets = frame.document.styleSheets;
+
+ for (let i = 0; i < docStyleSheets.length; ++i) {
+ let docStyleSheet = docStyleSheets[i];
+ if (docStyleSheet.title) {
+ docStyleSheet.disabled = (docStyleSheet.title != title);
+ } else if (docStyleSheet.disabled) {
+ docStyleSheet.disabled = false;
+ }
+ }
+ },
+
+ _stylesheetInFrame(frame, title) {
+ return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
+ },
+
+ _filterStyleSheets(styleSheets) {
+ let result = [];
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.title)
+ continue;
+
+ // Skip any stylesheets that don't match the screen media type.
+ if (currentStyleSheet.media.length > 0) {
+ let mediaQueryList = currentStyleSheet.media.mediaText;
+ if (!content.matchMedia(mediaQueryList).matches) {
+ continue;
+ }
+ }
+
+ let URI;
+ try {
+ if (!currentStyleSheet.ownerNode ||
+ // special-case style nodes, which have no href
+ currentStyleSheet.ownerNode.nodeName.toLowerCase() != "style") {
+ URI = Services.io.newURI(currentStyleSheet.href);
+ }
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw e;
+ }
+ continue;
+ }
+
+ // We won't send data URIs all of the way up to the parent, as these
+ // can be arbitrarily large.
+ let sentURI = (!URI || URI.scheme == "data") ? null : URI.spec;
+
+ result.push({
+ title: currentStyleSheet.title,
+ disabled: currentStyleSheet.disabled,
+ href: sentURI,
+ });
+ }
+
+ return result;
+ },
+};
+PageStyleHandler.init();
+
+// Keep a reference to the translation content handler to avoid it it being GC'ed.
+var trHandler = null;
+if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
+ Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
+ trHandler = new TranslationContentHandler(global, docShell);
+}
+
+function gKeywordURIFixup(fixupInfo) {
+ fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
+ if (!fixupInfo.consumer) {
+ return;
+ }
+
+ // Ignore info from other docshells
+ let parent = fixupInfo.consumer.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeRootTreeItem;
+ if (parent != docShell)
+ return;
+
+ let data = {};
+ for (let f of Object.keys(fixupInfo)) {
+ if (f == "consumer" || typeof fixupInfo[f] == "function")
+ continue;
+
+ if (fixupInfo[f] && fixupInfo[f] instanceof Ci.nsIURI) {
+ data[f] = fixupInfo[f].spec;
+ } else {
+ data[f] = fixupInfo[f];
+ }
+ }
+
+ sendAsyncMessage("Browser:URIFixup", data);
+}
+Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
+addEventListener("unload", () => {
+ Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
+}, false);
+
+addMessageListener("Browser:AppTab", function(message) {
+ if (docShell) {
+ docShell.isAppTab = message.data.isAppTab;
+ }
+});
+
+let PrerenderContentHandler = {
+ init() {
+ this._pending = [];
+ this._idMonotonic = 0;
+ this._initialized = true;
+ addMessageListener("Prerender:Canceled", this);
+ addMessageListener("Prerender:Swapped", this);
+ },
+
+ get initialized() {
+ return !!this._initialized;
+ },
+
+ receiveMessage(aMessage) {
+ switch (aMessage.name) {
+ case "Prerender:Canceled": {
+ for (let i = 0; i < this._pending.length; ++i) {
+ if (this._pending[i].id === aMessage.data.id) {
+ if (this._pending[i].failure) {
+ this._pending[i].failure.run();
+ }
+ // Remove the item from the array
+ this._pending.splice(i, 1);
+ break;
+ }
+ }
+ break;
+ }
+ case "Prerender:Swapped": {
+ for (let i = 0; i < this._pending.length; ++i) {
+ if (this._pending[i].id === aMessage.data.id) {
+ if (this._pending[i].success) {
+ this._pending[i].success.run();
+ }
+ // Remove the item from the array
+ this._pending.splice(i, 1);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ startPrerenderingDocument(aHref, aReferrer) {
+ // XXX: Make this constant a pref
+ if (this._pending.length >= 2) {
+ return;
+ }
+
+ let id = ++this._idMonotonic;
+ sendAsyncMessage("Prerender:Request", {
+ href: aHref.spec,
+ referrer: aReferrer ? aReferrer.spec : null,
+ id,
+ });
+
+ this._pending.push({
+ href: aHref,
+ referrer: aReferrer,
+ id,
+ success: null,
+ failure: null,
+ });
+ },
+
+ shouldSwitchToPrerenderedDocument(aHref, aReferrer, aSuccess, aFailure) {
+ // Check if we think there is a prerendering document pending for the given
+ // href and referrer. If we think there is one, we will send a message to
+ // the parent process asking it to do a swap, and hook up the success and
+ // failure listeners.
+ for (let i = 0; i < this._pending.length; ++i) {
+ let p = this._pending[i];
+ if (p.href.equals(aHref) && p.referrer.equals(aReferrer)) {
+ p.success = aSuccess;
+ p.failure = aFailure;
+ sendAsyncMessage("Prerender:Swap", {id: p.id});
+ return true;
+ }
+ }
+
+ return false;
+ }
+};
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ // We only want to initialize the PrerenderContentHandler in the content
+ // process. Outside of the content process, this should be unused.
+ PrerenderContentHandler.init();
+}
+
+var WebBrowserChrome = {
+ onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
+ return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ },
+
+ // Check whether this URI should load in the current process
+ shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
+ if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
+ return false;
+ }
+
+ return true;
+ },
+
+ shouldLoadURIInThisProcess(aURI) {
+ return E10SUtils.shouldLoadURIInThisProcess(aURI);
+ },
+
+ // Try to reload the currently active or currently loading page in a new process.
+ reloadInFreshProcess(aDocShell, aURI, aReferrer, aTriggeringPrincipal, aLoadFlags) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, true, aLoadFlags);
+ return true;
+ },
+
+ startPrerenderingDocument(aHref, aReferrer) {
+ if (PrerenderContentHandler.initialized) {
+ PrerenderContentHandler.startPrerenderingDocument(aHref, aReferrer);
+ }
+ },
+
+ shouldSwitchToPrerenderedDocument(aHref, aReferrer, aSuccess, aFailure) {
+ if (PrerenderContentHandler.initialized) {
+ return PrerenderContentHandler.shouldSwitchToPrerenderedDocument(
+ aHref, aReferrer, aSuccess, aFailure);
+ }
+ return false;
+ }
+};
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild);
+ tabchild.webBrowserChrome = WebBrowserChrome;
+}
+
+
+var DOMFullscreenHandler = {
+
+ init() {
+ addMessageListener("DOMFullscreen:Entered", this);
+ addMessageListener("DOMFullscreen:CleanUp", this);
+ addEventListener("MozDOMFullscreen:Request", this);
+ addEventListener("MozDOMFullscreen:Entered", this);
+ addEventListener("MozDOMFullscreen:NewOrigin", this);
+ addEventListener("MozDOMFullscreen:Exit", this);
+ addEventListener("MozDOMFullscreen:Exited", this);
+ },
+
+ get _windowUtils() {
+ if (!content) {
+ return null;
+ }
+ return content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ receiveMessage(aMessage) {
+ let windowUtils = this._windowUtils;
+ switch (aMessage.name) {
+ case "DOMFullscreen:Entered": {
+ this._lastTransactionId = windowUtils.lastTransactionId;
+ if (!windowUtils.handleFullscreenRequests() &&
+ !content.document.fullscreenElement) {
+ // If we don't actually have any pending fullscreen request
+ // to handle, neither we have been in fullscreen, tell the
+ // parent to just exit.
+ sendAsyncMessage("DOMFullscreen:Exit");
+ }
+ break;
+ }
+ case "DOMFullscreen:CleanUp": {
+ // If we've exited fullscreen at this point, no need to record
+ // transaction id or call exit fullscreen. This is especially
+ // important for non-e10s, since in that case, it is possible
+ // that no more paint would be triggered after this point.
+ if (content.document.fullscreenElement && windowUtils) {
+ this._lastTransactionId = windowUtils.lastTransactionId;
+ windowUtils.exitFullscreen();
+ }
+ break;
+ }
+ }
+ },
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozDOMFullscreen:Request": {
+ sendAsyncMessage("DOMFullscreen:Request");
+ break;
+ }
+ case "MozDOMFullscreen:NewOrigin": {
+ sendAsyncMessage("DOMFullscreen:NewOrigin", {
+ originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix,
+ });
+ break;
+ }
+ case "MozDOMFullscreen:Exit": {
+ sendAsyncMessage("DOMFullscreen:Exit");
+ break;
+ }
+ case "MozDOMFullscreen:Entered":
+ case "MozDOMFullscreen:Exited": {
+ addEventListener("MozAfterPaint", this);
+ if (!content || !content.document.fullscreenElement) {
+ // If we receive any fullscreen change event, and find we are
+ // actually not in fullscreen, also ask the parent to exit to
+ // ensure that the parent always exits fullscreen when we do.
+ sendAsyncMessage("DOMFullscreen:Exit");
+ }
+ break;
+ }
+ case "MozAfterPaint": {
+ // Only send Painted signal after we actually finish painting
+ // the transition for the fullscreen change.
+ // Note that this._lastTransactionId is not set when in non-e10s
+ // mode, so we need to check that explicitly.
+ if (!this._lastTransactionId ||
+ aEvent.transactionId > this._lastTransactionId) {
+ removeEventListener("MozAfterPaint", this);
+ sendAsyncMessage("DOMFullscreen:Painted");
+ }
+ break;
+ }
+ }
+ }
+};
+DOMFullscreenHandler.init();
+
+var RefreshBlocker = {
+ PREF: "accessibility.blockautorefresh",
+
+ // Bug 1247100 - When a refresh is caused by an HTTP header,
+ // onRefreshAttempted will be fired before onLocationChange.
+ // When a refresh is caused by a <meta> tag in the document,
+ // onRefreshAttempted will be fired after onLocationChange.
+ //
+ // We only ever want to send a message to the parent after
+ // onLocationChange has fired, since the parent uses the
+ // onLocationChange update to clear transient notifications.
+ // Sending the message before onLocationChange will result in
+ // us creating the notification, and then clearing it very
+ // soon after.
+ //
+ // To account for both cases (onRefreshAttempted before
+ // onLocationChange, and onRefreshAttempted after onLocationChange),
+ // we'll hold a mapping of DOM Windows that we see get
+ // sent through both onLocationChange and onRefreshAttempted.
+ // When either run, they'll check the WeakMap for the existence
+ // of the DOM Window. If it doesn't exist, it'll add it. If
+ // it finds it, it'll know that it's safe to send the message
+ // to the parent, since we know that both have fired.
+ //
+ // The DOM Window is removed from blockedWindows when we notice
+ // the nsIWebProgress change state to STATE_STOP for the
+ // STATE_IS_WINDOW case.
+ //
+ // DOM Windows are mapped to a JS object that contains the data
+ // to be sent to the parent to show the notification. Since that
+ // data is only known when onRefreshAttempted is fired, it's only
+ // ever stashed in the map if onRefreshAttempted fires first -
+ // otherwise, null is set as the value of the mapping.
+ blockedWindows: new WeakMap(),
+
+ init() {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.enable();
+ }
+
+ Services.prefs.addObserver(this.PREF, this, false);
+ },
+
+ uninit() {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.disable();
+ }
+
+ Services.prefs.removeObserver(this.PREF, this);
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed" && data == this.PREF) {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ }
+ },
+
+ enable() {
+ this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
+
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
+
+ addMessageListener("RefreshBlocker:Refresh", this);
+ },
+
+ disable() {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.removeProgressListener(this._filter);
+
+ this._filter.removeProgressListener(this);
+ this._filter = null;
+
+ removeMessageListener("RefreshBlocker:Refresh", this);
+ },
+
+ send(data) {
+ sendAsyncMessage("RefreshBlocker:Blocked", data);
+ },
+
+ /**
+ * Notices when the nsIWebProgress transitions to STATE_STOP for
+ * the STATE_IS_WINDOW case, which will clear any mappings from
+ * blockedWindows.
+ */
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this.blockedWindows.delete(aWebProgress.DOMWindow);
+ }
+ },
+
+ /**
+ * Notices when the location has changed. If, when running,
+ * onRefreshAttempted has already fired for this DOM Window, will
+ * send the appropriate refresh blocked data to the parent.
+ */
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ let win = aWebProgress.DOMWindow;
+ if (this.blockedWindows.has(win)) {
+ let data = this.blockedWindows.get(win);
+ if (data) {
+ // We saw onRefreshAttempted before onLocationChange, so
+ // send the message to the parent to show the notification.
+ this.send(data);
+ }
+ } else {
+ this.blockedWindows.set(win, null);
+ }
+ },
+
+ /**
+ * Notices when a refresh / reload was attempted. If, when running,
+ * onLocationChange has not yet run, will stash the appropriate data
+ * into the blockedWindows map to be sent when onLocationChange fires.
+ */
+ onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+ let win = aWebProgress.DOMWindow;
+ let outerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+
+ let data = {
+ URI: aURI.spec,
+ originCharset: aURI.originCharset,
+ delay: aDelay,
+ sameURI: aSameURI,
+ outerWindowID,
+ };
+
+ if (this.blockedWindows.has(win)) {
+ // onLocationChange must have fired before, so we can tell the
+ // parent to show the notification.
+ this.send(data);
+ } else {
+ // onLocationChange hasn't fired yet, so stash the data in the
+ // map so that onLocationChange can send it when it fires.
+ this.blockedWindows.set(win, data);
+ }
+
+ return false;
+ },
+
+ receiveMessage(message) {
+ let data = message.data;
+
+ if (message.name == "RefreshBlocker:Refresh") {
+ let win = Services.wm.getOuterWindowWithId(data.outerWindowID);
+ let refreshURI = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIRefreshURI);
+
+ let URI = BrowserUtils.makeURI(data.URI, data.originCharset, null);
+
+ refreshURI.forceRefreshURI(URI, data.delay, true);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener2,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+};
+
+RefreshBlocker.init();
+
+var UserContextIdNotifier = {
+ init() {
+ addEventListener("DOMWindowCreated", this);
+ },
+
+ uninit() {
+ removeEventListener("DOMWindowCreated", this);
+ },
+
+ handleEvent(aEvent) {
+ // When the window is created, we want to inform the tabbrowser about
+ // the userContextId in use in order to update the UI correctly.
+ // Just because we cannot change the userContextId from an active docShell,
+ // we don't need to check DOMContentLoaded again.
+ this.uninit();
+
+ // We use the docShell because content.document can have been loaded before
+ // setting the originAttributes.
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ let userContextId = loadContext.originAttributes.userContextId;
+
+ sendAsyncMessage("Browser:WindowCreated", { userContextId });
+ }
+};
+
+UserContextIdNotifier.init();
+
+ExtensionContent.init(this);
+addEventListener("unload", () => {
+ ExtensionContent.uninit(this);
+ RefreshBlocker.uninit();
+});
+
+addMessageListener("AllowScriptsToClose", () => {
+ content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .allowScriptsToClose();
+});
+
+addEventListener("MozAfterPaint", function onFirstPaint() {
+ removeEventListener("MozAfterPaint", onFirstPaint);
+ sendAsyncMessage("Browser:FirstPaint");
+});