зеркало из https://github.com/mozilla/gecko-dev.git
1066 строки
33 KiB
JavaScript
1066 строки
33 KiB
JavaScript
/* -*- 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. */
|
|
|
|
/* eslint-env mozilla/frame-script */
|
|
|
|
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
|
|
"resource:///modules/E10SUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
|
|
"resource://gre/modules/BrowserUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
|
"resource://gre/modules/sessionstore/Utils.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:MaybeShowMigrateMessage");
|
|
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");
|
|
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, aTriggeringPrincipal) {
|
|
// 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,
|
|
triggeringPrincipal: Utils.serializePrincipal(aTriggeringPrincipal),
|
|
});
|
|
|
|
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, aTriggeringPrincipal) {
|
|
if (PrerenderContentHandler.initialized) {
|
|
PrerenderContentHandler.startPrerenderingDocument(aHref, aReferrer, aTriggeringPrincipal);
|
|
}
|
|
},
|
|
|
|
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);
|
|
},
|
|
|
|
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 = Services.io.newURI(data.URI, data.originCharset);
|
|
|
|
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();
|
|
|
|
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
|
|
|
|
addEventListener("unload", () => {
|
|
RefreshBlocker.uninit();
|
|
});
|
|
|
|
addMessageListener("AllowScriptsToClose", () => {
|
|
content.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils)
|
|
.allowScriptsToClose();
|
|
});
|
|
|
|
addEventListener("MozAfterPaint", function onFirstPaint() {
|
|
removeEventListener("MozAfterPaint", onFirstPaint);
|
|
sendAsyncMessage("Browser:FirstPaint");
|
|
});
|