mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
551 lines
17 KiB
JavaScript
551 lines
17 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/. */
|
|
|
|
let {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, "ContentLinkHandler",
|
|
"resource:///modules/ContentLinkHandler.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
|
|
"resource://gre/modules/LoginManagerContent.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
|
|
"resource://gre/modules/InsecurePasswordUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
|
"resource:///modules/UITour.jsm");
|
|
|
|
// Creates a new nsIURI object.
|
|
function makeURI(uri, originCharset, baseURI) {
|
|
return Services.io.newURI(uri, originCharset, baseURI);
|
|
}
|
|
|
|
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;
|
|
let handlingUserInput;
|
|
try {
|
|
handlingUserInput = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils)
|
|
.setHandlingUserInput(message.data.handlingUserInput);
|
|
webNav.reload(reloadFlags);
|
|
} catch (e) {
|
|
} finally {
|
|
handlingUserInput.destruct();
|
|
}
|
|
});
|
|
|
|
addEventListener("DOMFormHasPassword", function(event) {
|
|
InsecurePasswordUtils.checkForInsecurePasswords(event.target);
|
|
LoginManagerContent.onFormPassword(event);
|
|
});
|
|
addEventListener("DOMAutoComplete", function(event) {
|
|
LoginManagerContent.onUsernameInput(event);
|
|
});
|
|
addEventListener("blur", function(event) {
|
|
LoginManagerContent.onUsernameInput(event);
|
|
});
|
|
|
|
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
|
addEventListener("contextmenu", function (event) {
|
|
sendSyncMessage("contextmenu", {}, { event: event });
|
|
}, false);
|
|
} else {
|
|
addEventListener("mozUITour", function(event) {
|
|
if (!Services.prefs.getBoolPref("browser.uitour.enabled"))
|
|
return;
|
|
|
|
let handled = UITour.onPageEvent(event);
|
|
if (handled)
|
|
addEventListener("pagehide", UITour);
|
|
}, false, true);
|
|
}
|
|
|
|
let AboutHomeListener = {
|
|
init: function(chromeGlobal) {
|
|
chromeGlobal.addEventListener('AboutHomeLoad', () => this.onPageLoad(), false, true);
|
|
},
|
|
|
|
handleEvent: function(aEvent) {
|
|
switch (aEvent.type) {
|
|
case "AboutHomeLoad":
|
|
this.onPageLoad();
|
|
break;
|
|
}
|
|
},
|
|
|
|
receiveMessage: function(aMessage) {
|
|
switch (aMessage.name) {
|
|
case "AboutHome:Update":
|
|
this.onUpdate(aMessage.data);
|
|
break;
|
|
}
|
|
},
|
|
|
|
onUpdate: function(aData) {
|
|
let doc = content.document;
|
|
if (doc.documentURI.toLowerCase() != "about:home")
|
|
return;
|
|
|
|
if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isWindowPrivate(content))
|
|
doc.getElementById("launcher").setAttribute("session", "true");
|
|
|
|
// Inject search engine and snippets URL.
|
|
let docElt = doc.documentElement;
|
|
// set the following attributes BEFORE searchEngineName, 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);
|
|
docElt.setAttribute("searchEngineName", aData.defaultEngineName);
|
|
},
|
|
|
|
onPageLoad: function() {
|
|
let doc = content.document;
|
|
if (doc.documentURI.toLowerCase() != "about:home" ||
|
|
doc.documentElement.hasAttribute("hasBrowserHandlers")) {
|
|
return;
|
|
}
|
|
|
|
doc.documentElement.setAttribute("hasBrowserHandlers", "true");
|
|
let self = this;
|
|
addMessageListener("AboutHome:Update", self);
|
|
addEventListener("click", this.onClick, true);
|
|
addEventListener("pagehide", function onPageHide(event) {
|
|
if (event.target.defaultView.frameElement)
|
|
return;
|
|
removeMessageListener("AboutHome:Update", self);
|
|
removeEventListener("click", self.onClick, true);
|
|
removeEventListener("pagehide", onPageHide, true);
|
|
if (event.target.documentElement)
|
|
event.target.documentElement.removeAttribute("hasBrowserHandlers");
|
|
}, true);
|
|
|
|
// XXX bug 738646 - when Marketplace is launched, remove this statement and
|
|
// the hidden attribute set on the apps button in aboutHome.xhtml
|
|
if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL &&
|
|
Services.prefs.getBoolPref("browser.aboutHome.apps"))
|
|
doc.getElementById("apps").removeAttribute("hidden");
|
|
|
|
sendAsyncMessage("AboutHome:RequestUpdate");
|
|
|
|
doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
|
|
sendAsyncMessage("AboutHome:Search", { searchData: e.detail });
|
|
}, true, true);
|
|
},
|
|
|
|
onClick: function(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 "apps":
|
|
sendAsyncMessage("AboutHome:Apps");
|
|
break;
|
|
|
|
case "addons":
|
|
sendAsyncMessage("AboutHome:Addons");
|
|
break;
|
|
|
|
case "sync":
|
|
sendAsyncMessage("AboutHome:Sync");
|
|
break;
|
|
|
|
case "settings":
|
|
sendAsyncMessage("AboutHome:Settings");
|
|
break;
|
|
}
|
|
},
|
|
};
|
|
AboutHomeListener.init(this);
|
|
|
|
|
|
// An event listener for custom "WebChannelMessageToChrome" events on pages
|
|
addEventListener("WebChannelMessageToChrome", function (e) {
|
|
// if target is window then we want the document principal, otherwise fallback to target itself.
|
|
let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
|
|
|
|
if (e.detail) {
|
|
sendAsyncMessage("WebChannelMessageToChrome", e.detail, null, principal);
|
|
} else {
|
|
Cu.reportError("WebChannel message failed. No message detail.");
|
|
}
|
|
}, true, true);
|
|
|
|
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts
|
|
addMessageListener("WebChannelMessageToContent", function (e) {
|
|
if (e.data) {
|
|
content.dispatchEvent(new content.CustomEvent("WebChannelMessageToContent", {
|
|
detail: Cu.cloneInto({
|
|
id: e.data.id,
|
|
message: e.data.message,
|
|
}, content),
|
|
}));
|
|
} else {
|
|
Cu.reportError("WebChannel message failed. No message data.");
|
|
}
|
|
});
|
|
|
|
|
|
let ContentSearchMediator = {
|
|
|
|
whitelist: new Set([
|
|
"about:home",
|
|
"about:newtab",
|
|
]),
|
|
|
|
init: function (chromeGlobal) {
|
|
chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
|
|
addMessageListener("ContentSearch", this);
|
|
},
|
|
|
|
handleEvent: function (event) {
|
|
if (this._contentWhitelisted) {
|
|
this._sendMsg(event.detail.type, event.detail.data);
|
|
}
|
|
},
|
|
|
|
receiveMessage: function (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: function (type, data=null) {
|
|
sendAsyncMessage("ContentSearch", {
|
|
type: type,
|
|
data: data,
|
|
});
|
|
},
|
|
|
|
_fireEvent: function (type, data=null) {
|
|
let event = Cu.cloneInto({
|
|
detail: {
|
|
type: type,
|
|
data: data,
|
|
},
|
|
}, content);
|
|
content.dispatchEvent(new content.CustomEvent("ContentSearchService",
|
|
event));
|
|
},
|
|
};
|
|
ContentSearchMediator.init(this);
|
|
|
|
|
|
var global = this;
|
|
|
|
// Lazily load the finder code
|
|
addMessageListener("Finder:Initialize", function () {
|
|
let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
|
|
new RemoteFinderListener(global);
|
|
});
|
|
|
|
|
|
let ClickEventHandler = {
|
|
init: function init() {
|
|
Cc["@mozilla.org/eventlistenerservice;1"]
|
|
.getService(Ci.nsIEventListenerService)
|
|
.addSystemEventListener(global, "click", this, true);
|
|
},
|
|
|
|
handleEvent: function(event) {
|
|
if (!event.isTrusted || event.defaultPrevented || event.button == 2) {
|
|
return;
|
|
}
|
|
|
|
let originalTarget = event.originalTarget;
|
|
let ownerDoc = originalTarget.ownerDocument;
|
|
|
|
// Handle click events from about pages
|
|
if (ownerDoc.documentURI.startsWith("about:certerror")) {
|
|
this.onAboutCertError(originalTarget, ownerDoc);
|
|
return;
|
|
} else if (ownerDoc.documentURI.startsWith("about:blocked")) {
|
|
this.onAboutBlocked(originalTarget, ownerDoc);
|
|
return;
|
|
} else if (ownerDoc.documentURI.startsWith("about:neterror")) {
|
|
this.onAboutNetError(originalTarget, ownerDoc);
|
|
}
|
|
|
|
let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
|
|
|
|
let json = { button: event.button, shiftKey: event.shiftKey,
|
|
ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
|
altKey: event.altKey, href: null, title: null,
|
|
bookmark: false };
|
|
|
|
if (href) {
|
|
json.href = href;
|
|
if (node) {
|
|
json.title = node.getAttribute("title");
|
|
if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
|
|
!event.altKey && !event.metaKey) {
|
|
json.bookmark = node.getAttribute("rel") == "sidebar";
|
|
if (json.bookmark) {
|
|
event.preventDefault(); // Need to prevent the pageload.
|
|
}
|
|
}
|
|
}
|
|
|
|
sendAsyncMessage("Content:Click", json);
|
|
return;
|
|
}
|
|
|
|
// This might be middle mouse navigation.
|
|
if (event.button == 1) {
|
|
sendAsyncMessage("Content:Click", json);
|
|
}
|
|
},
|
|
|
|
onAboutCertError: function (targetElement, ownerDoc) {
|
|
let docshell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIWebNavigation)
|
|
.QueryInterface(Ci.nsIDocShell);
|
|
sendAsyncMessage("Browser:CertExceptionError", {
|
|
location: ownerDoc.location.href,
|
|
elementId: targetElement.getAttribute("id"),
|
|
isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
|
|
}, {
|
|
failedChannel: docshell.failedChannel
|
|
});
|
|
},
|
|
|
|
onAboutBlocked: function (targetElement, ownerDoc) {
|
|
sendAsyncMessage("Browser:SiteBlockedError", {
|
|
location: ownerDoc.location.href,
|
|
isMalware: /e=malwareBlocked/.test(ownerDoc.documentURI),
|
|
elementId: targetElement.getAttribute("id"),
|
|
isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
|
|
});
|
|
},
|
|
|
|
onAboutNetError: function (targetElement, ownerDoc) {
|
|
let elmId = targetElement.getAttribute("id");
|
|
if (elmId != "errorTryAgain" || !/e=netOffline/.test(ownerDoc.documentURI)) {
|
|
return;
|
|
}
|
|
sendSyncMessage("Browser:NetworkError", {});
|
|
},
|
|
|
|
/**
|
|
* Extracts linkNode and href for the current click target.
|
|
*
|
|
* @param event
|
|
* The click event.
|
|
* @return [href, linkNode].
|
|
*
|
|
* @note linkNode will be null if the click wasn't on an anchor
|
|
* element (or XLink).
|
|
*/
|
|
_hrefAndLinkNodeForClickEvent: function(event) {
|
|
function isHTMLLink(aNode) {
|
|
// Be consistent with what nsContextMenu.js does.
|
|
return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
|
|
(aNode instanceof content.HTMLAreaElement && aNode.href) ||
|
|
aNode instanceof content.HTMLLinkElement);
|
|
}
|
|
|
|
let node = event.target;
|
|
while (node && !isHTMLLink(node)) {
|
|
node = node.parentNode;
|
|
}
|
|
|
|
if (node)
|
|
return [node.href, node];
|
|
|
|
// If there is no linkNode, try simple XLink.
|
|
let href, baseURI;
|
|
node = event.target;
|
|
while (node && !href) {
|
|
if (node.nodeType == content.Node.ELEMENT_NODE) {
|
|
href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
|
|
if (href)
|
|
baseURI = node.ownerDocument.baseURIObject;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
|
|
// In case of XLink, we don't return the node we got href from since
|
|
// callers expect <a>-like elements.
|
|
// Note: makeURI() will throw if aUri is not a valid URI.
|
|
return [href ? makeURI(href, null, baseURI).spec : null, null];
|
|
}
|
|
};
|
|
ClickEventHandler.init();
|
|
|
|
ContentLinkHandler.init(this);
|
|
|
|
addEventListener("DOMWebNotificationClicked", function(event) {
|
|
sendAsyncMessage("DOMWebNotificationClicked", {});
|
|
}, false);
|
|
|
|
let PageStyleHandler = {
|
|
init: function() {
|
|
addMessageListener("PageStyle:Switch", this);
|
|
addMessageListener("PageStyle:Disable", this);
|
|
|
|
// Send a CPOW to the parent so that it can synchronously request
|
|
// the list of style sheets.
|
|
sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this});
|
|
},
|
|
|
|
get markupDocumentViewer() {
|
|
return docShell.contentViewer;
|
|
},
|
|
|
|
// Called synchronously via CPOW from the parent.
|
|
getStyleSheetInfo: function() {
|
|
let styleSheets = this._filterStyleSheets(this.getAllStyleSheets());
|
|
return {
|
|
styleSheets: styleSheets,
|
|
authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
|
|
preferredStyleSheetSet: content.document.preferredStyleSheetSet
|
|
};
|
|
},
|
|
|
|
// Called synchronously via CPOW from the parent.
|
|
getAllStyleSheets: function(frameset = content) {
|
|
let selfSheets = Array.slice(frameset.document.styleSheets);
|
|
let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
|
|
return selfSheets.concat(...subSheets);
|
|
},
|
|
|
|
receiveMessage: function(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;
|
|
}
|
|
},
|
|
|
|
_stylesheetSwitchAll: function (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: function (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: function (frame, title) {
|
|
return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
|
|
},
|
|
|
|
_filterStyleSheets: function(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;
|
|
}
|
|
}
|
|
|
|
result.push({title: currentStyleSheet.title,
|
|
disabled: currentStyleSheet.disabled});
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
PageStyleHandler.init();
|
|
|
|
// Keep a reference to the translation content handler to avoid it it being GC'ed.
|
|
let trHandler = null;
|
|
if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
|
|
Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
|
|
trHandler = new TranslationContentHandler(global, docShell);
|
|
}
|