mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-25 09:57:32 +09:00
Compare commits
7 commits
c02dd71673
...
f628239ef3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f628239ef3 | ||
|
|
bb0adb61ae | ||
|
|
3837792171 | ||
|
|
89b2dcbb96 | ||
|
|
3ad2791ea1 | ||
|
|
6e43835b34 | ||
|
|
536619ee89 |
25 changed files with 528 additions and 78 deletions
|
|
@ -5466,6 +5466,9 @@ function handleLinkClick(event, href, linkNode) {
|
|||
urlSecurityCheck(href, doc.nodePrincipal);
|
||||
let params = {
|
||||
charset: doc.characterSet,
|
||||
currentBrowser: gBrowser.getBrowserForContentWindow(doc.defaultView.top),
|
||||
frameOuterWindowID: doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID,
|
||||
allowMixedContent: persistAllowMixedContentInChildTab,
|
||||
referrerURI: referrerURI,
|
||||
referrerPolicy: referrerPolicy,
|
||||
|
|
|
|||
|
|
@ -442,7 +442,9 @@ var ClickEventHandler = {
|
|||
}
|
||||
}
|
||||
|
||||
let json = { button: event.button, shiftKey: event.shiftKey,
|
||||
let json = { frameOuterWindowID: ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID,
|
||||
button: event.button, shiftKey: event.shiftKey,
|
||||
ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
||||
altKey: event.altKey, href: null, title: null,
|
||||
bookmark: false, referrerPolicy: referrerPolicy,
|
||||
|
|
|
|||
|
|
@ -938,6 +938,8 @@ nsContextMenu.prototype = {
|
|||
|
||||
_openLinkInParameters : function (extra) {
|
||||
let params = { charset: gContextMenuContentData.charSet,
|
||||
currentBrowser: this.browser,
|
||||
frameOuterWindowID: this.frameOuterWindowID,
|
||||
originPrincipal: this.principal,
|
||||
triggeringPrincipal: this.principal,
|
||||
referrerURI: gContextMenuContentData.documentURIObject,
|
||||
|
|
|
|||
|
|
@ -273,6 +273,18 @@ function openLinkIn(url, where, params) {
|
|||
// Note that if |w| is null we might have no current browser (we'll open a new window).
|
||||
var aCurrentBrowser = params.currentBrowser || (w && w.gBrowser.selectedBrowser);
|
||||
|
||||
// Capture the source before opening a foreground tab changes the selection.
|
||||
function notifyNavigationTarget(createdTabBrowser) {
|
||||
if (params.frameOuterWindowID && aCurrentBrowser) {
|
||||
Services.obs.notifyObservers({wrappedJSObject: {
|
||||
url,
|
||||
createdTabBrowser,
|
||||
sourceTabBrowser: aCurrentBrowser,
|
||||
sourceFrameOuterWindowID: params.frameOuterWindowID,
|
||||
}}, "webNavigation-createdNavigationTarget", null);
|
||||
}
|
||||
}
|
||||
|
||||
if (where == "save") {
|
||||
// TODO(1073187): propagate referrerPolicy.
|
||||
|
||||
|
|
@ -365,7 +377,21 @@ function openLinkIn(url, where, params) {
|
|||
features += ",private";
|
||||
}
|
||||
|
||||
Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
|
||||
let newWindow = Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
|
||||
if (params.frameOuterWindowID && aCurrentBrowser) {
|
||||
let cleanup = () => {
|
||||
Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
|
||||
newWindow.removeEventListener("unload", cleanup);
|
||||
};
|
||||
let observer = subject => {
|
||||
if (subject == newWindow) {
|
||||
cleanup();
|
||||
notifyNavigationTarget(newWindow.gBrowser.selectedBrowser);
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
|
||||
newWindow.addEventListener("unload", cleanup);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -474,6 +500,7 @@ function openLinkIn(url, where, params) {
|
|||
triggeringPrincipal: aTriggeringPrincipal,
|
||||
});
|
||||
browserUsedForLoad = tabUsedForLoad.linkedBrowser;
|
||||
notifyNavigationTarget(browserUsedForLoad);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ var ContentClick = {
|
|||
|
||||
let params = {
|
||||
charset: browser.characterSet,
|
||||
currentBrowser: browser,
|
||||
frameOuterWindowID: json.frameOuterWindowID,
|
||||
referrerURI: browser.documentURI,
|
||||
referrerPolicy: json.referrerPolicy,
|
||||
noReferrer: json.noReferrer,
|
||||
|
|
|
|||
|
|
@ -2848,6 +2848,13 @@ IsInternalURIScheme(nsIURI* uri)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Extension channels resolve to file: or jar:file: internally. Module
|
||||
// imports must retain the public extension origin as their base URL.
|
||||
bool isExtension;
|
||||
if (NS_SUCCEEDED(uri->SchemeIs("moz-extension", &isExtension)) && isExtension) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ enum CSSStyleSheetParsingMode {
|
|||
"agent"
|
||||
};
|
||||
|
||||
[Constructor]
|
||||
interface CSSStyleSheet : StyleSheet {
|
||||
[Pure]
|
||||
readonly attribute CSSRule? ownerRule;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ enum MozContentPolicyType {
|
|||
* A thin wrapper around nsIChannel and nsIHttpChannel that allows JS
|
||||
* callers to access them without XPConnect overhead.
|
||||
*/
|
||||
[ChromeOnly, Exposed=Window]
|
||||
[ChromeOnly, Exposed=(Window,System)]
|
||||
interface ChannelWrapper : EventTarget {
|
||||
/**
|
||||
* Returns the wrapper instance for the given channel. The same wrapper is
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//#define USEWEAKREFS // (haven't quite figured that out yet)
|
||||
|
||||
#include "nsWindowWatcher.h"
|
||||
#include "nsHashPropertyBag.h"
|
||||
#include "nsAutoWindowStateHelper.h"
|
||||
|
||||
#include "nsCRT.h"
|
||||
|
|
@ -1212,6 +1213,29 @@ nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent,
|
|||
// userContextId.
|
||||
MOZ_ASSERT(CheckUserContextCompatibility(newDocShell));
|
||||
|
||||
// If this tab or window has been opened by a window.open call, we have to provide
|
||||
// all the data needed to send a webNavigation.onCreatedNavigationTarget event.
|
||||
if (windowIsNew && parentDocShell && newDocShellItem) {
|
||||
nsCOMPtr<nsIObserverService> obsSvc =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
if (obsSvc) {
|
||||
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
|
||||
|
||||
if (uriToLoad) {
|
||||
// The url notified in the webNavigation.onCreatedNavigationTarget event.
|
||||
props->SetPropertyAsACString(NS_LITERAL_STRING("url"),
|
||||
uriToLoad->GetSpecOrDefault());
|
||||
}
|
||||
|
||||
props->SetPropertyAsInterface(NS_LITERAL_STRING("sourceTabDocShell"), parentDocShell);
|
||||
props->SetPropertyAsInterface(NS_LITERAL_STRING("createdTabDocShell"), newDocShellItem);
|
||||
|
||||
obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
|
||||
"webNavigation-createdNavigationTarget-from-js", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (uriToLoad && aNavigate) {
|
||||
newDocShell->LoadURI(
|
||||
uriToLoad,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@
|
|||
#include "mozilla/CSSStyleSheet.h"
|
||||
|
||||
#include "mozAutoDocUpdate.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIMediaList.h"
|
||||
#include "nsNullPrincipal.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
|
|
@ -222,6 +224,40 @@ StyleSheet::DeleteRule(uint32_t aIndex)
|
|||
|
||||
// WebIDL CSSStyleSheet API
|
||||
|
||||
/* static */ already_AddRefed<StyleSheet>
|
||||
StyleSheet::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||||
do_QueryInterface(aGlobal.GetAsSupports());
|
||||
if (!window) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocument> document = window->GetDoc();
|
||||
if (!document) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> documentURI = document->GetDocumentURI();
|
||||
nsCOMPtr<nsIURI> baseURI = document->GetBaseURI();
|
||||
nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
|
||||
if (!documentURI || !baseURI || !principal) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<StyleSheet> sheet =
|
||||
new CSSStyleSheet(css::eAuthorSheetFeatures, CORS_NONE,
|
||||
document->GetReferrerPolicy());
|
||||
sheet->SetURIs(documentURI, nullptr, baseURI);
|
||||
sheet->SetPrincipal(principal);
|
||||
sheet->SetComplete();
|
||||
|
||||
return sheet.forget();
|
||||
}
|
||||
|
||||
dom::CSSRuleList*
|
||||
StyleSheet::GetCssRules(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aRv)
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ public:
|
|||
// The XPCOM SetDisabled is fine for WebIDL.
|
||||
|
||||
// WebIDL CSSStyleSheet API
|
||||
static already_AddRefed<StyleSheet> Constructor(const dom::GlobalObject& aGlobal,
|
||||
ErrorResult& aRv);
|
||||
virtual css::Rule* GetDOMOwnerRule() const = 0;
|
||||
dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aRv);
|
||||
|
|
|
|||
|
|
@ -1788,6 +1788,48 @@ SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aCon
|
|||
}
|
||||
return;
|
||||
|
||||
#include "CSSCalc.h"
|
||||
|
||||
struct RuleNodeReduceNumberCalcOps
|
||||
: public mozilla::css::BasicFloatCalcOps
|
||||
, public mozilla::css::CSSValueInputCalcOps
|
||||
{
|
||||
float ComputeLeafValue(const nsCSSValue& aValue)
|
||||
{
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number,
|
||||
"Expected a number-only calc expression");
|
||||
return aValue.GetFloatValue();
|
||||
}
|
||||
|
||||
float ComputeNumber(const nsCSSValue& aValue)
|
||||
{
|
||||
return mozilla::css::ComputeCalc(aValue, *this);
|
||||
}
|
||||
};
|
||||
|
||||
case eCSSUnit_Calc: {
|
||||
RuleNodeReduceNumberCalcOps ops;
|
||||
aField = css::ComputeCalc(aValue, ops);
|
||||
if (mozilla::IsNaN(aField)) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
if (aFlags & SETFCT_POSITIVE) {
|
||||
NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property");
|
||||
if (aField < 0.0f) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
}
|
||||
if (aFlags & SETFCT_OPACITY) {
|
||||
if (aField < 0.0f) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
if (aField > 1.0f) {
|
||||
aField = 1.0f;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case eCSSUnit_Inherit:
|
||||
aConditions.SetUncacheable();
|
||||
aField = aParentValue;
|
||||
|
|
@ -8130,14 +8172,18 @@ nsRuleNode::ComputeBorderData(void* aStartStruct,
|
|||
{
|
||||
const nsCSSPropertyID* subprops =
|
||||
nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius);
|
||||
const float RADIUS_MAX = 17895697; // CSS length clamp value
|
||||
NS_FOR_CSS_FULL_CORNERS(corner) {
|
||||
int cx = FullToHalfCorner(corner, false);
|
||||
int cy = FullToHalfCorner(corner, true);
|
||||
const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]);
|
||||
nsCSSValue radius = *aRuleData->ValueFor(subprops[corner]);
|
||||
nsStyleCoord parentX = parentBorder->mBorderRadius.Get(cx);
|
||||
nsStyleCoord parentY = parentBorder->mBorderRadius.Get(cy);
|
||||
nsStyleCoord coordX, coordY;
|
||||
|
||||
// Clamp border radius to the max value so it will not wrap and cause artifacts.
|
||||
if (radius.GetFloatValue() > RADIUS_MAX) {
|
||||
radius.SetFloatValue(RADIUS_MAX, eCSSUnit_Number);
|
||||
}
|
||||
if (SetPairCoords(radius, coordX, coordY, parentX, parentY,
|
||||
SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
|
||||
SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ support-files = file_bug1089417_iframe.html
|
|||
[test_condition_text.html]
|
||||
[test_condition_text_assignment.html]
|
||||
[test_contain_formatting_context.html]
|
||||
[test_constructed_stylesheet.html]
|
||||
[test_counter_descriptor_storage.html]
|
||||
[test_counter_style.html]
|
||||
[test_css_cross_domain.html]
|
||||
|
|
|
|||
39
layout/style/test/test_constructed_stylesheet.html
Normal file
39
layout/style/test/test_constructed_stylesheet.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test CSSStyleSheet constructor</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test"></pre>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
let sheet;
|
||||
|
||||
try {
|
||||
sheet = new CSSStyleSheet();
|
||||
ok(true, "CSSStyleSheet constructor should not throw");
|
||||
} catch (e) {
|
||||
ok(false, "CSSStyleSheet constructor should not throw: " + e);
|
||||
}
|
||||
|
||||
if (sheet) {
|
||||
ok(sheet instanceof CSSStyleSheet, "Constructor should create a CSSStyleSheet");
|
||||
is(sheet.type, "text/css", "Constructed sheet should have CSS type");
|
||||
is(sheet.href, null, "Constructed sheet should not have an href");
|
||||
is(sheet.cssRules.length, 0, "Constructed sheet should start with no rules");
|
||||
|
||||
is(sheet.insertRule("#test { color: rgb(1, 2, 3); }", 0), 0,
|
||||
"insertRule should work on a constructed sheet");
|
||||
is(sheet.cssRules.length, 1, "insertRule should append a rule");
|
||||
is(sheet.cssRules[0].selectorText, "#test", "Inserted rule should be readable");
|
||||
|
||||
sheet.deleteRule(0);
|
||||
is(sheet.cssRules.length, 0, "deleteRule should remove the rule");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -115,6 +115,20 @@ function WebNavigationEventManager(context, eventName) {
|
|||
parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
|
||||
};
|
||||
|
||||
if (eventName == "onCreatedNavigationTarget") {
|
||||
let source = {};
|
||||
extensions.emit("fill-browser-data", data.sourceTabBrowser, source);
|
||||
if (!(source.tabId >= 0)) {
|
||||
return;
|
||||
}
|
||||
delete data2.frameId;
|
||||
delete data2.parentFrameId;
|
||||
data2.sourceTabId = source.tabId;
|
||||
data2.sourceFrameId = ExtensionManagement.getFrameId(data.sourceWindowId);
|
||||
// Firefox does not expose renderer process IDs through this API.
|
||||
data2.sourceProcessId = -1;
|
||||
}
|
||||
|
||||
if (eventName == "onErrorOccurred") {
|
||||
data2.error = data.error;
|
||||
}
|
||||
|
|
@ -162,7 +176,7 @@ extensions.registerSchemaAPI("webNavigation", "addon_parent", context => {
|
|||
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
|
||||
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
|
||||
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
|
||||
onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"),
|
||||
onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
|
||||
getAllFrames(details) {
|
||||
let tab = TabManager.getTab(details.tabId, context);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
|
||||
"resource://gre/modules/MatchPattern.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
|
||||
"resource://gre/modules/WebRequest.jsm");
|
||||
|
||||
|
|
@ -75,11 +73,16 @@ function WebRequestEventManager(context, eventName) {
|
|||
}
|
||||
}
|
||||
|
||||
if (data.registerTraceableChannel) {
|
||||
let remoteTab = context.xulBrowser && context.xulBrowser.frameLoader
|
||||
? context.xulBrowser.frameLoader.remoteTab : null;
|
||||
data.registerTraceableChannel({id: context.extension.id}, remoteTab);
|
||||
}
|
||||
return context.runSafe(callback, data2);
|
||||
};
|
||||
|
||||
let filter2 = {};
|
||||
filter2.urls = new MatchPattern(filter.urls);
|
||||
filter2.urls = filter.urls;
|
||||
if (filter.types) {
|
||||
filter2.types = filter.types;
|
||||
}
|
||||
|
|
@ -105,7 +108,9 @@ function WebRequestEventManager(context, eventName) {
|
|||
}
|
||||
}
|
||||
|
||||
WebRequest[eventName].addListener(listener, filter2, info2);
|
||||
WebRequest[eventName].addListener(listener, filter2, info2, {
|
||||
policy: {id: context.extension.id, allowedOrigins: context.extension.whiteListedHosts},
|
||||
});
|
||||
return () => {
|
||||
WebRequest[eventName].removeListener(listener);
|
||||
};
|
||||
|
|
@ -117,14 +122,6 @@ function WebRequestEventManager(context, eventName) {
|
|||
WebRequestEventManager.prototype = Object.create(SingletonEventManager.prototype);
|
||||
|
||||
function makeWebRequestEvent(context, eventName) {
|
||||
if (!(eventName in WebRequest) || !WebRequest[eventName]) {
|
||||
let name = `webRequest.${eventName}`;
|
||||
return new SingletonEventManager(context, name, () => {
|
||||
Cu.reportError(`webRequest.${eventName} is not supported by this runtime.`);
|
||||
return () => {};
|
||||
}).api();
|
||||
}
|
||||
|
||||
return new WebRequestEventManager(context, eventName).api();
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +153,7 @@ extensions.registerSchemaAPI("webRequest", "addon_parent", context => {
|
|||
|
||||
return WebRequest.getSecurityInfo({
|
||||
id: requestId,
|
||||
policy: context.extension.policy,
|
||||
policy: {id: context.extension.id},
|
||||
remoteTab,
|
||||
options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -284,7 +284,6 @@
|
|||
},
|
||||
{
|
||||
"name": "onCreatedNavigationTarget",
|
||||
"unsupported": true,
|
||||
"type": "function",
|
||||
"description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
|
||||
"parameters": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
add_task(function* test_created_navigation_target_dispatch() {
|
||||
let scope = {};
|
||||
Services.scriptloader.loadSubScript("resource://gre/modules/WebNavigation.jsm", scope);
|
||||
let {Manager, WebNavigation} = scope;
|
||||
let event = WebNavigation.onCreatedNavigationTarget;
|
||||
let source = {}, target = {};
|
||||
let received = [];
|
||||
let listener = data => received.push(data);
|
||||
event.addListener(listener, {matches: url => url == "https://example.com/target"});
|
||||
do_register_cleanup(() => event.removeListener(listener));
|
||||
|
||||
for (let sourceFirst of [true, false]) {
|
||||
let data = {
|
||||
url: "https://example.com/target", sourceWindowId: 123,
|
||||
createdWindowId: sourceFirst ? 456 : 789,
|
||||
};
|
||||
Manager.onCreatedNavigationTarget(sourceFirst ? source : target,
|
||||
Object.assign({isSourceTab: sourceFirst}, data));
|
||||
equal(received.length, 0, "wait for both browsers");
|
||||
Manager.onCreatedNavigationTarget(sourceFirst ? target : source,
|
||||
Object.assign({isSourceTab: !sourceFirst}, data));
|
||||
equal(received.length, 1, "dispatch once, in either message order");
|
||||
equal(received[0].browser, target);
|
||||
equal(received[0].sourceTabBrowser, source);
|
||||
equal(received[0].sourceWindowId, 123);
|
||||
received.length = 0;
|
||||
}
|
||||
|
||||
function notify(url) {
|
||||
Services.obs.notifyObservers({wrappedJSObject: {
|
||||
url, sourceTabBrowser: source, createdTabBrowser: target,
|
||||
sourceFrameOuterWindowID: 123,
|
||||
}}, "webNavigation-createdNavigationTarget", null);
|
||||
}
|
||||
notify("https://other.example/target");
|
||||
equal(received.length, 0, "filter applies to chrome-created targets too");
|
||||
notify("https://example.com/target");
|
||||
equal(received.length, 1);
|
||||
Manager.onCreatedNavigationTarget(source, {
|
||||
isSourceTab: true, createdWindowId: 1000, sourceWindowId: 123,
|
||||
url: "https://example.com/target",
|
||||
});
|
||||
event.removeListener(listener);
|
||||
equal(Manager.createdNavigationTargetByOuterWindowId.size, 0,
|
||||
"unregistering releases unmatched messages and timers");
|
||||
notify("https://example.com/target");
|
||||
equal(received.length, 1, "removed listeners are not invoked");
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
"use strict";
|
||||
|
||||
// Exercise real channels: a successful import alone does not prove that the
|
||||
// observer, native wrapper, filters and blocking response are connected.
|
||||
add_task(function* test_webrequest_backend() {
|
||||
let {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
|
||||
let server = createHttpServer();
|
||||
let hits = 0;
|
||||
server.registerPathHandler("/request", (request, response) => {
|
||||
++hits;
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.write("allowed");
|
||||
});
|
||||
let url = `http://localhost:${server.identity.primaryPort}/request`;
|
||||
function request(system = false) {
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
let channel = NetUtil.newChannel({
|
||||
uri,
|
||||
loadingPrincipal: system ? Services.scriptSecurityManager.getSystemPrincipal()
|
||||
: Services.scriptSecurityManager.createCodebasePrincipal(uri, {}),
|
||||
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST,
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
NetUtil.asyncFetch(channel, (stream, status) => resolve(status));
|
||||
});
|
||||
}
|
||||
|
||||
let calls = 0;
|
||||
let block = data => {
|
||||
++calls;
|
||||
equal(data.url, url);
|
||||
equal(data.type, "xmlhttprequest");
|
||||
equal(typeof data.requestId, "string");
|
||||
return {cancel: true};
|
||||
};
|
||||
let event = WebRequest.onBeforeRequest;
|
||||
do_register_cleanup(() => event.removeListener(block));
|
||||
event.addListener(block, {urls: ["http://localhost/*"]}, ["blocking"]);
|
||||
equal(yield request(), Cr.NS_ERROR_ABORT, "blocking listener cancels the channel");
|
||||
equal(calls, 1);
|
||||
equal(hits, 0, "cancelled request never reaches the server");
|
||||
event.removeListener(block);
|
||||
equal(yield request(), Cr.NS_OK, "removing the listener restores loading");
|
||||
|
||||
for (let filter of [
|
||||
{urls: ["http://example.org/*"]},
|
||||
{urls: ["<all_urls>"], types: ["image"]},
|
||||
{urls: ["<all_urls>"], incognito: true},
|
||||
{urls: ["<all_urls>"], tabId: 123},
|
||||
]) {
|
||||
event.addListener(block, filter, ["blocking"]);
|
||||
equal(yield request(), Cr.NS_OK, "nonmatching requests are untouched");
|
||||
event.removeListener(block);
|
||||
}
|
||||
event.addListener(block, {urls: ["<all_urls>"]}, ["blocking"], {
|
||||
policy: {id: "backend-test", allowedOrigins: new (Cu.import(
|
||||
"resource://gre/modules/MatchPattern.jsm", {}).MatchPattern)(["http://example.org/*"])},
|
||||
});
|
||||
equal(yield request(), Cr.NS_OK, "host permissions constrain the listener");
|
||||
event.removeListener(block);
|
||||
event.addListener(block, {urls: ["<all_urls>"]}, ["blocking"]);
|
||||
equal(yield request(true), Cr.NS_OK, "system requests are not exposed");
|
||||
event.removeListener(block);
|
||||
equal(calls, 1, "only the matching content request was dispatched");
|
||||
});
|
||||
|
|
@ -8,6 +8,8 @@ support-files =
|
|||
tags = webextensions
|
||||
|
||||
[test_csp_custom_policies.js]
|
||||
[test_webrequest_backend.js]
|
||||
[test_webnavigation_created_target.js]
|
||||
[test_csp_validator.js]
|
||||
[test_ext_alarms.js]
|
||||
[test_ext_alarms_does_not_fire.js]
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const EXPORTED_SYMBOLS = ["SecurityInfo"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
const wpl = Ci.nsIWebProgressListener;
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const EXPORTED_SYMBOLS = ["WebRequest"];
|
||||
|
||||
/* exported WebRequest */
|
||||
|
|
@ -12,29 +14,36 @@ const EXPORTED_SYMBOLS = ["WebRequest"];
|
|||
|
||||
const { nsIHttpActivityObserver, nsISocketTransport } = Ci;
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
|
||||
ExtensionUtils: "resource://gre/modules/ExtensionUtils.jsm",
|
||||
WebRequestUpload: "resource://gre/modules/WebRequestUpload.jsm",
|
||||
SecurityInfo: "resource://gre/modules/SecurityInfo.jsm",
|
||||
});
|
||||
|
||||
// WebRequest.jsm's only consumer is ext-webRequest.js, so we can depend on
|
||||
// the apiManager.global being initialized.
|
||||
XPCOMUtils.defineLazyGetter(this, "tabTracker", () => {
|
||||
return ExtensionParent.apiManager.global.tabTracker;
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(this, "getCookieStoreIdForOriginAttributes", () => {
|
||||
return ExtensionParent.apiManager.global.getCookieStoreIdForOriginAttributes;
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils", "resource://gre/modules/ExtensionUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestUpload", "resource://gre/modules/WebRequestUpload.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SecurityInfo", "resource://gre/modules/SecurityInfo.jsm");
|
||||
|
||||
|
||||
function getCookieStoreIdForOriginAttributes(attrs) {
|
||||
if (attrs.privateBrowsingId) {
|
||||
return "firefox-private";
|
||||
}
|
||||
return attrs.userContextId ? "firefox-container-" + attrs.userContextId : "firefox-default";
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
|
||||
"@mozilla.org/categorymanager;1", "nsICategoryManager");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", "resource://gre/modules/MatchPattern.jsm");
|
||||
|
||||
function matchesRequest(channel, opts, extraData) {
|
||||
return !channel.isSystemLoad &&
|
||||
(!opts.urlPattern || opts.urlPattern.matches(channel.finalURI)) &&
|
||||
(!opts.policy || opts.policy.allowedOrigins.matches(channel.finalURI)) &&
|
||||
channel.matches(opts.filter, opts.policy ? opts.policy.id : "", extraData);
|
||||
}
|
||||
|
||||
function runLater(job) {
|
||||
Services.tm.dispatchToMainThread(job);
|
||||
Services.tm.mainThread.dispatch(job, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
function parseFilter(filter) {
|
||||
|
|
@ -165,7 +174,7 @@ class HeaderChanger {
|
|||
}
|
||||
|
||||
const checkRestrictedHeaderValue = (value, opts = {}) => {
|
||||
let uri = Services.io.newURI(`https://${value}/`);
|
||||
let uri = Services.io.newURI(`https://${value}/`, null, null);
|
||||
let { policy } = opts;
|
||||
|
||||
if (policy && !policy.allowedOrigins.matches(uri)) {
|
||||
|
|
@ -281,7 +290,7 @@ var ChannelEventSink = {
|
|||
_classID: Components.ID("115062f8-92f1-11e5-8b7f-080027b0f7ec"),
|
||||
_contractID: "@mozilla.org/webrequest/channel-event-sink;1",
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink, Ci.nsIFactory]),
|
||||
|
||||
init() {
|
||||
Components.manager
|
||||
|
|
@ -295,7 +304,7 @@ var ChannelEventSink = {
|
|||
},
|
||||
|
||||
register() {
|
||||
Services.catMan.addCategoryEntry(
|
||||
categoryManager.addCategoryEntry(
|
||||
"net-channel-event-sinks",
|
||||
this._contractID,
|
||||
this._contractID,
|
||||
|
|
@ -305,7 +314,7 @@ var ChannelEventSink = {
|
|||
},
|
||||
|
||||
unregister() {
|
||||
Services.catMan.deleteCategoryEntry(
|
||||
categoryManager.deleteCategoryEntry(
|
||||
"net-channel-event-sinks",
|
||||
this._contractID,
|
||||
false
|
||||
|
|
@ -460,7 +469,7 @@ class AuthRequestor {
|
|||
this.httpObserver.runChannelListener(wrapper, "onAuthRequired", data);
|
||||
|
||||
return {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
|
||||
cancel() {
|
||||
try {
|
||||
callback.onAuthCancelled(context, false);
|
||||
|
|
@ -474,10 +483,10 @@ class AuthRequestor {
|
|||
}
|
||||
}
|
||||
|
||||
AuthRequestor.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIInterfaceRequestor",
|
||||
"nsIAuthPromptProvider",
|
||||
"nsIAuthPrompt2",
|
||||
AuthRequestor.prototype.QueryInterface = XPCOMUtils.generateQI([
|
||||
Ci.nsIInterfaceRequestor,
|
||||
Ci.nsIAuthPromptProvider,
|
||||
Ci.nsIAuthPrompt2,
|
||||
]);
|
||||
|
||||
// Most WebRequest events are implemented via the observer services, but
|
||||
|
|
@ -563,21 +572,21 @@ HttpObserverManager = {
|
|||
this.listeners.onSendHeaders.size;
|
||||
if (needOpening && !this.openingInitialized) {
|
||||
this.openingInitialized = true;
|
||||
Services.obs.addObserver(this, "http-on-modify-request");
|
||||
Services.obs.addObserver(this, "http-on-modify-request", false);
|
||||
} else if (!needOpening && this.openingInitialized) {
|
||||
this.openingInitialized = false;
|
||||
Services.obs.removeObserver(this, "http-on-modify-request");
|
||||
}
|
||||
if (needBeforeConnect && !this.beforeConnectInitialized) {
|
||||
this.beforeConnectInitialized = true;
|
||||
Services.obs.addObserver(this, "http-on-before-connect");
|
||||
Services.obs.addObserver(this, "http-on-before-connect", false);
|
||||
} else if (!needBeforeConnect && this.beforeConnectInitialized) {
|
||||
this.beforeConnectInitialized = false;
|
||||
Services.obs.removeObserver(this, "http-on-before-connect");
|
||||
}
|
||||
|
||||
let haveBlocking = Object.values(this.listeners).some(listeners =>
|
||||
Array.from(listeners.values()).some(listener => listener.blockingAllowed)
|
||||
Array.from(listeners.values()).some(listener => listener.blocking)
|
||||
);
|
||||
|
||||
this.needTracing =
|
||||
|
|
@ -593,9 +602,9 @@ HttpObserverManager = {
|
|||
|
||||
if (needExamine && !this.examineInitialized) {
|
||||
this.examineInitialized = true;
|
||||
Services.obs.addObserver(this, "http-on-examine-response");
|
||||
Services.obs.addObserver(this, "http-on-examine-cached-response");
|
||||
Services.obs.addObserver(this, "http-on-examine-merged-response");
|
||||
Services.obs.addObserver(this, "http-on-examine-response", false);
|
||||
Services.obs.addObserver(this, "http-on-examine-cached-response", false);
|
||||
Services.obs.addObserver(this, "http-on-examine-merged-response", false);
|
||||
} else if (!needExamine && this.examineInitialized) {
|
||||
this.examineInitialized = false;
|
||||
Services.obs.removeObserver(this, "http-on-examine-response");
|
||||
|
|
@ -692,7 +701,7 @@ HttpObserverManager = {
|
|||
// Make a trip through the event loop to make sure errors have a
|
||||
// chance to be processed before we fall back to a generic error
|
||||
// string.
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
runLater(() => {
|
||||
channel.errorCheck();
|
||||
if (!channel.errorString) {
|
||||
this.runChannelListener(channel, "onErrorOccurred", {
|
||||
|
|
@ -712,7 +721,7 @@ HttpObserverManager = {
|
|||
},
|
||||
|
||||
getRequestData(channel, extraData) {
|
||||
let originAttributes = channel.loadInfo?.originAttributes;
|
||||
let originAttributes = channel.channel.loadInfo?.originAttributes;
|
||||
let data = {
|
||||
requestId: String(channel.id),
|
||||
url: channel.finalURL,
|
||||
|
|
@ -725,6 +734,9 @@ HttpObserverManager = {
|
|||
documentUrl: channel.documentURL || undefined,
|
||||
|
||||
tabId: this.getBrowserData(channel).tabId,
|
||||
browser: channel.browserElement,
|
||||
windowId: channel.windowId,
|
||||
isSystemPrincipal: channel.isSystemLoad,
|
||||
frameId: channel.windowId,
|
||||
parentWindowId: channel.parentWindowId,
|
||||
|
||||
|
|
@ -783,7 +795,8 @@ HttpObserverManager = {
|
|||
let browserData = wrapper._browserData;
|
||||
if (!browserData) {
|
||||
if (wrapper.browserElement) {
|
||||
browserData = tabTracker.getBrowserData(wrapper.browserElement);
|
||||
browserData = {};
|
||||
ExtensionParent.apiManager.emit("fill-browser-data", wrapper.browserElement, browserData);
|
||||
} else {
|
||||
browserData = { tabId: -1, windowId: -1 };
|
||||
}
|
||||
|
|
@ -815,7 +828,7 @@ HttpObserverManager = {
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (!channel.matches(opts.filter, opts.policy ? opts.policy.id : "", extraData)) {
|
||||
if (!matchesRequest(channel, opts, extraData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -957,7 +970,7 @@ HttpObserverManager = {
|
|||
if (result.redirectUrl) {
|
||||
try {
|
||||
channel.suspended = false;
|
||||
channel.redirectTo(Services.io.newURI(result.redirectUrl));
|
||||
channel.redirectTo(Services.io.newURI(result.redirectUrl, null, null));
|
||||
|
||||
// Web Extensions using the WebRequest API are allowed
|
||||
// to redirect a channel to a data: URI, hence we mark
|
||||
|
|
@ -967,7 +980,7 @@ HttpObserverManager = {
|
|||
// RedirectTo() implementation explicitly drops the flag
|
||||
// to avoid additional redirects not caused by the
|
||||
// Web Extension.
|
||||
channel.loadInfo.allowInsecureRedirectToDataURI = true;
|
||||
channel.channel.loadInfo.allowInsecureRedirectToDataURI = true;
|
||||
|
||||
// To pass CORS checks, we pretend the current request's
|
||||
// response allows the triggering origin to access.
|
||||
|
|
@ -1047,7 +1060,7 @@ HttpObserverManager = {
|
|||
}
|
||||
|
||||
for (let opts of listener.values()) {
|
||||
if (channel.matches(opts.filter, opts.policy ? opts.policy.id : "", extraData)) {
|
||||
if (matchesRequest(channel, opts, extraData)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1096,6 +1109,7 @@ HttpEvent.prototype = {
|
|||
addListener(callback, filter = null, options = null, optionsObject = null) {
|
||||
let opts = parseExtra(options, this.options, optionsObject);
|
||||
opts.filter = parseFilter(filter);
|
||||
opts.urlPattern = opts.filter.urls ? new MatchPattern(opts.filter.urls) : null;
|
||||
HttpObserverManager.addListener(this.internalEvent, callback, opts);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -4,21 +4,19 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const EXPORTED_SYMBOLS = ["WebRequestUpload"];
|
||||
|
||||
/* exported WebRequestUpload */
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
const { ExtensionUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionUtils.jsm"
|
||||
);
|
||||
const { ExtensionUtils } = Cu.import("resource://gre/modules/ExtensionUtils.jsm", {});
|
||||
|
||||
const { DefaultMap } = ExtensionUtils;
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["TextEncoder"]);
|
||||
Cu.importGlobalProperties(["TextEncoder"]);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
|
|
@ -475,7 +473,8 @@ function* getRawDataChunked(
|
|||
// the file, rather than its data.
|
||||
if (
|
||||
unbuffered instanceof Ci.nsIFileInputStream ||
|
||||
unbuffered instanceof Ci.mozIRemoteLazyInputStream
|
||||
("mozIRemoteLazyInputStream" in Ci &&
|
||||
unbuffered instanceof Ci.mozIRemoteLazyInputStream)
|
||||
) {
|
||||
// But this is not actually supported yet.
|
||||
yield { file: "<file>" };
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const Cu = Components.utils;
|
|||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
"resource:///modules/RecentWindow.jsm");
|
||||
|
|
@ -21,8 +22,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
|||
// e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value).
|
||||
const RECENT_DATA_THRESHOLD = 5 * 1000000;
|
||||
|
||||
// TODO:
|
||||
// onCreatedNavigationTarget
|
||||
|
||||
var Manager = {
|
||||
// Map[string -> Map[listener -> URLFilter]]
|
||||
|
|
@ -32,7 +31,10 @@ var Manager = {
|
|||
// Collect recent tab transition data in a WeakMap:
|
||||
// browser -> tabTransitionData
|
||||
this.recentTabTransitionData = new WeakMap();
|
||||
this.createdNavigationTargetByOuterWindowId = new Map();
|
||||
Services.obs.addObserver(this, "autocomplete-did-enter-text", true);
|
||||
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget", false);
|
||||
Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this);
|
||||
|
||||
Services.mm.addMessageListener("Content:Click", this);
|
||||
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
|
||||
|
|
@ -45,7 +47,13 @@ var Manager = {
|
|||
|
||||
uninit() {
|
||||
// Stop collecting recent tab transition data and reset the WeakMap.
|
||||
Services.obs.removeObserver(this, "autocomplete-did-enter-text", true);
|
||||
Services.obs.removeObserver(this, "autocomplete-did-enter-text");
|
||||
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");
|
||||
Services.mm.removeMessageListener("Extension:CreatedNavigationTarget", this);
|
||||
for (let pending of this.createdNavigationTargetByOuterWindowId.values()) {
|
||||
clearTimeout(pending.timer);
|
||||
}
|
||||
this.createdNavigationTargetByOuterWindowId.clear();
|
||||
this.recentTabTransitionData = new WeakMap();
|
||||
|
||||
Services.mm.removeMessageListener("Content:Click", this);
|
||||
|
|
@ -102,6 +110,22 @@ var Manager = {
|
|||
observe: function(subject, topic, data) {
|
||||
if (topic == "autocomplete-did-enter-text") {
|
||||
this.onURLBarAutoCompletion(subject);
|
||||
} else if (topic == "webNavigation-createdNavigationTarget") {
|
||||
// The observed notification is coming from privileged JavaScript components running
|
||||
// in the main process (e.g. when a new tab or window is opened using the context menu
|
||||
// or Ctrl/Shift + click on a link).
|
||||
const {
|
||||
createdTabBrowser,
|
||||
url,
|
||||
sourceFrameOuterWindowID,
|
||||
sourceTabBrowser,
|
||||
} = subject.wrappedJSObject;
|
||||
|
||||
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
|
||||
sourceTabBrowser,
|
||||
sourceWindowId: sourceFrameOuterWindowID,
|
||||
url,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -241,6 +265,9 @@ var Manager = {
|
|||
*/
|
||||
receiveMessage({name, data, target}) {
|
||||
switch (name) {
|
||||
case "Extension:CreatedNavigationTarget":
|
||||
this.onCreatedNavigationTarget(target, data);
|
||||
break;
|
||||
case "Extension:StateChange":
|
||||
this.onStateChange(target, data);
|
||||
break;
|
||||
|
|
@ -274,6 +301,44 @@ var Manager = {
|
|||
}
|
||||
},
|
||||
|
||||
onCreatedNavigationTarget(browser, data) {
|
||||
const {isSourceTab, createdWindowId, sourceWindowId, url} = data;
|
||||
|
||||
// Source and target frame scripts identify their browsers independently.
|
||||
// Pair their messages by the new window's outer ID, in either arrival order.
|
||||
const pairedMessage = this.createdNavigationTargetByOuterWindowId.get(createdWindowId);
|
||||
|
||||
if (!pairedMessage) {
|
||||
// A tab can close before its frame script reports. Do not retain it forever.
|
||||
let timer = setTimeout(() => {
|
||||
this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);
|
||||
}, 30000);
|
||||
this.createdNavigationTargetByOuterWindowId.set(createdWindowId, {browser, data, timer});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pairedMessage.data.isSourceTab == isSourceTab) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(pairedMessage.timer);
|
||||
this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);
|
||||
|
||||
let sourceTabBrowser;
|
||||
let createdTabBrowser;
|
||||
|
||||
if (isSourceTab) {
|
||||
sourceTabBrowser = browser;
|
||||
createdTabBrowser = pairedMessage.browser;
|
||||
} else {
|
||||
sourceTabBrowser = pairedMessage.browser;
|
||||
createdTabBrowser = browser;
|
||||
}
|
||||
|
||||
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
|
||||
sourceTabBrowser, sourceWindowId, url,
|
||||
});
|
||||
},
|
||||
|
||||
onStateChange(browser, data) {
|
||||
let stateFlags = data.stateFlags;
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
|
|
@ -357,7 +422,7 @@ const EVENTS = [
|
|||
"onErrorOccurred",
|
||||
"onReferenceFragmentUpdated",
|
||||
"onHistoryStateUpdated",
|
||||
// "onCreatedNavigationTarget",
|
||||
"onCreatedNavigationTarget",
|
||||
];
|
||||
|
||||
var WebNavigation = {};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,55 @@ addMessageListener("Extension:DisableWebNavigation", () => {
|
|||
removeEventListener("DOMContentLoaded", loadListener);
|
||||
});
|
||||
|
||||
var CreatedNavigationTargetListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget-from-js", false);
|
||||
},
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget-from-js");
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (!(subject instanceof Ci.nsIPropertyBag2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let props = subject.QueryInterface(Ci.nsIPropertyBag2);
|
||||
|
||||
const createdDocShell = props.getPropertyAsInterface("createdTabDocShell", Ci.nsIDocShell);
|
||||
const sourceDocShell = props.getPropertyAsInterface("sourceTabDocShell", Ci.nsIDocShell);
|
||||
|
||||
const isSourceTabDescendant = sourceDocShell.sameTypeRootTreeItem === docShell;
|
||||
|
||||
if (docShell !== createdDocShell && docShell !== sourceDocShell &&
|
||||
!isSourceTabDescendant) {
|
||||
// if the createdNavigationTarget is not related to this docShell
|
||||
// (this docShell is not the newly created docShell, it is not the source docShell,
|
||||
// and the source docShell is not a descendant of it)
|
||||
// there is nothing to do here and return early.
|
||||
return;
|
||||
}
|
||||
|
||||
const isSourceTab = docShell === sourceDocShell || isSourceTabDescendant;
|
||||
const sourceWindowId = WebNavigationFrames.getWindowId(sourceDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow));
|
||||
const createdWindowId = WebNavigationFrames.getWindowId(createdDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow));
|
||||
|
||||
let url = "about:blank";
|
||||
if (props.hasKey("url")) {
|
||||
url = props.getPropertyAsACString("url");
|
||||
}
|
||||
|
||||
sendAsyncMessage("Extension:CreatedNavigationTarget", {
|
||||
url,
|
||||
sourceWindowId,
|
||||
createdWindowId,
|
||||
isSourceTab,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
var FormSubmitListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsIFormSubmitObserver,
|
||||
|
|
@ -256,11 +305,13 @@ var WebProgressListener = {
|
|||
var disabled = false;
|
||||
WebProgressListener.init();
|
||||
FormSubmitListener.init();
|
||||
CreatedNavigationTargetListener.init();
|
||||
addEventListener("unload", () => {
|
||||
if (!disabled) {
|
||||
disabled = true;
|
||||
WebProgressListener.uninit();
|
||||
FormSubmitListener.uninit();
|
||||
CreatedNavigationTargetListener.uninit();
|
||||
}
|
||||
});
|
||||
addMessageListener("Extension:DisableWebNavigation", () => {
|
||||
|
|
@ -268,5 +319,6 @@ addMessageListener("Extension:DisableWebNavigation", () => {
|
|||
disabled = true;
|
||||
WebProgressListener.uninit();
|
||||
FormSubmitListener.uninit();
|
||||
CreatedNavigationTargetListener.uninit();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue