Merge inbound to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-09-12 20:37:35 -04:00
commit e4754304bc
185 changed files with 4807 additions and 2400 deletions

View File

@ -293,11 +293,12 @@ function eventQueue(aEventType)
{
// Some scenario was matched, we wait on next invoker processing.
if (this.mNextInvokerStatus == kInvokerCanceled) {
this.mNextInvokerStatus = kInvokerNotScheduled;
this.setInvokerStatus(kInvokerNotScheduled,
"scenario was matched, wait for next invoker activation");
return;
}
this.mNextInvokerStatus = kInvokerNotScheduled;
this.setInvokerStatus(kInvokerNotScheduled, "the next invoker is processed now");
// Finish processing of the current invoker if any.
var testFailed = false;
@ -433,7 +434,7 @@ function eventQueue(aEventType)
this.processNextInvokerInTimeout =
function eventQueue_processNextInvokerInTimeout(aUncondProcess)
{
this.mNextInvokerStatus = kInvokerPending;
this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
// No need to wait extra timeout when a) we know we don't need to do that
// and b) there's no any single unexpected event.
@ -541,15 +542,22 @@ function eventQueue(aEventType)
}
// If we don't have more events to wait then schedule next invoker.
if (this.hasMatchedScenario() &&
(this.mNextInvokerStatus == kInvokerNotScheduled)) {
this.processNextInvokerInTimeout();
if (this.hasMatchedScenario()) {
if (this.mNextInvokerStatus == kInvokerNotScheduled) {
this.processNextInvokerInTimeout();
} else if (this.mNextInvokerStatus == kInvokerCanceled) {
this.setInvokerStatus(kInvokerPending,
"Full match. Void the cancelation of next invoker processing");
}
return;
}
// If we have scheduled a next invoker then cancel in case of match.
if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers)
this.mNextInvokerStatus = kInvokerCanceled;
if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers) {
this.setInvokerStatus(kInvokerCanceled,
"Cancel the scheduled invoker in case of match");
}
}
// Helpers
@ -622,6 +630,17 @@ function eventQueue(aEventType)
return true;
}
this.isUnexpectedEventScenario =
function eventQueue_isUnexpectedEventsScenario(aScenario)
{
for (var idx = 0; idx < aScenario.length; idx++) {
if (!aScenario[idx].unexpected)
break;
}
return idx == aScenario.length;
}
this.hasUnexpectedEventsScenario =
function eventQueue_hasUnexpectedEventsScenario()
{
@ -629,23 +648,19 @@ function eventQueue(aEventType)
return true;
for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
var eventSeq = this.mScenarios[scnIdx];
for (var idx = 0; idx < eventSeq.length; idx++) {
if (!eventSeq[idx].unexpected)
break;
}
if (idx == eventSeq.length)
if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx]))
return true;
}
return false;
}
this.hasMatchedScenario =
function eventQueue_hasMatchedScenario()
{
for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
if (!this.areExpectedEventsLeft(this.mScenarios[scnIdx]))
var scn = this.mScenarios[scnIdx];
if (!this.isUnexpectedEventScenario(scn) && !this.areExpectedEventsLeft(scn))
return true;
}
return false;
@ -775,6 +790,14 @@ function eventQueue(aEventType)
return invoker.getID();
}
this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus, aLogMsg)
{
this.mNextInvokerStatus = aStatus;
// Uncomment it to debug invoker processing logic.
//gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
}
this.mDefEventType = aEventType;
this.mInvokers = new Array();
@ -872,24 +895,10 @@ eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent)
!(aEvent instanceof nsIAccessibleStateChangeEvent);
}
eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
aScenarioIdx, aEventIdx,
aAreExpectedEventsLeft,
aInvokerStatus)
eventQueue.invokerStatusToMsg =
function eventQueue_invokerStatusToMsg(aInvokerStatus, aMsg)
{
if (!gLogger.isEnabled()) // debug stuff
return;
// Dump DOM event information. Skip a11y event since it is dumped by
// gA11yEventObserver.
if (aOrigEvent instanceof nsIDOMEvent) {
var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
gLogger.logToDOM(info);
}
var msg = "unhandled expected events: " + aAreExpectedEventsLeft +
", invoker status: ";
var msg = "invoker status: ";
switch (aInvokerStatus) {
case kInvokerNotScheduled:
msg += "not scheduled";
@ -902,23 +911,37 @@ eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
break;
}
gLogger.logToConsole(msg);
gLogger.logToDOM(msg);
if (aMsg)
msg += " (" + aMsg + ")";
if (!aMatchedChecker)
return;
return msg;
}
var msg = "EQ: ";
var emphText = "matched ";
eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
aScenarioIdx, aEventIdx,
aAreExpectedEventsLeft,
aInvokerStatus)
{
// Dump DOM event information. Skip a11y event since it is dumped by
// gA11yEventObserver.
if (aOrigEvent instanceof nsIDOMEvent) {
var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
gLogger.logToDOM(info);
}
var infoMsg = "unhandled expected events: " + aAreExpectedEventsLeft +
", " + eventQueue.invokerStatusToMsg(aInvokerStatus);
var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
var consoleMsg = "*****\nScenario " + aScenarioIdx +
", event " + aEventIdx + " matched: " + currType + "\n*****";
", event " + aEventIdx + " matched: " + currType + "\n" + infoMsg + "\n*****";
gLogger.logToConsole(consoleMsg);
msg += " event, type: " + currType + ", target: " + currTargetDescr;
var emphText = "matched ";
var msg = "EQ event, type: " + currType + ", target: " + currTargetDescr +
", " + infoMsg;
gLogger.logToDOM(msg, true, emphText);
}

View File

@ -20,7 +20,7 @@
src="../events.js"></script>
<script type="application/javascript">
gA11yEventDumpToConsole = true; // debugging stuff
//gA11yEventDumpToConsole = true; // debugging stuff
function loadFile()
{

View File

@ -747,9 +747,23 @@ pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
// Enable the disk space watcher
pref("disk_space_watcher.enabled", true);
// SNTP preferences.
pref("network.sntp.maxRetryCount", 10);
pref("network.sntp.refreshPeriod", 86400); // In seconds.
pref("network.sntp.pools", // Servers separated by ';'.
"0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org;3.pool.ntp.org");
pref("network.sntp.port", 123);
pref("network.sntp.timeout", 30); // In seconds.
// Enable promise
pref("dom.promise.enabled", false);
// DOM Inter-App Communication API.
#ifdef MOZ_WIDGET_GONK
// Enable this only for gonk-specific build but not for desktop build.
pref("dom.inter-app-communication-api.enabled", true);
#endif
// Allow ADB to run for this many hours before disabling
// (only applies when marionette is disabled)
// 0 disables the timer.

View File

@ -1069,10 +1069,14 @@ let CompositionManager = {
_isStarted: false,
_text: '',
_clauseAttrMap: {
'raw-input': domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
'selected-raw-text': domWindowUtils.COMPOSITION_ATTR_SELECTEDRAWTEXT,
'converted-text': domWindowUtils.COMPOSITION_ATTR_CONVERTEDTEXT,
'selected-converted-text': domWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT
'raw-input':
Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT,
'selected-raw-text':
Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDRAWTEXT,
'converted-text':
Ci.nsICompositionStringSynthesizer.ATTR_CONVERTEDTEXT,
'selected-converted-text':
Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDCONVERTEDTEXT
},
setComposition: function cm_setComposition(element, text, cursor, clauses) {
@ -1081,20 +1085,14 @@ let CompositionManager = {
return;
}
let len = text.length;
if (cursor < 0) {
cursor = 0;
} else if (cursor > len) {
if (cursor > len) {
cursor = len;
}
let clauseLens = [len, 0, 0];
let clauseAttrs = [domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
domWindowUtils.COMPOSITION_ATTR_RAWINPUT];
let clauseLens = [];
let clauseAttrs = [];
if (clauses) {
let remainingLength = len;
// Currently we don't support 4 or more clauses composition string.
let clauseNum = Math.min(3, clauses.length);
for (let i = 0; i < clauseNum; i++) {
for (let i = 0; i < clauses.length; i++) {
if (clauses[i]) {
let clauseLength = clauses[i].length || 0;
// Make sure the total clauses length is not bigger than that of the
@ -1103,16 +1101,19 @@ let CompositionManager = {
clauseLength = remainingLength;
}
remainingLength -= clauseLength;
clauseLens[i] = clauseLength;
clauseAttrs[i] = this._clauseAttrMap[clauses[i].selectionType] ||
domWindowUtils.COMPOSITION_ATTR_RAWINPUT;
clauseLens.push(clauseLength);
clauseAttrs.push(this._clauseAttrMap[clauses[i].selectionType] ||
Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
}
}
// If the total clauses length is less than that of the composition
// string, extend the last clause to the end of the composition string.
if (remainingLength > 0) {
clauseLens[2] += remainingLength;
clauseLens[clauseLens.length - 1] += remainingLength;
}
} else {
clauseLens.push(len);
clauseAttrs.push(Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
}
// Start composition if need to.
@ -1127,11 +1128,15 @@ let CompositionManager = {
this._text = text;
domWindowUtils.sendCompositionEvent('compositionupdate', text, '');
}
domWindowUtils.sendTextEvent(text,
clauseLens[0], clauseAttrs[0],
clauseLens[1], clauseAttrs[1],
clauseLens[2], clauseAttrs[2],
cursor, 0);
let compositionString = domWindowUtils.createCompositionStringSynthesizer();
compositionString.setString(text);
for (var i = 0; i < clauseLens.length; i++) {
compositionString.appendClause(clauseLens[i], clauseAttrs[i]);
}
if (cursor >= 0) {
compositionString.setCaret(cursor, 0);
}
compositionString.dispatchEvent();
},
endComposition: function cm_endComposition(text) {
@ -1142,9 +1147,12 @@ let CompositionManager = {
if (this._text !== text) {
domWindowUtils.sendCompositionEvent('compositionupdate', text, '');
}
let compositionString = domWindowUtils.createCompositionStringSynthesizer();
compositionString.setString(text);
// Set the cursor position to |text.length| so that the text will be
// committed before the cursor position.
domWindowUtils.sendTextEvent(text, 0, 0, 0, 0, 0, 0, text.length, 0);
compositionString.setCaret(text.length, 0);
compositionString.dispatchEvent();
domWindowUtils.sendCompositionEvent('compositionend', text, '');
this._text = '';
this._isStarted = false;

View File

@ -631,9 +631,18 @@ Services.obs.addObserver(function onSystemMessageOpenApp(subject, topic, data) {
shell.openAppForSystemMessage(msg);
}, 'system-messages-open-app', false);
Services.obs.addObserver(function(aSubject, aTopic, aData) {
Services.obs.addObserver(function onInterAppCommConnect(subject, topic, data) {
data = JSON.parse(data);
shell.sendChromeEvent({ type: "inter-app-comm-permission",
chromeEventID: data.callerID,
manifestURL: data.manifestURL,
keyword: data.keyword,
peers: data.appsToSelect });
}, 'inter-app-comm-select-app', false);
Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
shell.sendChromeEvent({ type: "fullscreenoriginchange",
fullscreenorigin: aData });
fullscreenorigin: data });
}, "fullscreen-origin-change", false);
Services.obs.addObserver(function onWebappsStart(subject, topic, data) {
@ -700,6 +709,13 @@ var CustomEventManager = {
case 'captive-portal-login-cancel':
CaptivePortalLoginHelper.handleEvent(detail);
break;
case 'inter-app-comm-permission':
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: detail.chromeEventID,
keyword: detail.keyword,
manifestURL: detail.manifestURL,
selectedApps: detail.peers }));
break;
}
}
}

View File

@ -505,6 +505,11 @@
@BINPATH@/components/Push.manifest
@BINPATH@/components/PushServiceLauncher.js
@BINPATH@/components/InterAppComm.manifest
@BINPATH@/components/InterAppCommService.js
@BINPATH@/components/InterAppConnection.js
@BINPATH@/components/InterAppMessagePort.js
@BINPATH@/components/nsDOMIdentity.js
@BINPATH@/components/nsIDService.js
@BINPATH@/components/Identity.manifest

View File

@ -374,10 +374,13 @@ def parsefile(pathname):
# colon followed by anything except a slash (Windows path detection)
_depfilesplitter = re.compile(r':(?![\\/])')
# simple variable references
_vars = re.compile('\$\((\w+)\)')
def parsedepfile(pathname):
"""
Parse a filename listing only depencencies into a parserdata.StatementList.
Simple variable references are allowed in such files.
"""
def continuation_iter(lines):
current_line = []
@ -394,12 +397,29 @@ def parsedepfile(pathname):
if current_line:
yield ''.join(current_line)
def get_expansion(s):
if '$' in s:
expansion = data.Expansion()
# for an input like e.g. "foo $(bar) baz",
# _vars.split returns ["foo", "bar", "baz"]
# every other element is a variable name.
for i, element in enumerate(_vars.split(s)):
if i % 2:
expansion.appendfunc(functions.VariableRef(None,
data.StringExpansion(element, None)))
elif element:
expansion.appendstr(element)
return expansion
return data.StringExpansion(s, None)
pathname = os.path.realpath(pathname)
stmts = parserdata.StatementList()
for line in continuation_iter(open(pathname).readlines()):
target, deps = _depfilesplitter.split(line, 1)
stmts.append(parserdata.Rule(data.StringExpansion(target, None),
data.StringExpansion(deps, None), False))
stmts.append(parserdata.Rule(get_expansion(target),
get_expansion(deps), False))
return stmts
def parsestring(s, filename):

View File

@ -0,0 +1 @@
$(FILE)1: filemissing

View File

@ -0,0 +1,10 @@
#T gmake skip
FILE = includedeps-variables
all: $(FILE)1
includedeps $(TESTPATH)/includedeps-variables.deps
filemissing:
@echo TEST-PASS

View File

@ -10,11 +10,6 @@ NO_PROFILE_GUIDED_OPTIMIZE = 1
VPATH += $(topsrcdir)/build
HOST_CPPSRCS = \
elf.cpp \
elfhack.cpp \
$(NULL)
OS_CXXFLAGS := $(filter-out -fno-exceptions,$(OS_CXXFLAGS)) -fexceptions
ifndef CROSS_COMPILE

View File

@ -12,3 +12,7 @@ CSRCS += [
'test-ctors.c',
]
HOST_CPPSRCS += [
'elf.cpp',
'elfhack.cpp',
]

View File

@ -7,10 +7,6 @@ STL_FLAGS =
NO_EXPAND_LIBS = 1
NO_PROFILE_GUIDED_OPTIMIZE = 1
ifdef MOZ_LIBSTDCXX_HOST_VERSION
HOST_CPPSRCS = stdc++compat.cpp
endif
include $(topsrcdir)/config/rules.mk
CXXFLAGS += -DMOZ_LIBSTDCXX_VERSION=$(MOZ_LIBSTDCXX_TARGET_VERSION)

View File

@ -12,4 +12,7 @@ if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION']:
if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
HOST_LIBRARY_NAME = 'host_stdc++compat'
HOST_CPPSRCS += [
'stdc++compat.cpp',
]

View File

@ -10,10 +10,12 @@ interface nsIDocShell;
/**
* nsIContentSecurityPolicy
* Describes an XPCOM component used to model an enforce CSPs.
* Describes an XPCOM component used to model and enforce CSPs. Instances of
* this class may have multiple policies within them, but there should only be
* one of these per document/principal.
*/
[scriptable, uuid(230b126d-afc3-4588-9794-3e135594d626)]
[scriptable, uuid(e5020ec3-1437-46f5-b4eb-8b60766d02c0)]
interface nsIContentSecurityPolicy : nsISupports
{
@ -25,61 +27,87 @@ interface nsIContentSecurityPolicy : nsISupports
attribute boolean isInitialized;
/**
* When set to true, content load-blocking and fail-closed are disabled: CSP
* will ONLY send reports, and not modify behavior.
* Accessor method for a read-only string version of the policy at a given
* index.
*/
attribute boolean reportOnlyMode;
AString getPolicy(in unsigned long index);
/**
* A read-only string version of the policy for debugging.
* Returns the number of policies attached to this CSP instance. Useful with
* getPolicy().
*/
readonly attribute AString policy;
attribute long policyCount;
/**
* Remove a policy associated with this CSP context.
* @throws NS_ERROR_FAILURE if the index is out of bounds or invalid.
*/
void removePolicy(in unsigned long index);
/**
* Parse and install a CSP policy.
* @param aPolicy
* String representation of the policy (e.g., header value)
* @param selfURI
* the URI of the protected document/principal
* @param reportOnly
* Should this policy affect content, script and style processing or
* just send reports if it is violated?
* @param specCompliant
* Whether or not the policy conforms to the W3C specification.
* If this is false, that indicates this policy is from the older
* implementation with different semantics and directive names.
*/
void appendPolicy(in AString policyString, in nsIURI selfURI,
in boolean reportOnly, in boolean specCompliant);
/**
* Whether this policy allows in-page script.
* @param shouldReportViolation
* @param shouldReportViolations
* Whether or not the use of inline script should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* any policy (report-only or otherwise) is violated,
* shouldReportViolations is true as well.
* @return
* Whether or not the effects of the inline script should be allowed
* (block the compilation if false).
*/
boolean getAllowsInlineScript(out boolean shouldReportViolation);
boolean getAllowsInlineScript(out boolean shouldReportViolations);
/**
* whether this policy allows eval and eval-like functions
* such as setTimeout("code string", time).
* @param shouldReportViolation
* @param shouldReportViolations
* Whether or not the use of eval should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* This function returns "true" when violating report-only policies, but
* when any policy (report-only or otherwise) is violated,
* shouldReportViolations is true as well.
* @return
* Whether or not the effects of the eval call should be allowed
* (block the call if false).
*/
boolean getAllowsEval(out boolean shouldReportViolation);
boolean getAllowsEval(out boolean shouldReportViolations);
/**
* Whether this policy allows in-page styles.
* This includes <style> tags with text content and style="" attributes in
* HTML elements.
* @param shouldReportViolation
* Whether or not the use of eval should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* @param shouldReportViolations
* Whether or not the use of inline style should be reported.
* If there are report-only policies, this function may return true
* (don't block), but one or more policy may still want to send
* violation reports so shouldReportViolations will be true even if the
* inline style should be permitted.
* @return
* Whether or not the effects of the eval call should be allowed
* (block the call if false).
* Whether or not the effects of the inline style should be allowed
* (block the rules if false).
*/
boolean getAllowsInlineStyle(out boolean shouldReportViolation);
boolean getAllowsInlineStyle(out boolean shouldReportViolations);
/**
* Log policy violation on the Error Console and send a report if a report-uri
* is present in the policy
* For each violated policy (of type violationType), log policy violation on
* the Error Console and send a report to report-uris present in the violated
* policies.
*
* @param violationType
* one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
@ -99,47 +127,24 @@ interface nsIContentSecurityPolicy : nsISupports
const unsigned short VIOLATION_TYPE_EVAL = 2;
const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3;
/**
* Manually triggers violation report sending given a URI and reason.
* The URI may be null, in which case "self" is sent.
* @param blockedURI
* the URI that violated the policy
* @param violatedDirective
* the directive that was violated.
* @param scriptSample
* a sample of the violating inline script
* @param lineNum
* source line number of the violation (if available)
* @return
* nothing.
*/
void sendReports(in AString blockedURI,
in AString violatedDirective,
in AString scriptSample,
in int32_t lineNum);
/**
* Called after the CSP object is created to fill in the appropriate request
* and request header information needed in case a report needs to be sent.
*/
void scanRequestData(in nsIHttpChannel aChannel);
/**
* Updates the policy currently stored in the CSP to be "refined" or
* tightened by the one specified in the string policyString.
*/
void refinePolicy(in AString policyString, in nsIURI selfURI, in boolean specCompliant);
/**
* Verifies ancestry as permitted by the policy.
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
* NOTE: Calls to this may trigger violation reports when queried, so this
* value should not be cached.
*
* @param docShell
* containing the protected resource
* @return
* true if the frame's ancestors are all permitted by policy
* true if the frame's ancestors are all allowed by policy (except for
* report-only policies, which will send reports and then return true
* here when violated).
*/
boolean permitsAncestry(in nsIDocShell docShell);
@ -151,11 +156,11 @@ interface nsIContentSecurityPolicy : nsISupports
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*/
short shouldLoad(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeTypeGuess,
short shouldLoad(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeTypeGuess,
in nsISupports aExtra);
/**
@ -163,10 +168,10 @@ interface nsIContentSecurityPolicy : nsISupports
* document are being processed. Given a bit of information about the request,
* decides whether or not the policy is satisfied.
*/
short shouldProcess(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
short shouldProcess(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeType,
in nsISupports aExtra);

View File

@ -108,7 +108,7 @@ this.CSPdebug = function CSPdebug(aMsg) {
}
// Callback to resume a request once the policy-uri has been fetched
function CSPPolicyURIListener(policyURI, docRequest, csp) {
function CSPPolicyURIListener(policyURI, docRequest, csp, reportOnly) {
this._policyURI = policyURI; // location of remote policy
this._docRequest = docRequest; // the parent document request
this._csp = csp; // parent document's CSP
@ -116,6 +116,7 @@ function CSPPolicyURIListener(policyURI, docRequest, csp) {
this._wrapper = null; // nsIScriptableInputStream
this._docURI = docRequest.QueryInterface(Ci.nsIChannel)
.URI; // parent document URI (to be used as 'self')
this._reportOnly = reportOnly;
}
CSPPolicyURIListener.prototype = {
@ -147,8 +148,8 @@ CSPPolicyURIListener.prototype = {
if (Components.isSuccessCode(status)) {
// send the policy we received back to the parent document's CSP
// for parsing
this._csp.refinePolicy(this._policy, this._docURI,
this._csp._specCompliant);
this._csp.appendPolicy(this._policy, this._docURI,
this._reportOnly, this._csp._specCompliant);
}
else {
// problem fetching policy so fail closed
@ -177,6 +178,7 @@ this.CSPRep = function CSPRep(aSpecCompliant) {
this._allowEval = false;
this._allowInlineScripts = false;
this._reportOnlyMode = false;
// don't auto-populate _directives, so it is easier to find bugs
this._directives = {};
@ -244,12 +246,14 @@ CSPRep.ALLOW_DIRECTIVE = "allow";
* @returns
* an instance of CSPRep
*/
CSPRep.fromString = function(aStr, self, docRequest, csp) {
CSPRep.fromString = function(aStr, self, docRequest, csp, reportOnly) {
if (typeof reportOnly === 'undefined') reportOnly = false;
var SD = CSPRep.SRC_DIRECTIVES_OLD;
var UD = CSPRep.URI_DIRECTIVES;
var aCSPR = new CSPRep();
aCSPR._originalText = aStr;
aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
aCSPR._reportOnlyMode = reportOnly;
var selfUri = null;
if (self instanceof Ci.nsIURI) {
@ -451,7 +455,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
// policy-uri can't be abused for CSRF
chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
chan.loadGroup = docRequest.loadGroup;
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null);
}
catch (e) {
// resume the document request and apply most restrictive policy
@ -461,8 +465,8 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
return CSPRep.fromString("default-src 'none'");
}
// return a fully-open policy to be intersected with the contents of the
// policy-uri when it returns
// return a fully-open policy to be used until the contents of the
// policy-uri come back.
return CSPRep.fromString("default-src *");
}
@ -499,12 +503,15 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
*/
// When we deprecate our original CSP implementation, we rename this to
// CSPRep.fromString and remove the existing CSPRep.fromString above.
CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp) {
CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp, reportOnly) {
if (typeof reportOnly === 'undefined') reportOnly = false;
var SD = CSPRep.SRC_DIRECTIVES_NEW;
var UD = CSPRep.URI_DIRECTIVES;
var aCSPR = new CSPRep(true);
aCSPR._originalText = aStr;
aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
aCSPR._reportOnlyMode = reportOnly;
var selfUri = null;
if (self instanceof Ci.nsIURI) {
@ -716,8 +723,8 @@ CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp) {
return CSPRep.fromStringSpecCompliant("default-src 'none'");
}
// return a fully-open policy to be intersected with the contents of the
// policy-uri when it returns
// return a fully-open policy to be used until the contents of the
// policy-uri come back
return CSPRep.fromStringSpecCompliant("default-src *");
}
@ -836,120 +843,6 @@ CSPRep.prototype = {
return this._specCompliant;
},
/**
* Intersects with another CSPRep, deciding the subset policy
* that should be enforced, and returning a new instance.
* This assumes that either both CSPReps are specCompliant or they are both
* not.
* @param aCSPRep
* a CSPRep instance to use as "other" CSP
* @returns
* a new CSPRep instance of the intersection
*/
intersectWith:
function cspsd_intersectWith(aCSPRep) {
var newRep = new CSPRep();
let DIRS = aCSPRep._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW :
CSPRep.SRC_DIRECTIVES_OLD;
// one or more of the two CSPReps may not have any given directive. In
// these cases, we need to pick "all" or "none" based on the type of CSPRep
// (spec compliant or not).
let thisHasDefault = this._directives.hasOwnProperty(DIRS.DEFAULT_SRC),
thatHasDefault = aCSPRep._directives.hasOwnProperty(DIRS.DEFAULT_SRC);
for (var dir in DIRS) {
let dirv = DIRS[dir];
let thisHasDir = this._directives.hasOwnProperty(dirv),
thatHasDir = aCSPRep._directives.hasOwnProperty(dirv);
// if both specific src directives are absent, skip this (new policy will
// rely on default-src)
if (!thisHasDir && !thatHasDir) {
continue;
}
// frame-ancestors is a special case; it doesn't fall back to
// default-src, so only add it to newRep if one or both of the policies
// have it.
if (dirv === DIRS.FRAME_ANCESTORS) {
if (thisHasDir && thatHasDir) {
// both have frame-ancestors, intersect them.
newRep._directives[dirv] =
aCSPRep._directives[dirv].intersectWith(this._directives[dirv]);
} else if (thisHasDir || thatHasDir) {
// one or the other has frame-ancestors, copy it.
newRep._directives[dirv] =
( thisHasDir ? this : aCSPRep )._directives[dirv].clone();
}
}
else if (aCSPRep._specCompliant) {
// CSP 1.0 doesn't require default-src, so an intersection only makes
// sense if there is a default-src or both policies have the directive.
if (!thisHasDir && !thisHasDefault) {
// only aCSPRep has a relevant directive
newRep._directives[dirv] = aCSPRep._directives[dirv].clone();
}
else if (!thatHasDir && !thatHasDefault) {
// only "this" has a relevant directive
newRep._directives[dirv] = this._directives[dirv].clone();
}
else {
// both policies have a relevant directive (may be default-src)
var isect1 = thisHasDir ?
this._directives[dirv] :
this._directives[DIRS.DEFAULT_SRC];
var isect2 = thatHasDir ?
aCSPRep._directives[dirv] :
aCSPRep._directives[DIRS.DEFAULT_SRC];
newRep._directives[dirv] = isect1.intersectWith(isect2);
}
}
else {
// pre-1.0 CSP requires a default-src, so we can assume it's here
// (since the parser created one).
var isect1 = thisHasDir ?
this._directives[dirv] :
this._directives[DIRS.DEFAULT_SRC];
var isect2 = thatHasDir ?
aCSPRep._directives[dirv] :
aCSPRep._directives[DIRS.DEFAULT_SRC];
newRep._directives[dirv] = isect1.intersectWith(isect2);
}
}
// REPORT_URI
var reportURIDir = CSPRep.URI_DIRECTIVES.REPORT_URI;
if (this._directives[reportURIDir] && aCSPRep._directives[reportURIDir]) {
newRep._directives[reportURIDir] =
this._directives[reportURIDir].concat(aCSPRep._directives[reportURIDir]);
}
else if (this._directives[reportURIDir]) {
// blank concat makes a copy of the string.
newRep._directives[reportURIDir] = this._directives[reportURIDir].concat();
}
else if (aCSPRep._directives[reportURIDir]) {
// blank concat makes a copy of the string.
newRep._directives[reportURIDir] = aCSPRep._directives[reportURIDir].concat();
}
newRep._allowEval = this.allowsEvalInScripts
&& aCSPRep.allowsEvalInScripts;
newRep._allowInlineScripts = this.allowsInlineScripts
&& aCSPRep.allowsInlineScripts;
newRep._allowInlineStyles = this.allowsInlineStyles
&& aCSPRep.allowsInlineStyles;
newRep._innerWindowID = this._innerWindowID ?
this._innerWindowID : aCSPRep._innerWindowID;
return newRep;
},
/**
* Returns true if "eval" is enabled through the "eval" keyword.
*/
@ -1192,62 +1085,6 @@ CSPSourceList.prototype = {
}
}
return false;
},
/**
* Intersects with another CSPSourceList, deciding the subset directive
* that should be enforced, and returning a new instance.
* @param that
* the other CSPSourceList to intersect "this" with
* @returns
* a new instance of a CSPSourceList representing the intersection
*/
intersectWith:
function cspsd_intersectWith(that) {
var newCSPSrcList = null;
if (!that) return this.clone();
if (this.isNone() || that.isNone())
newCSPSrcList = CSPSourceList.fromString("'none'", this._CSPRep);
if (this.isAll()) newCSPSrcList = that.clone();
if (that.isAll()) newCSPSrcList = this.clone();
if (!newCSPSrcList) {
// the shortcuts didn't apply, must do intersection the hard way.
// -- find only common sources
// XXX (sid): we should figure out a better algorithm for this.
// This is horribly inefficient.
var isrcs = [];
for (var i in this._sources) {
for (var j in that._sources) {
var s = that._sources[j].intersectWith(this._sources[i]);
if (s) {
isrcs.push(s);
}
}
}
// Next, remove duplicates
dup: for (var i = 0; i < isrcs.length; i++) {
for (var j = 0; j < i; j++) {
if (isrcs[i].equals(isrcs[j])) {
isrcs.splice(i, 1);
i--;
continue dup;
}
}
}
newCSPSrcList = new CSPSourceList();
newCSPSrcList._sources = isrcs;
}
if ((!newCSPSrcList._CSPRep) && that._CSPRep) {
newCSPSrcList._CSPRep = that._CSPRep;
}
return newCSPSrcList;
}
}
@ -1661,93 +1498,6 @@ CSPSource.prototype = {
return true;
},
/**
* Determines the intersection of two sources.
* Returns a null object if intersection generates no
* hosts that satisfy it.
* @param that
* the other CSPSource to intersect "this" with
* @returns
* a new instance of a CSPSource representing the intersection
*/
intersectWith:
function(that) {
var newSource = new CSPSource();
// 'self' is not part of the intersection. Intersect the raw values from
// the source, self must be set by someone creating this source.
// When intersecting, we take the more specific of the two: if one scheme,
// host or port is undefined, the other is taken. (This is contrary to
// when "permits" is called -- there, the value of 'self' is looked at
// when a scheme, host or port is undefined.)
// port
if (!this.port)
newSource._port = that.port;
else if (!that.port)
newSource._port = this.port;
else if (this.port === "*")
newSource._port = that.port;
else if (that.port === "*")
newSource._port = this.port;
else if (that.port === this.port)
newSource._port = this.port;
else {
let msg = CSPLocalizer.getFormatStr("notIntersectPort",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
return null;
}
// scheme
if (!this.scheme)
newSource._scheme = that.scheme;
else if (!that.scheme)
newSource._scheme = this.scheme;
if (this.scheme === "*")
newSource._scheme = that.scheme;
else if (that.scheme === "*")
newSource._scheme = this.scheme;
else if (that.scheme === this.scheme)
newSource._scheme = this.scheme;
else {
var msg = CSPLocalizer.getFormatStr("notIntersectScheme",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
return null;
}
// NOTE: Both sources must have a host, if they don't, something funny is
// going on. The fromString() factory method should have set the host to
// * if there's no host specified in the input. Regardless, if a host is
// not present either the scheme is hostless or any host should be allowed.
// This means we can use the other source's host as the more restrictive
// host expression, or if neither are present, we can use "*", but the
// error should still be reported.
// host
if (this.host && that.host) {
newSource._host = this.host.intersectWith(that.host);
} else if (this.host) {
let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
[that.toString()]);
cspError(this._CSPRep, msg);
newSource._host = this.host.clone();
} else if (that.host) {
let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
[this.toString()]);
cspError(this._CSPRep, msg);
newSource._host = that.host.clone();
} else {
let msg = CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
newSource._host = CSPHost.fromString("*");
}
return newSource;
},
/**
* Compares one CSPSource to another.
*
@ -1896,36 +1646,6 @@ CSPHost.prototype = {
return true;
},
/**
* Determines the intersection of two Hosts.
* Basically, they must be the same, or one must have a wildcard.
* @param that
* the other CSPHost to intersect "this" with
* @returns
* a new instance of a CSPHost representing the intersection
* (or null, if they can't be intersected)
*/
intersectWith:
function(that) {
if (!(this.permits(that) || that.permits(this))) {
// host definitions cannot co-exist without a more general host
// ... one must be a subset of the other, or intersection makes no sense.
return null;
}
// pick the more specific one, if both are same length.
if (this._segments.length == that._segments.length) {
// *.a vs b.a : b.a
return (this._segments[0] === "*") ? that.clone() : this.clone();
}
// different lengths...
// *.b.a vs *.a : *.b.a
// *.b.a vs d.c.b.a : d.c.b.a
return (this._segments.length > that._segments.length) ?
this.clone() : that.clone();
},
/**
* Compares one CSPHost to another.
*

View File

@ -28,6 +28,10 @@ const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
const WARN_FLAG = Ci.nsIScriptError.warningFlag;
const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
// The cutoff length of content location in creating CSP cache key.
const CSP_CACHE_URI_CUTOFF_SIZE = 512;
@ -40,21 +44,15 @@ Cu.import("resource://gre/modules/CSPUtils.jsm");
function ContentSecurityPolicy() {
CSPdebug("CSP CREATED");
this._isInitialized = false;
this._reportOnlyMode = false;
this._policy = CSPRep.fromString("default-src *");
// default options "wide open" since this policy will be intersected soon
this._policy._allowInlineScripts = true;
this._policy._allowInlineStyles = true;
this._policy._allowEval = true;
this._policies = [];
this._request = "";
this._requestOrigin = "";
this._requestPrincipal = "";
this._referrer = "";
this._docRequest = null;
CSPdebug("CSP POLICY INITED TO 'default-src *'");
CSPdebug("CSP object initialized, no policies to enforce yet");
this._cache = { };
}
@ -131,37 +129,68 @@ ContentSecurityPolicy.prototype = {
this._isInitialized = foo;
},
get policy () {
return this._policy.toString();
_getPolicyInternal: function(index) {
if (index < 0 || index >= this._policies.length) {
throw Cr.NS_ERROR_FAILURE;
}
return this._policies[index];
},
getAllowsInlineScript: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsInlineScripts;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsInlineScripts;
},
getAllowsEval: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsEvalInScripts;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsEvalInScripts;
},
getAllowsInlineStyle: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsInlineStyles;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsInlineStyles;
_buildViolatedDirectiveString:
function(aDirectiveName, aPolicy) {
var SD = CSPRep.SRC_DIRECTIVES_NEW;
var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
var directive = aPolicy._directives[cspContext];
return cspContext + ' ' + directive.toString();
},
/**
* Log policy violation on the Error Console and send a report if a report-uri
* is present in the policy
* Returns policy string representing the policy at "index".
*/
getPolicy: function(index) {
return this._getPolicyInternal(index).toString();
},
/**
* Returns count of policies.
*/
get numPolicies() {
return this._policies.length;
},
getAllowsInlineScript: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsInlineScripts;
});
},
getAllowsEval: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsEvalInScripts;
});
},
getAllowsInlineStyle: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsInlineStyles;
});
},
/**
* For each policy, log any violation on the Error Console and send a report
* if a report-uri is present in the policy
*
* @param aViolationType
* one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
@ -173,48 +202,44 @@ ContentSecurityPolicy.prototype = {
* source line number of the violation (if available)
*/
logViolationDetails:
function(aViolationType, aSourceFile, aScriptSample, aLineNum) {
// allowsInlineScript and allowsEval both return true when report-only mode
// is enabled, resulting in a call to this function. Therefore we need to
// check that the policy was in fact violated before logging any violations
switch (aViolationType) {
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
if (!this._policy.allowsInlineStyles)
this._asyncReportViolation('self', null, CSPLocalizer.getStr("inlineStyleBlocked"),
'violated base restriction: Inline Stylesheets will not apply',
aSourceFile, aScriptSample, aLineNum);
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
if (!this._policy.allowsInlineScripts)
this._asyncReportViolation('self', null, CSPLocalizer.getStr("inlineScriptBlocked"),
'violated base restriction: Inline Scripts will not execute',
aSourceFile, aScriptSample, aLineNum);
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
if (!this._policy.allowsEvalInScripts)
this._asyncReportViolation('self', null, CSPLocalizer.getStr("scriptFromStringBlocked"),
'violated base restriction: Code will not be created from strings',
aSourceFile, aScriptSample, aLineNum);
break;
function(aViolationType, aSourceFile, aScriptSample, aLineNum, violatedPolicyIndex) {
for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
let policy = this._policies[policyIndex];
// call-sites to the eval/inline checks recieve two return values: allows
// and violates. Policies that are report-only allow the
// loads/compilations but violations should still be reported. Not all
// policies in this nsIContentSecurityPolicy instance will be violated,
// which is why we must check again here.
switch (aViolationType) {
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
if (!policy.allowsInlineStyles) {
var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
if (!policy.allowsInlineScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
if (!policy.allowsEvalInScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
EVAL_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
}
}
},
set reportOnlyMode(val) {
this._reportOnlyMode = val;
},
get reportOnlyMode () {
return this._reportOnlyMode;
},
/*
// Having a setter is a bad idea... opens up the policy to "loosening"
// Instead, use "refinePolicy."
set policy (aStr) {
this._policy = CSPRep.fromString(aStr);
},
*/
/**
* Given an nsIHttpChannel, fill out the appropriate data.
*/
@ -250,26 +275,29 @@ ContentSecurityPolicy.prototype = {
/* ........ Methods .............. */
/**
* Given a new policy, intersects the currently enforced policy with the new
* one and stores the result. The effect is a "tightening" or refinement of
* an old policy. This is called any time a new policy is encountered and
* the effective policy has to be refined.
* Adds a new policy to our list of policies for this CSP context.
* @returns the count of policies.
*/
refinePolicy:
function csp_refinePolicy(aPolicy, selfURI, aSpecCompliant) {
CSPdebug("REFINE POLICY: " + aPolicy);
CSPdebug(" SELF: " + selfURI.asciiSpec);
appendPolicy:
function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
#ifndef MOZ_B2G
CSPdebug("APPENDING POLICY: " + aPolicy);
CSPdebug(" SELF: " + selfURI.asciiSpec);
CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
#endif
// For nested schemes such as view-source: make sure we are taking the
// innermost URI to use as 'self' since that's where we will extract the
// scheme, host and port from
if (selfURI instanceof Ci.nsINestedURI) {
#ifndef MOZ_B2G
CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec);
#endif
selfURI = selfURI.innermostURI;
}
// stay uninitialized until policy merging is done
this._isInitialized = false;
// stay uninitialized until policy setup is done
var newpolicy;
// If there is a policy-uri, fetch the policy, then re-call this function.
// (1) parse and create a CSPRep object
@ -279,29 +307,37 @@ ContentSecurityPolicy.prototype = {
// If we want to be CSP 1.0 spec compliant, use the new parser.
// The old one will be deprecated in the future and will be
// removed at that time.
var newpolicy;
if (aSpecCompliant) {
newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
selfURI,
this._docRequest,
this);
this,
aReportOnly);
} else {
newpolicy = CSPRep.fromString(aPolicy,
selfURI,
this._docRequest,
this);
this,
aReportOnly);
}
// (2) Intersect the currently installed CSPRep object with the new one
var intersect = this._policy.intersectWith(newpolicy);
newpolicy._specCompliant = !!aSpecCompliant;
newpolicy._isInitialized = true;
this._policies.push(newpolicy);
this._cache = {}; // reset cache since effective policy changes
},
// (3) Save the result
this._policy = intersect;
this._policy._specCompliant = !!aSpecCompliant;
this._isInitialized = true;
this._cache = {};
/**
* Removes a policy from the array of policies.
*/
removePolicy:
function csp_removePolicy(index) {
if (index < 0 || index >= this._policies.length) {
CSPdebug("Cannot remove policy " + index + "; not enough policies.");
return;
}
this._policies.splice(index, 1);
this._cache = {}; // reset cache since effective policy changes
},
/**
@ -309,17 +345,29 @@ ContentSecurityPolicy.prototype = {
*/
sendReports:
function(blockedUri, originalUri, violatedDirective,
aSourceFile, aScriptSample, aLineNum) {
var uriString = this._policy.getReportURIs();
violatedPolicyIndex, aSourceFile,
aScriptSample, aLineNum) {
let policy = this._getPolicyInternal(violatedPolicyIndex);
if (!policy) {
CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
return;
}
var uriString = policy.getReportURIs();
var uris = uriString.split(/\s+/);
if (uris.length > 0) {
// see if we need to sanitize the blocked-uri
let blocked = '';
if (originalUri) {
// We've redirected, only report the blocked origin
let clone = blockedUri.clone();
clone.path = '';
blocked = clone.asciiSpec;
try {
let clone = blockedUri.clone();
clone.path = '';
blocked = clone.asciiSpec;
} catch(e) {
CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
}
}
else if (blockedUri instanceof Ci.nsIURI) {
blocked = blockedUri.cloneIgnoringRef().asciiSpec;
@ -360,17 +408,6 @@ ContentSecurityPolicy.prototype = {
var reportString = JSON.stringify(report);
CSPdebug("Constructed violation report:\n" + reportString);
var violationMessage = null;
if (blockedUri["asciiSpec"]) {
violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
} else {
violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
}
this._policy.log(WARN_FLAG, violationMessage,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
// For each URI in the report list, send out a report.
// We make the assumption that all of the URIs are absolute URIs; this
// should be taken care of in CSPRep.fromString (where it converts any
@ -396,7 +433,7 @@ ContentSecurityPolicy.prototype = {
// we need to set an nsIChannelEventSink on the channel object
// so we can tell it to not follow redirects when posting the reports
chan.notificationCallbacks = new CSPReportRedirectSink(this._policy);
chan.notificationCallbacks = new CSPReportRedirectSink(policy);
if (this._docRequest) {
chan.loadGroup = this._docRequest.loadGroup;
}
@ -431,25 +468,68 @@ ContentSecurityPolicy.prototype = {
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
}
}
}
},
/**
* Logs a meaningful CSP warning to the developer console.
*/
logToConsole:
function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
let policy = this._policies[aViolatedPolicyIndex];
switch(aObserverSubject.data) {
case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
break;
case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
break;
case EVAL_VIOLATION_OBSERVER_SUBJECT:
violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
break;
}
var violationMessage = null;
if (blockedUri["asciiSpec"]) {
violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
} else {
violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
}
policy.log(WARN_FLAG, violationMessage,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
},
/**
* Exposed Method to analyze docShell for approved frame ancestry.
* Also sends violation reports if necessary.
* NOTE: Also sends violation reports if necessary.
* @param docShell
* the docShell for this policy's resource.
* @return
* true if the frame ancestry is allowed by this policy.
* true if the frame ancestry is allowed by this policy and the load
* should progress.
*/
permitsAncestry:
function(docShell) {
// Cannot shortcut checking all the policies since violation reports have
// to be triggered if any policy wants it.
var permitted = true;
for (let i = 0; i < this._policies.length; i++) {
if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
permitted = false;
}
}
return permitted;
},
_permitsAncestryInternal:
function(docShell, policy, policyIndex) {
if (!docShell) { return false; }
CSPdebug(" in permitsAncestry(), docShell = " + docShell);
// walk up this docShell tree until we hit chrome
var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
@ -471,7 +551,9 @@ ContentSecurityPolicy.prototype = {
ancestor.userPass = '';
} catch (ex) {}
#ifndef MOZ_B2G
CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
#endif
ancestors.push(ancestor);
}
}
@ -482,17 +564,16 @@ ContentSecurityPolicy.prototype = {
let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
for (let i in ancestors) {
let ancestor = ancestors[i];
if (!this._policy.permits(ancestor, cspContext)) {
if (!policy.permits(ancestor, cspContext)) {
// report the frame-ancestor violation
let directive = this._policy._directives[cspContext];
let violatedPolicy = (directive._isImplicit
? 'default-src' : 'frame-ancestors ')
+ directive.toString();
let directive = policy._directives[cspContext];
let violatedPolicy = 'frame-ancestors ' + directive.toString();
this._asyncReportViolation(ancestors[i], null, violatedPolicy);
this._asyncReportViolation(ancestors[i], null, violatedPolicy,
policyIndex);
// need to lie if we are testing in report-only mode
return this._reportOnlyMode;
return policy._reportOnlyMode;
}
}
return true;
@ -548,63 +629,82 @@ ContentSecurityPolicy.prototype = {
let cp = Ci.nsIContentPolicy;
// iterate through all the _policies and send reports where a policy is
// violated. After the check, determine the overall effect (blocked or
// loaded?) and cache it.
let policyAllowsLoadArray = [];
for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
let policy = this._policies[policyIndex];
#ifndef MOZ_B2G
CSPdebug("policy is " + (this._policy._specCompliant ?
"1.0 compliant" : "pre-1.0"));
CSPdebug("policy is " + (policy._specCompliant ?
"1.0 compliant" : "pre-1.0"));
CSPdebug("policy is " + (policy._reportOnlyMode ?
"report-only" : "blocking"));
#endif
if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policy._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
} else if (aContentType == cp.TYPE_WEBSOCKET && this._policy._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
} else {
cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
}
CSPdebug("shouldLoad cspContext = " + cspContext);
// if the mapping is null, there's no policy, let it through.
if (!cspContext) {
return Ci.nsIContentPolicy.ACCEPT;
}
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = this._policy.permits(aContentLocation, cspContext)
? Ci.nsIContentPolicy.ACCEPT
: Ci.nsIContentPolicy.REJECT_SERVER;
// frame-ancestors is taken care of early on (as this document is loaded)
// If the result is *NOT* ACCEPT, then send report
if (res != Ci.nsIContentPolicy.ACCEPT) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = "unknown directive",
violatedPolicy = "unknown policy";
// The policy might not explicitly declare each source directive (so
// the cspContext may be implicit). If so, we have to report
// violations as appropriate: specific or the default-src directive.
if (this._policy._directives.hasOwnProperty(cspContext)) {
directive = this._policy._directives[cspContext];
violatedPolicy = cspContext + ' ' + directive.toString();
} else if (this._policy._directives.hasOwnProperty("default-src")) {
directive = this._policy._directives["default-src"];
violatedPolicy = "default-src " + directive.toString();
} else {
violatedPolicy = "unknown directive";
CSPdebug('ERROR in blocking content: ' +
'CSP is not sure which part of the policy caused this block');
}
this._asyncReportViolation(aContentLocation, aOriginalUri, violatedPolicy);
} catch(e) {
CSPdebug('---------------- ERROR: ' + e);
if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
} else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
} else {
cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
}
}
let ret = (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
#ifndef MOZ_B2G
CSPdebug("shouldLoad cspContext = " + cspContext);
#endif
// if the mapping is null, there's no policy, let it through.
if (!cspContext) {
return Ci.nsIContentPolicy.ACCEPT;
}
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = policy.permits(aContentLocation, cspContext)
? cp.ACCEPT : cp.REJECT_SERVER;
// record whether the thing should be blocked or just reported.
policyAllowsLoadArray.push(res == cp.ACCEPT || policy._reportOnlyMode);
// frame-ancestors is taken care of early on (as this document is loaded)
// If the result is *NOT* ACCEPT, then send report
if (res != Ci.nsIContentPolicy.ACCEPT) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = "unknown directive",
violatedPolicy = "unknown policy";
// The policy might not explicitly declare each source directive (so
// the cspContext may be implicit). If so, we have to report
// violations as appropriate: specific or the default-src directive.
if (policy._directives.hasOwnProperty(cspContext)) {
directive = policy._directives[cspContext];
violatedPolicy = cspContext + ' ' + directive.toString();
} else if (policy._directives.hasOwnProperty("default-src")) {
directive = policy._directives["default-src"];
violatedPolicy = "default-src " + directive.toString();
} else {
violatedPolicy = "unknown directive";
CSPdebug('ERROR in blocking content: ' +
'CSP is not sure which part of the policy caused this block');
}
this._asyncReportViolation(aContentLocation, aOriginalUri,
violatedPolicy, policyIndex);
} catch(e) {
CSPdebug('---------------- ERROR: ' + e);
}
}
} // end for-each loop over policies
// the ultimate decision is based on whether any policies want to reject
// the load. The array keeps track of whether the policies allowed the
// loads. If any doesn't, we'll reject the load (and cache the result).
let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
cp.REJECT_SERVER : cp.ACCEPT);
if (key) {
this._cache[key] = ret;
}
@ -629,14 +729,17 @@ ContentSecurityPolicy.prototype = {
* topic that a violation occurred. Also triggers report sending. All
* asynchronous on the main thread.
*
* @param blockedContentSource
* @param aBlockedContentSource
* Either a CSP Source (like 'self', as string) or nsIURI: the source
* of the violation.
* @param originalUri
* @param aOriginalUri
* The original URI if the blocked content is a redirect, else null
* @param violatedDirective
* @param aViolatedDirective
* the directive that was violated (string).
* @param observerSubject
* @param aViolatedPolicyIndex
* the index of the policy that was violated (so we know where to send
* the reports).
* @param aObserverSubject
* optional, subject sent to the nsIObservers listening to the CSP
* violation topic.
* @param aSourceFile
@ -647,32 +750,38 @@ ContentSecurityPolicy.prototype = {
* source line number of the violation (if available)
*/
_asyncReportViolation:
function(blockedContentSource, originalUri, violatedDirective, observerSubject,
function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
aViolatedPolicyIndex, aObserverSubject,
aSourceFile, aScriptSample, aLineNum) {
// if optional observerSubject isn't specified, default to the source of
// the violation.
if (!observerSubject)
observerSubject = blockedContentSource;
if (!aObserverSubject)
aObserverSubject = aBlockedContentSource;
// gotta wrap things that aren't nsISupports, since it's sent out to
// observers as such. Objects that are not nsISupports are converted to
// strings and then wrapped into a nsISupportsCString.
if (!(observerSubject instanceof Ci.nsISupports)) {
let d = observerSubject;
observerSubject = Cc["@mozilla.org/supports-cstring;1"]
if (!(aObserverSubject instanceof Ci.nsISupports)) {
let d = aObserverSubject;
aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
.createInstance(Ci.nsISupportsCString);
observerSubject.data = d;
aObserverSubject.data = d;
}
var reportSender = this;
Services.tm.mainThread.dispatch(
function() {
Services.obs.notifyObservers(observerSubject,
Services.obs.notifyObservers(aObserverSubject,
CSP_VIOLATION_TOPIC,
violatedDirective);
reportSender.sendReports(blockedContentSource, originalUri,
violatedDirective,
aViolatedDirective);
reportSender.sendReports(aBlockedContentSource, aOriginalUri,
aViolatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample, aLineNum);
reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
aViolatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample,
aLineNum, aObserverSubject);
}, Ci.nsIThread.DISPATCH_NORMAL);
},
};

View File

@ -60,88 +60,97 @@ CSPService::ShouldLoad(uint32_t aContentType,
nsIPrincipal *aRequestPrincipal,
int16_t *aDecision)
{
if (!aContentLocation)
return NS_ERROR_FAILURE;
if (!aContentLocation)
return NS_ERROR_FAILURE;
#ifdef PR_LOGGING
{
nsAutoCString location;
aContentLocation->GetSpec(location);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSPService::ShouldLoad called for %s", location.get()));
}
#endif
// default decision, CSP can revise it if there's a policy to enforce
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// shortcut for about: chrome: and resource: and javascript: uris since
// they're not subject to CSP content policy checks.
bool schemeMatch = false;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
// (their mappings are null in contentSecurityPolicy.js)
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
return NS_OK;
}
// find the principal of the document that initiated this request and see
// if it has a CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
nsAutoString policy;
csp->GetPolicy(policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Document has CSP: %s",
NS_ConvertUTF16toUTF8(policy).get()));
#endif
// obtain the enforcement decision
// (don't pass aExtra, we use that slot for redirects)
csp->ShouldLoad(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
nullptr,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
{
nsAutoCString location;
aContentLocation->GetSpec(location);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSPService::ShouldLoad called for %s", location.get()));
}
#endif
// default decision, CSP can revise it if there's a policy to enforce
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// shortcut for about: chrome: and resource: and javascript: uris since
// they're not subject to CSP content policy checks.
bool schemeMatch = false;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
// (their mappings are null in contentSecurityPolicy.js)
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
return NS_OK;
}
// find the principal of the document that initiated this request and see
// if it has a CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
{
int numPolicies = 0;
nsresult rv = csp->GetPolicyCount(&numPolicies);
if (NS_SUCCEEDED(rv)) {
for (int i=0; i<numPolicies; i++) {
nsAutoString policy;
csp->GetPolicy(i, policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Document has CSP[%d]: %s", i,
NS_ConvertUTF16toUTF8(policy).get()));
}
}
}
#endif
// obtain the enforcement decision
// (don't pass aExtra, we use that slot for redirects)
csp->ShouldLoad(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
nullptr,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
return NS_OK;
}
NS_IMETHODIMP
@ -154,52 +163,60 @@ CSPService::ShouldProcess(uint32_t aContentType,
nsIPrincipal *aRequestPrincipal,
int16_t *aDecision)
{
if (!aContentLocation)
return NS_ERROR_FAILURE;
if (!aContentLocation)
return NS_ERROR_FAILURE;
// default decision is to accept the item
*aDecision = nsIContentPolicy::ACCEPT;
// default decision is to accept the item
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// find the nsDocument that initiated this request and see if it has a
// CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
nsAutoString policy;
csp->GetPolicy(policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("shouldProcess - document has policy: %s",
NS_ConvertUTF16toUTF8(policy).get()));
#endif
// obtain the enforcement decision
csp->ShouldProcess(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
aExtra,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// find the nsDocument that initiated this request and see if it has a
// CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
{
int numPolicies = 0;
nsresult rv = csp->GetPolicyCount(&numPolicies);
if (NS_SUCCEEDED(rv)) {
for (int i=0; i<numPolicies; i++) {
nsAutoString policy;
csp->GetPolicy(i, policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("shouldProcess - document has policy[%d]: %s", i,
NS_ConvertUTF16toUTF8(policy).get()));
}
}
}
#endif
// obtain the enforcement decision
csp->ShouldProcess(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
aExtra,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
return NS_OK;
}
/* nsIChannelEventSink implementation */

View File

@ -2458,6 +2458,30 @@ nsDocument::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages)
}
}
static nsresult
AppendCSPFromHeader(nsIContentSecurityPolicy* csp, const nsAString& aHeaderValue,
nsIURI* aSelfURI, bool aReportOnly, bool aSpecCompliant)
{
// Need to tokenize the header value since multiple headers could be
// concatenated into one comma-separated list of policies.
// See RFC2616 section 4.2 (last paragraph)
nsresult rv = NS_OK;
nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& policy = tokenizer.nextToken();
rv = csp->AppendPolicy(policy, aSelfURI, aReportOnly, aSpecCompliant);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
return NS_OK;
}
nsresult
nsDocument::InitCSP(nsIChannel* aChannel)
{
@ -2502,15 +2526,23 @@ nsDocument::InitCSP(nsIChannel* aChannel)
bool specCompliantEnabled =
Preferences::GetBool("security.csp.speccompliant");
// If spec compliant pref isn't set, pretend we never got these headers.
if ((!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) &&
!specCompliantEnabled) {
// If spec compliant pref isn't set, pretend we never got
// these headers.
if (!specCompliantEnabled) {
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Got spec compliant CSP headers but pref was not set"));
cspHeaderValue.Truncate();
cspROHeaderValue.Truncate();
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Got spec compliant CSP headers but pref was not set"));
cspHeaderValue.Truncate();
cspROHeaderValue.Truncate();
}
// If the old header is present, warn that it will be deprecated.
if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
// Also, if the new headers AND the old headers were present, warn
// that the old headers will be ignored.
if (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
}
}
@ -2571,19 +2603,13 @@ nsDocument::InitCSP(nsIChannel* aChannel)
}
// used as a "self" identifier for the CSP.
nsCOMPtr<nsIURI> chanURI;
aChannel->GetURI(getter_AddRefs(chanURI));
nsCOMPtr<nsIURI> selfURI;
aChannel->GetURI(getter_AddRefs(selfURI));
// Store the request context for violation reports
csp->ScanRequestData(httpChannel);
// The CSP is refined in the following order:
// 1. Default app CSP, if applicable
// 2. App manifest CSP, if provided
// 3. HTTP header CSP, if provided
// Note that since each application of refinePolicy is a set intersection,
// the order in which multiple CSP's are refined does not matter.
// ----- if the doc is an app and we want a default CSP, apply it.
if (applyAppDefaultCSP) {
nsAdoptingString appCSP;
if (appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED) {
@ -2594,92 +2620,43 @@ nsDocument::InitCSP(nsIChannel* aChannel)
NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.certified.CSP.default");
}
if (appCSP)
if (appCSP) {
// Use the 1.0 CSP parser for apps if the pref to do so is set.
csp->RefinePolicy(appCSP, chanURI, specCompliantEnabled);
csp->AppendPolicy(appCSP, selfURI, false, specCompliantEnabled);
}
}
// ----- if the doc is an app and specifies a CSP in its manifest, apply it.
if (applyAppManifestCSP) {
// Use the 1.0 CSP parser for apps if the pref to do so is set.
csp->RefinePolicy(appManifestCSP, chanURI, specCompliantEnabled);
csp->AppendPolicy(appManifestCSP, selfURI, false, specCompliantEnabled);
}
// While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers
// take priority. If any spec-compliant headers are present, the x- headers
// are ignored, and the spec compliant parser is used.
bool cspSpecCompliant = (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty());
// If the old header is present, warn that it will be deprecated.
if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
// Also, if the new headers AND the old headers were present, warn
// that the old headers will be ignored.
if (cspSpecCompliant) {
mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
}
}
// can coexist with x- headers. If both exist, they're both enforced, but
// there's a warning posted in the web console that the x-headers are going
// away.
// ----- if there's a full-strength CSP header, apply it.
bool applyCSPFromHeader =
(( cspSpecCompliant && !cspHeaderValue.IsEmpty()) ||
(!cspSpecCompliant && !cspOldHeaderValue.IsEmpty()));
if (applyCSPFromHeader) {
// Need to tokenize the header value since multiple headers could be
// concatenated into one comma-separated list of policies.
// See RFC2616 section 4.2 (last paragraph)
nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
cspHeaderValue :
cspOldHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& policy = tokenizer.nextToken();
csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
if (!cspOldHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- if there's a report-only CSP header, apply it
if (( cspSpecCompliant && !cspROHeaderValue.IsEmpty()) ||
(!cspSpecCompliant && !cspOldROHeaderValue.IsEmpty())) {
// post a warning and skip report-only CSP when both read only and regular
// CSP policies are present since CSP only allows one policy and it can't
// be partially report-only.
if (applyAppDefaultCSP || applyCSPFromHeader) {
mCSPWebConsoleErrorQueue.Add("ReportOnlyCSPIgnored");
#ifdef PR_LOGGING
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Skipped report-only CSP init for document %p because another, enforced policy is set", this));
#endif
} else {
// we can apply the report-only policy because there's no other CSP
// applied.
csp->SetReportOnlyMode(true);
if (!cspHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// Need to tokenize the header value since multiple headers could be
// concatenated into one comma-separated list of policies.
// See RFC2616 section 4.2 (last paragraph)
nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
cspROHeaderValue :
cspOldROHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& policy = tokenizer.nextToken();
csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP (report-only) refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
}
// ----- if there's a report-only CSP header, apply it.
if (!cspOldROHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
NS_ENSURE_SUCCESS(rv, rv);
}
if (!cspROHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- Enforce frame-ancestor policy on any applied policies

View File

@ -92,6 +92,11 @@ MOCHITEST_FILES := \
test_CSP_bug888172.html \
file_CSP_bug888172.html \
file_CSP_bug888172.sjs \
test_bug836922_npolicies.html \
file_bug836922_npolicies.html \
file_bug836922_npolicies.html^headers^ \
file_bug836922_npolicies_violation.sjs \
file_bug836922_npolicies_ro_violation.sjs \
$(NULL)
MOCHITEST_CHROME_FILES := \

View File

@ -0,0 +1,15 @@
<html>
<head>
<link rel='stylesheet' type='text/css'
href='/tests/content/base/test/csp/file_CSP.sjs?testid=css_self&type=text/css' />
<link rel='stylesheet' type='text/css'
href='http://example.com/tests/content/base/test/csp/file_CSP.sjs?testid=css_examplecom&type=text/css' />
</head>
<body>
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img_self&type=img/png"> </img>
<img src="http://example.com/tests/content/base/test/csp/file_CSP.sjs?testid=img_examplecom&type=img/png"> </img>
<script src='/tests/content/base/test/csp/file_CSP.sjs?testid=script_self&type=text/javascript'></script>
</body>
</html>

View File

@ -0,0 +1,2 @@
content-security-policy: default-src 'self'; img-src 'none'; report-uri http://mochi.test:8888/tests/content/base/test/csp/file_bug836922_npolicies_violation.sjs
content-security-policy-report-only: default-src *; img-src 'self'; script-src 'none'; report-uri http://mochi.test:8888/tests/content/base/test/csp/file_bug836922_npolicies_ro_violation.sjs

View File

@ -0,0 +1,53 @@
// SJS file that receives violation reports and then responds with nothing.
const CC = Components.Constructor;
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream");
const STATE_KEY = "bug836922_ro_violations";
function handleRequest(request, response)
{
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
if ('results' in query) {
// if asked for the received data, send it.
response.setHeader("Content-Type", "text/javascript", false);
if (getState(STATE_KEY)) {
response.write(getState(STATE_KEY));
} else {
// no state has been recorded.
response.write(JSON.stringify({}));
}
} else if ('reset' in query) {
//clear state
setState(STATE_KEY, JSON.stringify(null));
} else {
// ... otherwise, just respond "ok".
response.write("null");
var bodystream = new BinaryInputStream(request.bodyInputStream);
var avail;
var bytes = [];
while ((avail = bodystream.available()) > 0)
Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
var data = String.fromCharCode.apply(null, bytes);
// figure out which test was violating a policy
var testpat = new RegExp("testid=([a-z0-9_]+)");
var testid = testpat.exec(data)[1];
// store the violation in the persistent state
var s = JSON.parse(getState(STATE_KEY) || "{}");
s[testid] ? s[testid]++ : s[testid] = 1;
setState(STATE_KEY, JSON.stringify(s));
}
}

View File

@ -0,0 +1,59 @@
// SJS file that receives violation reports and then responds with nothing.
const CC = Components.Constructor;
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream");
const STATE = "bug836922_violations";
function handleRequest(request, response)
{
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
if ('results' in query) {
// if asked for the received data, send it.
response.setHeader("Content-Type", "text/javascript", false);
if (getState(STATE)) {
response.write(getState(STATE));
} else {
// no state has been recorded.
response.write(JSON.stringify({}));
}
} else if ('reset' in query) {
//clear state
setState(STATE, JSON.stringify(null));
} else {
// ... otherwise, just respond "ok".
response.write("null");
var bodystream = new BinaryInputStream(request.bodyInputStream);
var avail;
var bytes = [];
while ((avail = bodystream.available()) > 0)
Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
var data = String.fromCharCode.apply(null, bytes);
// figure out which test was violating a policy
var testpat = new RegExp("testid=([a-z0-9_]+)");
var testid = testpat.exec(data)[1];
// store the violation in the persistent state
var s = getState(STATE);
if (!s) s = "{}";
s = JSON.parse(s);
if (!s) s = {};
if (!s[testid]) s[testid] = 0;
s[testid]++;
setState(STATE, JSON.stringify(s));
}
}

View File

@ -0,0 +1,255 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Content Security Policy multiple policy support (regular and Report-Only mode)</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/csp/";
// These are test results: verified indicates whether or not the test has run.
// true/false is the pass/fail result.
window.loads = {
css_self: {expected: true, verified: false},
css_examplecom: {expected: false, verified: false},
img_self: {expected: false, verified: false},
img_examplecom: {expected: false, verified: false},
script_self: {expected: true, verified: false},
};
window.violation_reports = {
css_self:
{expected: 0, expected_ro: 0}, /* totally fine */
css_examplecom:
{expected: 1, expected_ro: 0}, /* violates enforced CSP */
img_self:
{expected: 1, expected_ro: 0}, /* violates enforced CSP */
img_examplecom:
{expected: 1, expected_ro: 1}, /* violates both CSPs */
script_self:
{expected: 0, expected_ro: 1}, /* violates report-only */
};
// This is used to watch the blocked data bounce off CSP and allowed data
// get sent out to the wire. This also watches for violation reports to go out.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if(!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
if (topic === "http-on-modify-request") {
var asciiSpec = SpecialPowers.getPrivilegedProps(
SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"),
"URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
// violation reports don't come through here, but the requested resources do
// if the test has already finished, move on. Some things throw multiple
// requests (preloads and such)
try {
if (window.loads[testid].verified) return;
} catch(e) { return; }
// these are requests that were allowed by CSP
var testid = testpat.exec(asciiSpec)[1];
window.testResult(testid, 'allowed', asciiSpec + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
// if the violated policy was report-only, the resource will still be
// loaded even if this topic is notified.
var asciiSpec = SpecialPowers.getPrivilegedProps(
SpecialPowers.do_QueryInterface(subject, "nsIURI"),
"asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
// if the test has already finished, move on.
try {
if (window.loads[testid].verified) return;
} catch(e) { return; }
// record the ones that were supposed to be blocked, but don't use this
// as an indicator for tests that are not blocked but do generate reports.
// We skip recording the result if the load is expected since a
// report-only policy will generate a request *and* a violation note.
if (!window.loads[testid].expected) {
window.testResult(testid,
'blocked',
asciiSpec + " blocked by \"" + data + "\"");
}
}
// if any test is unverified, keep waiting
for (var v in window.loads) {
if(!window.loads[v].verified) {
return;
}
}
window.bug836922examiner.remove();
window.resultPoller.pollForFinish();
},
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
}
}
window.bug836922examiner = new examiner();
// Poll for results and see if enough reports came in. Keep trying
// for a few seconds before failing with lack of reports.
// Have to do this because there's a race between the async reporting
// and this test finishing, and we don't want to win the race.
window.resultPoller = {
POLL_ATTEMPTS_LEFT: 14,
pollForFinish:
function() {
var vr = resultPoller.tallyReceivedReports();
if (resultPoller.verifyReports(vr, resultPoller.POLL_ATTEMPTS_LEFT < 1)) {
// report success condition.
resultPoller.resetReportServer();
SimpleTest.finish();
} else {
resultPoller.POLL_ATTEMPTS_LEFT--;
// try again unless we reached the threshold.
setTimeout(resultPoller.pollForFinish, 100);
}
},
resetReportServer:
function() {
var xhr = new XMLHttpRequest();
var xhr_ro = new XMLHttpRequest();
xhr.open("GET", "file_bug836922_npolicies_violation.sjs?reset", false);
xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?reset", false);
xhr.send(null);
xhr_ro.send(null);
},
tallyReceivedReports:
function() {
var xhr = new XMLHttpRequest();
var xhr_ro = new XMLHttpRequest();
xhr.open("GET", "file_bug836922_npolicies_violation.sjs?results", false);
xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?results", false);
xhr.send(null);
xhr_ro.send(null);
var received = JSON.parse(xhr.responseText);
var received_ro = JSON.parse(xhr_ro.responseText);
var results = {enforced: {}, reportonly: {}};
for (var r in window.violation_reports) {
results.enforced[r] = 0;
results.reportonly[r] = 0;
}
for (var r in received) {
results.enforced[r] += received[r];
}
for (var r in received_ro) {
results.reportonly[r] += received_ro[r];
}
return results;
},
verifyReports:
function(receivedCounts, lastAttempt) {
for (var r in window.violation_reports) {
var exp = window.violation_reports[r].expected;
var exp_ro = window.violation_reports[r].expected_ro;
var rec = receivedCounts.enforced[r];
var rec_ro = receivedCounts.reportonly[r];
// if this test breaks, these are helpful dumps:
//dump(">>> Verifying " + r + "\n");
//dump(" > Expected: " + exp + " / " + exp_ro + " (ro)\n");
//dump(" > Received: " + rec + " / " + rec_ro + " (ro) \n");
// in all cases, we're looking for *at least* the expected number of
// reports of each type (there could be more in some edge cases).
// If there are not enough, we keep waiting and poll the server again
// later. If there are enough, we can successfully finish.
if (exp == 0)
is(rec, 0,
"Expected zero enforced-policy violation " +
"reports for " + r + ", got " + rec);
else if (lastAttempt)
ok(rec >= exp,
"Received (" + rec + "/" + exp + ") " +
"enforced-policy reports for " + r);
else if (rec < exp)
return false; // continue waiting for more
if(exp_ro == 0)
is(rec_ro, 0,
"Expected zero report-only-policy violation " +
"reports for " + r + ", got " + rec_ro);
else if (lastAttempt)
ok(rec_ro >= exp_ro,
"Received (" + rec_ro + "/" + exp_ro + ") " +
"report-only-policy reports for " + r);
else if (rec_ro < exp_ro)
return false; // continue waiting for more
}
// if we complete the loop, we've found all of the violation
// reports we expect.
if (lastAttempt) return true;
// Repeat successful tests once more to record successes via ok()
return resultPoller.verifyReports(receivedCounts, true);
}
};
window.testResult = function(testname, result, msg) {
// otherwise, make sure the allowed ones are expected and blocked ones are not.
if (window.loads[testname].expected) {
is(result, 'allowed', ">> " + msg);
} else {
is(result, 'blocked', ">> " + msg);
}
window.loads[testname].verified = true;
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", true]]},
function() {
// save this for last so that our listeners are registered.
// ... this loads the testbed of good and bad requests.
document.getElementById('cspframe').src = 'http://mochi.test:8888' + path + 'file_bug836922_npolicies.html';
});
</script>
</pre>
</body>
</html>

View File

@ -67,7 +67,7 @@ listener.prototype = {
// nsIContentSecurityPolicy instance. The problem is, this cspr_str is a
// string and not a policy due to the way it's exposed from
// nsIContentSecurityPolicy, so we have to re-parse it.
let cspr_str = this._csp.policy;
let cspr_str = this._csp.getPolicy(0);
let cspr = CSPRep.fromString(cspr_str, mkuri(DOCUMENT_URI));
// and in reparsing it, we lose the 'self' relationships, so need to also

View File

@ -79,10 +79,8 @@ function makeTest(id, expectedJSON, useReportOnlyPolicy, callback) {
csp.scanRequestData(selfchan);
// Load up the policy
csp.refinePolicy(policy, selfuri, false);
// set as report-only if that's the case
if (useReportOnlyPolicy) csp.reportOnlyMode = true;
csp.appendPolicy(policy, selfuri, useReportOnlyPolicy, false);
// prime the report server
var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
@ -100,7 +98,7 @@ function run_test() {
// test that inline script violations cause a report.
makeTest(0, {"blocked-uri": "self"}, false,
function(csp) {
let inlineOK = true, oReportViolation = {};
let inlineOK = true, oReportViolation = {'value': false};
inlineOK = csp.getAllowsInlineScript(oReportViolation);
// this is not a report only policy, so it better block inline scripts
@ -108,17 +106,19 @@ function run_test() {
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
0);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
0);
}
});
// test that eval violations cause a report.
makeTest(1, {"blocked-uri": "self"}, false,
function(csp) {
let evalOK = true, oReportViolation = {};
let evalOK = true, oReportViolation = {'value': false};
evalOK = csp.getAllowsEval(oReportViolation);
// this is not a report only policy, so it better block eval
@ -126,11 +126,13 @@ function run_test() {
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
1);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL,
selfuri.asciiSpec,
"script sample",
1);
}
});
makeTest(2, {"blocked-uri": "http://blocked.test/foo.js"}, false,
@ -144,25 +146,28 @@ function run_test() {
// test that inline script violations cause a report in report-only policy
makeTest(3, {"blocked-uri": "self"}, true,
function(csp) {
let inlineOK = true, oReportViolation = {};
let inlineOK = true, oReportViolation = {'value': false};
inlineOK = csp.getAllowsInlineScript(oReportViolation);
// this is a report only policy, so it better allow inline scripts
do_check_true(inlineOK);
// ... but still cause reports to go out
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
3);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
3);
}
});
// test that eval violations cause a report in report-only policy
makeTest(4, {"blocked-uri": "self"}, true,
function(csp) {
let evalOK = true, oReportViolation = {};
let evalOK = true, oReportViolation = {'value': false};
evalOK = csp.getAllowsEval(oReportViolation);
// this is a report only policy, so it better allow eval
@ -170,10 +175,12 @@ function run_test() {
// ... but still cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
4);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
4);
}
});
}

View File

@ -141,15 +141,6 @@ test(
do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c"
});
test(
function test_CSPHost_intersectWith() {
var h = CSPHost.fromString("*.b.c");
//"*.a.b.c ^ *.b.c should be *.a.b.c"
do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString());
//"*.b.c ^ *.d.e should not work (null)"
do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e")));
});
///////////////////// Test the Source object //////////////////////
@ -317,42 +308,6 @@ test(
do_check_false(wildcardHostSourceList.permits("http://barbaz.com"));
});
test(
function test_CSPSourceList_intersect() {
// for this test, 'self' values are irrelevant
// policy a /\ policy b intersects policies, not context (where 'self'
// values come into play)
var nullSourceList = CSPSourceList.fromString("'none'");
var simpleSourceList = CSPSourceList.fromString("http://a.com");
var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88");
var singleFooSourceList = CSPSourceList.fromString("https://foo.com");
var allSourceList = CSPSourceList.fromString("*");
//"Intersection of one source with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone());
//"Intersection of two sources with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone());
//"Intersection of '*' with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(allSourceList).isNone());
//"Intersection of one source with '*' source list should be one source.");
do_check_equivalent(allSourceList.intersectWith(simpleSourceList),
simpleSourceList);
//"Intersection of two sources with '*' source list should be two sources.");
do_check_equivalent(allSourceList.intersectWith(doubleSourceList),
doubleSourceList);
//"Non-overlapping source lists should intersect to 'none'");
do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone());
//"subset and superset should intersect to subset.");
do_check_equivalent(singleFooSourceList,
doubleSourceList.intersectWith(singleFooSourceList));
//TODO: write more tests?
});
///////////////////// Test the Whole CSP rep object //////////////////////
test(
@ -680,7 +635,7 @@ test(function test_FrameAncestor_ignores_userpass_bug779918() {
function testPermits(aChildUri, aParentUri, aContentType) {
let cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
.createInstance(Ci.nsIContentSecurityPolicy);
cspObj.refinePolicy(testPolicy, aChildUri, false);
cspObj.appendPolicy(testPolicy, aChildUri, false, false);
let docshellparent = Cc["@mozilla.org/docshell;1"]
.createInstance(Ci.nsIDocShell);
let docshellchild = Cc["@mozilla.org/docshell;1"]
@ -909,56 +864,6 @@ test(
do_check_false(p_none.permits("http://bar.com"));
});
test(
function test_bug783497_refinePolicyIssues() {
const firstPolicy = "allow 'self'; img-src 'self'; script-src 'self'; options 'bogus-option'";
const secondPolicy = "default-src 'none'; script-src 'self'";
var cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
.createInstance(Ci.nsIContentSecurityPolicy);
var selfURI = URI("http://self.com/");
function testPermits(aUri, aContentType) {
return cspObj.shouldLoad(aContentType, aUri, null, null, null, null)
== Ci.nsIContentPolicy.ACCEPT;
};
// everything is allowed by the default policy
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_true(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
// fold in the first policy
cspObj.refinePolicy(firstPolicy, selfURI, false);
// script-src and img-src are limited to self after the first policy
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_false(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
// fold in the second policy
cspObj.refinePolicy(secondPolicy, selfURI, false);
// script-src is self and img-src is none after the merge
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_false(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
});
test(
function test_bug764937_defaultSrcMissing() {
@ -974,7 +879,7 @@ test(
};
const policy = "script-src 'self'";
cspObjSpecCompliant.refinePolicy(policy, selfURI, true);
cspObjSpecCompliant.appendPolicy(policy, selfURI, false, true);
// Spec-Compliant policy default-src defaults to *.
// This means all images are allowed, and only 'self'
@ -992,7 +897,7 @@ test(
URI("http://bar.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
cspObjOld.refinePolicy(policy, selfURI, false);
cspObjOld.appendPolicy(policy, selfURI, false, false);
// non-Spec-Compliant policy default-src defaults to 'none'
// This means all images are blocked, and so are all scripts (because the

View File

@ -6,9 +6,6 @@ const C_i = Components.interfaces;
const UNORDERED_TYPE = C_i.nsIDOMXPathResult.ANY_UNORDERED_NODE_TYPE;
// Instantiate nsIDOMScriptObjectFactory so that DOMException is usable in xpcshell
Components.classesByID["{9eb760f0-4380-11d2-b328-00805f8a3859}"].getService(C_i.nsISupports);
/**
* Determine if the data node has only ignorable white-space.
*

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<meta charset="UTF-8">
<script>
function f()
{
function spin() {
for (var i = 0; i < 8; ++i) {
var x = new XMLHttpRequest();
x.open('GET', 'data:text/html,' + i, false);
x.send();
}
}
window.addEventListener("popstate", spin, false);
window.close();
window.location = "#c";
finish();
}
function start()
{
var html = "<script>" + f + "<\/script><body onload=f()>";
var win = window.open("data:text/html," + encodeURIComponent(html), null, "width=300,height=300");
win.finish = function() { document.documentElement.removeAttribute("class"); };
}
</script>
</head>
<body onload="start();"></body>
</html>

View File

@ -11,3 +11,4 @@ load 500328-1.html
load 514779-1.xhtml
load 614499-1.html
load 678872-1.html
skip-if(Android||B2G) pref(dom.disable_open_during_load,false) load 914521.html

View File

@ -9214,18 +9214,23 @@ nsDocShell::InternalLoad(nsIURI * aURI,
SetDocCurrentStateObj(mOSHE);
// Dispatch the popstate and hashchange events, as appropriate.
if (mScriptGlobal) {
//
// The event dispatch below can cause us to re-enter script and
// destroy the docshell, nulling out mScriptGlobal. Hold a stack
// reference to avoid null derefs. See bug 914521.
nsRefPtr<nsGlobalWindow> win = mScriptGlobal;
if (win) {
// Fire a hashchange event URIs differ, and only in their hashes.
bool doHashchange = sameExceptHashes && !curHash.Equals(newHash);
if (historyNavBetweenSameDoc || doHashchange) {
mScriptGlobal->DispatchSyncPopState();
win->DispatchSyncPopState();
}
if (doHashchange) {
// Make sure to use oldURI here, not mCurrentURI, because by
// now, mCurrentURI has changed!
mScriptGlobal->DispatchAsyncHashchange(oldURI, aURI);
win->DispatchAsyncHashchange(oldURI, aURI);
}
}

View File

@ -551,6 +551,10 @@ ManifestHelper.prototype = {
return this._localeProp("description");
},
get type() {
return this._localeProp("type");
},
get version() {
return this._localeProp("version");
},

View File

@ -0,0 +1,25 @@
/* 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/. */
#include "InterAppComm.h"
#include "nsContentUtils.h"
#include "nsPIDOMWindow.h"
#include "nsJSPrincipals.h"
#include "mozilla/Preferences.h"
#include "AccessCheck.h"
using namespace mozilla::dom;
/* static */ bool
InterAppComm::EnabledForScope(JSContext* /* unused */, JSObject* aObj)
{
// Disable the constructors if they're disabled by the preference for sure.
if (!Preferences::GetBool("dom.inter-app-communication-api.enabled", false)) {
return false;
}
// Only expose the constructors to the chrome codes for Gecko internal uses.
// The content pages shouldn't be aware of the constructors.
return xpc::AccessCheck::isChrome(aObj);
}

View File

@ -0,0 +1,24 @@
/* 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/. */
#ifndef mozilla_dom_apps_InterAppComm_h
#define mozilla_dom_apps_InterAppComm_h
// Forward declarations.
struct JSContext;
class JSObject;
namespace mozilla {
namespace dom {
class InterAppComm
{
public:
static bool EnabledForScope(JSContext* /* unused */, JSObject* aObj);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_apps_InterAppComm_h

View File

@ -0,0 +1,18 @@
component {9dbfa904-0718-11e3-8e77-0721a45514b8} InterAppConnection.js
contract @mozilla.org/dom/inter-app-connection;1 {9dbfa904-0718-11e3-8e77-0721a45514b8}
component {6a77e9e0-0645-11e3-b90b-73bb7c78e06a} InterAppConnection.js
contract @mozilla.org/dom/inter-app-connection-request;1 {6a77e9e0-0645-11e3-b90b-73bb7c78e06a}
component {c66e0f8c-e3cb-11e2-9e85-43ef6244b884} InterAppMessagePort.js
contract @mozilla.org/dom/inter-app-message-port;1 {c66e0f8c-e3cb-11e2-9e85-43ef6244b884}
component {3dd15ce6-e7be-11e2-82bc-77967e7a63e6} InterAppCommService.js
contract @mozilla.org/inter-app-communication-service;1 {3dd15ce6-e7be-11e2-82bc-77967e7a63e6}
category profile-after-change InterAppCommService @mozilla.org/inter-app-communication-service;1
component {d7c7a466-f91d-11e2-812a-6fab12ece58e} InterAppConnection.js
contract @mozilla.org/dom/system-messages/wrapper/connection;1 {d7c7a466-f91d-11e2-812a-6fab12ece58e}
component {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad} InterAppMessagePort.js
contract @mozilla.org/dom/inter-app-message-event;1 {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}

View File

@ -0,0 +1,879 @@
/* 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/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
function InterAppCommService() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
kMessages.forEach(function(aMsg) {
ppmm.addMessageListener(aMsg, this);
}, this);
// This matrix is used for saving the inter-app connection info registered in
// the app manifest. The object literal is defined as below:
//
// {
// "keyword1": {
// "subAppManifestURL1": {
// /* subscribed info */
// },
// "subAppManifestURL2": {
// /* subscribed info */
// },
// ...
// },
// "keyword2": {
// "subAppManifestURL3": {
// /* subscribed info */
// },
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://subApp1.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp1.gaiamobile.org/handler.html",
// description: "blah blah",
// appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED,
// rules: { ... }
// },
// "app://subApp2.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp2.gaiamobile.org/handler.html",
// description: "blah blah",
// appStatus: Ci.nsIPrincipal.APP_STATUS_PRIVILEGED,
// rules: { ... }
// }
// },
// "bar": {
// "app://subApp3.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp3.gaiamobile.org/handler.html",
// description: "blah blah",
// appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED,
// rules: { ... }
// }
// }
// }
//
// TODO Bug 908999 - Update registered connections when app gets uninstalled.
this._registeredConnections = {};
// This matrix is used for saving the permitted connections, which allows
// the messaging between publishers and subscribers. The object literal is
// defined as below:
//
// {
// "keyword1": {
// "pubAppManifestURL1": [
// "subAppManifestURL1",
// "subAppManifestURL2",
// ...
// ],
// "pubAppManifestURL2": [
// "subAppManifestURL3",
// "subAppManifestURL4",
// ...
// ],
// ...
// },
// "keyword2": {
// "pubAppManifestURL3": [
// "subAppManifestURL5",
// ...
// ],
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://pubApp1.gaiamobile.org/manifest.webapp": [
// "app://subApp1.gaiamobile.org/manifest.webapp",
// "app://subApp2.gaiamobile.org/manifest.webapp"
// ],
// "app://pubApp2.gaiamobile.org/manifest.webapp": [
// "app://subApp3.gaiamobile.org/manifest.webapp",
// "app://subApp4.gaiamobile.org/manifest.webapp"
// ]
// },
// "bar": {
// "app://pubApp3.gaiamobile.org/manifest.webapp": [
// "app://subApp5.gaiamobile.org/manifest.webapp",
// ]
// }
// }
//
// TODO Bug 908999 - Update allowed connections when app gets uninstalled.
this._allowedConnections = {};
// This matrix is used for saving the caller info from the content process,
// which is indexed by a random UUID, to know where to return the promise
// resolvser's callback when the prompt UI for allowing connections returns.
// An example of the object literal is shown as below:
//
// {
// "fooID": {
// outerWindowID: 12,
// requestID: 34,
// target: pubAppTarget1
// },
// "barID": {
// outerWindowID: 56,
// requestID: 78,
// target: pubAppTarget2
// }
// }
//
// where |outerWindowID| is the ID of the window requesting the connection,
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
}
InterAppCommService.prototype = {
registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aAppStatus, aRules) {
let manifestURL = aManifestURI.spec;
let pageURL = aHandlerPageURI.spec;
if (DEBUG) {
debug("registerConnection: aKeyword: " + aKeyword +
" manifestURL: " + manifestURL + " pageURL: " + pageURL +
" aDescription: " + aDescription + " aAppStatus: " + aAppStatus +
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
" aRules.manifestURLs: " + aRules.manifestURLs +
" aRules.installOrigins: " + aRules.installOrigins);
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
subAppManifestURLs = this._registeredConnections[aKeyword] = {};
}
subAppManifestURLs[manifestURL] = {
pageURL: pageURL,
description: aDescription,
appStatus: aAppStatus,
rules: aRules,
manifestURL: manifestURL
};
},
_matchMinimumAccessLevel: function(aRules, aAppStatus) {
if (!aRules || !aRules.minimumAccessLevel) {
if (DEBUG) {
debug("rules.minimumAccessLevel is not available. No need to match.");
}
return true;
}
let minAccessLevel = aRules.minimumAccessLevel;
switch (minAccessLevel) {
case "web":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "privileged":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "certified":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
}
if (DEBUG) {
debug("rules.minimumAccessLevel is not matched!" +
" minAccessLevel: " + minAccessLevel +
" aAppStatus : " + aAppStatus);
}
return false;
},
_matchManifestURLs: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.manifestURLs)) {
if (DEBUG) {
debug("rules.manifestURLs is not available. No need to match.");
}
return true;
}
let manifestURLs = aRules.manifestURLs;
if (manifestURLs.indexOf(aManifestURL) != -1) {
return true;
}
if (DEBUG) {
debug("rules.manifestURLs is not matched!" +
" manifestURLs: " + manifestURLs +
" aManifestURL : " + aManifestURL);
}
return false;
},
_matchInstallOrigins: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.installOrigins)) {
if (DEBUG) {
debug("rules.installOrigins is not available. No need to match.");
}
return true;
}
let installOrigin =
appsService.getAppByManifestURL(aManifestURL).installOrigin;
let installOrigins = aRules.installOrigins;
if (installOrigins.indexOf(installOrigin) != -1) {
return true;
}
if (DEBUG) {
debug("rules.installOrigins is not matched!" +
" aManifestURL: " + aManifestURL +
" installOrigins: " + installOrigins +
" installOrigin : " + installOrigin);
}
return false;
},
_matchRules: function(aPubAppManifestURL, aPubAppStatus, aPubRules,
aSubAppManifestURL, aSubAppStatus, aSubRules) {
// TODO Bug 907068 In the initiative step, we only expose this API to
// certified apps to meet the time line. Eventually, we need to make
// it available for the non-certified apps as well. For now, only the
// certified apps can match the rules.
if (aPubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
aSubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
if (DEBUG) {
debug("Only certified apps are allowed to do connections.");
}
return false;
}
if (!aPubRules && !aSubRules) {
if (DEBUG) {
debug("No rules for publisher and subscriber. No need to match.");
}
return true;
}
// Check minimumAccessLevel.
if (!this._matchMinimumAccessLevel(aPubRules, aSubAppStatus) ||
!this._matchMinimumAccessLevel(aSubRules, aPubAppStatus)) {
return false;
}
// Check manifestURLs.
if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
!this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check installOrigins.
if (!this._matchInstallOrigins(aPubRules, aSubAppManifestURL) ||
!this._matchInstallOrigins(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check developers.
// TODO Do we really want to check this? This one seems naive.
if (DEBUG) debug("All rules are matched.");
return true;
},
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
if (DEBUG) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
}
if (aAllowedSubAppManifestURLs.length == 0) {
if (DEBUG) debug("No apps are allowed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let messagePortIDs = [];
aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
if (!subscribedInfo) {
if (DEBUG) {
debug("The sunscribed info is not available. Skipping: " +
aAllowedSubAppManifestURL);
}
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
{ keyword: aKeyword,
messagePortID: messagePortID },
Services.io.newURI(subscribedInfo.pageURL, null, null),
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
}, this);
if (messagePortIDs.length == 0) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
// Return the message port IDs to open the message ports for the publisher.
if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
{ keyword: aKeyword,
messagePortIDs: messagePortIDs,
oid: aOuterWindowID, requestID: aRequestID });
},
_connect: function(aMessage, aTarget) {
let keyword = aMessage.keyword;
let pubRules = aMessage.rules;
let pubAppManifestURL = aMessage.manifestURL;
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let pubAppStatus = aMessage.appStatus;
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
if (DEBUG) {
debug("No apps are subscribed for this connection. Returning.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
// Fetch the apps that used to be allowed to connect before, so that
// users don't need to select/allow them again. That is, we only pop up
// the prompt UI for the *new* connections.
let allowedSubAppManifestURLs = [];
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (allowedPubAppManifestURLs &&
allowedPubAppManifestURLs[pubAppManifestURL]) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
}
// Check rules to see if a subscribed app is allowed to connect.
let appsToSelect = [];
for (let subAppManifestURL in subAppManifestURLs) {
if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
if (DEBUG) {
debug("Don't need to select again. Skipping: " + subAppManifestURL);
}
continue;
}
// Only rule-matched publishers/subscribers are allowed to connect.
let subscribedInfo = subAppManifestURLs[subAppManifestURL];
let subAppStatus = subscribedInfo.appStatus;
let subRules = subscribedInfo.rules;
let matched =
this._matchRules(pubAppManifestURL, pubAppStatus, pubRules,
subAppManifestURL, subAppStatus, subRules);
if (!matched) {
if (DEBUG) {
debug("Rules are not matched. Skipping: " + subAppManifestURL);
}
continue;
}
appsToSelect.push({
manifestURL: subAppManifestURL,
description: subscribedInfo.description
});
}
if (appsToSelect.length == 0) {
if (DEBUG) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
// Remember the caller info with an UUID so that we can know where to
// return the promise resolver's callback when the prompt UI returns.
let callerID = UUIDGenerator.generateUUID().toString();
this._promptUICallers[callerID] = {
outerWindowID: outerWindowID,
requestID: requestID,
target: aTarget
};
// TODO Bug 897169 Temporarily disable the notification for popping up
// the prompt until the UX/UI for the prompt is confirmed.
//
// TODO Bug 908191 We need to change the way of interaction between API and
// run-time prompt from observer notification to xpcom-interface caller.
//
/*
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, "inter-app-comm-select-app",
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
appsToSelect: appsToSelect }));
*/
// TODO Bug 897169 Simulate the return of the app-selected result by
// the prompt, which always allows the connection. This dummy codes
// will be removed when the UX/UI for the prompt is ready.
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
selectedApps: appsToSelect }));
},
_getConnections: function(aMessage, aTarget) {
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let connections = [];
for (let keyword in this._allowedConnections) {
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[allowedPubAppManifestURL];
allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
connections.push({ keyword: keyword,
pubAppManifestURL: allowedPubAppManifestURL,
subAppManifestURL: allowedSubAppManifestURL });
});
}
}
aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
{ connections: connections,
oid: outerWindowID, requestID: requestID });
},
_cancelConnection: function(aMessage) {
let keyword = aMessage.keyword;
let pubAppManifestURL = aMessage.pubAppManifestURL;
let subAppManifestURL = aMessage.subAppManifestURL;
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
if (DEBUG) debug("keyword is not found: " + keyword);
return;
}
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[pubAppManifestURL];
if (!allowedSubAppManifestURLs) {
if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
return;
}
let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
if (index == -1) {
if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
return;
}
if (DEBUG) debug("Cancelling the connection.");
allowedSubAppManifestURLs.splice(index, 1);
// Clean up the parent entries if needed.
if (allowedSubAppManifestURLs.length == 0) {
delete allowedPubAppManifestURLs[pubAppManifestURL];
if (Object.keys(allowedPubAppManifestURLs).length == 0) {
delete this._allowedConnections[keyword];
}
}
if (DEBUG) debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
if (DEBUG) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
}
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
if (DEBUG) {
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
}
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to register.");
}
return;
}
if (DEBUG) debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
if (DEBUG) {
debug("Checking if the other port used to send messages but queued.");
}
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to unregister.");
}
return;
}
if (DEBUG) {
debug("Unregistering message port for " + manifestURL);
}
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
return
}
if (DEBUG) debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
if (DEBUG) {
debug("The receiver's target is not ready yet. Queuing the message.");
}
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
let callerID = aData.callerID;
let caller = this._promptUICallers[callerID];
if (!caller) {
if (DEBUG) debug("Error! Cannot find the caller.");
return;
}
delete this._promptUICallers[callerID];
let outerWindowID = caller.outerWindowID;
let requestID = caller.requestID;
let target = caller.target;
let manifestURL = aData.manifestURL;
let keyword = aData.keyword;
let selectedApps = aData.selectedApps;
if (selectedApps.length == 0) {
if (DEBUG) debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
// Find the entry of allowed connections to add the selected apps.
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
}
let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
if (!allowedSubAppManifestURLs) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
}
// Add the selected app into the existing set of allowed connections.
selectedApps.forEach(function(aSelectedApp) {
let allowedSubAppManifestURL = aSelectedApp.manifestURL;
if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
}
});
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
let target = aMessage.target;
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (aMessage.name !== "child-process-shutdown" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
if (DEBUG) {
debug("Got message from a process carrying illegal manifest URL.");
}
return null;
}
}
switch (aMessage.name) {
case "Webapps:Connect":
this._connect(message, target);
break;
case "Webapps:GetConnections":
this._getConnections(message, target);
break;
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
kMessages.forEach(function(aMsg) {
ppmm.removeMessageListener(aMsg, this);
}, this);
ppmm = null;
break;
case "inter-app-comm-select-app-result":
if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
this._handleSelectcedApps(JSON.parse(aData));
break;
}
},
classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
Ci.nsIObserver])
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);

View File

@ -0,0 +1,144 @@
/* 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/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppConnection: " + Date.now() + ": " + aMsg + "\n");
}
/**
* MozInterAppConnection implementation.
*/
function InterAppConnection() {
if (DEBUG) debug("InterAppConnection()");
this.keyword = null;
this.publisher = null;
this.subscriber = null;
};
InterAppConnection.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: "MozInterAppConnection",
classID: Components.ID("{9dbfa904-0718-11e3-8e77-0721a45514b8}"),
contractID: "@mozilla.org/dom/inter-app-connection;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
__init: function(aKeyword, aPublisher, aSubscriber) {
if (DEBUG) {
debug("__init: aKeyword: " + aKeyword +
" aPublisher: " + aPublisher + " aSubscriber: " + aSubscriber);
}
this.keyword = aKeyword;
this.publisher = aPublisher;
this.subscriber = aSubscriber;
},
// Ci.nsIDOMGlobalPropertyInitializer implementation.
init: function(aWindow) {
if (DEBUG) debug("init");
this.initDOMRequestHelper(aWindow, []);
let principal = aWindow.document.nodePrincipal;
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
},
cancel: function() {
if (DEBUG) debug("cancel");
cpmm.sendAsyncMessage("InterAppConnection:Cancel",
{ keyword: this.keyword,
pubAppManifestURL: this.publisher,
subAppManifestURL: this.subscriber,
manifestURL: this._manifestURL });
}
};
/**
* MozInterAppConnectionRequest implementation.
*/
function InterAppConnectionRequest() {
if (DEBUG) debug("InterAppConnectionRequest()");
this.keyword = null;
this.port = null;
};
InterAppConnectionRequest.prototype = {
classDescription: "MozInterAppConnectionRequest",
classID: Components.ID("{6a77e9e0-0645-11e3-b90b-73bb7c78e06a}"),
contractID: "@mozilla.org/dom/inter-app-connection-request;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
__init: function(aKeyword, aPort) {
if (DEBUG) debug("__init: aKeyword: " + aKeyword + " aPort: " + aPort);
this.keyword = aKeyword;
this.port = aPort;
}
};
/**
* InterAppConnectionRequestWrapper implementation.
*
* This implements nsISystemMessagesWrapper.wrapMessage(), which provides a
* plugable way to wrap a "connection" type system message.
*
* Please see SystemMessageManager.js to know how it customizes the wrapper.
*/
function InterAppConnectionRequestWrapper() {
if (DEBUG) debug("InterAppConnectionRequestWrapper()");
}
InterAppConnectionRequestWrapper.prototype = {
// nsISystemMessagesWrapper implementation.
wrapMessage: function(aMessage, aWindow) {
if (DEBUG) debug("wrapMessage: " + JSON.stringify(aMessage));
let port = new aWindow.MozInterAppMessagePort(aMessage.messagePortID);
let connectionRequest =
new aWindow.MozInterAppConnectionRequest(aMessage.keyword, port);
return connectionRequest;
},
classDescription: "InterAppConnectionRequestWrapper",
classID: Components.ID("{d7c7a466-f91d-11e2-812a-6fab12ece58e}"),
contractID: "@mozilla.org/dom/system-messages/wrapper/connection;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesWrapper])
}
this.NSGetFactory =
XPCOMUtils.generateNSGetFactory([InterAppConnection,
InterAppConnectionRequest,
InterAppConnectionRequestWrapper]);

View File

@ -0,0 +1,245 @@
/* 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/. */
// TODO Bug 907060 Per off-line discussion, after the MessagePort is done
// at Bug 643325, we will start to refactorize the common logic of both
// Inter-App Communication and Shared Worker. For now, we hope to design an
// MozInterAppMessagePort to meet the timeline, which still follows exactly
// the same interface and semantic as the MessagePort is. In the future,
// we can then align it back to MessagePort with backward compatibility.
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppMessagePort: " + Date.now() + ": " + aMsg + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
const kMessages = ["InterAppMessagePort:OnMessage"];
function InterAppMessageEvent() {
this.type = this.data = null;
};
InterAppMessageEvent.prototype = {
classDescription: "MozInterAppMessageEvent",
classID: Components.ID("{33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}"),
contractID: "@mozilla.org/dom/inter-app-message-event;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
__init: function(aType, aDict) {
this.type = aType;
this.__DOM_IMPL__.initEvent(aType, aDict.bubbles || false,
aDict.cancelable || false);
this.data = aDict.data;
}
};
function InterAppMessagePort() {
if (DEBUG) debug("InterAppMessagePort()");
};
InterAppMessagePort.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: "MozInterAppMessagePort",
classID: Components.ID("{c66e0f8c-e3cb-11e2-9e85-43ef6244b884}"),
contractID: "@mozilla.org/dom/inter-app-message-port;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
// Ci.nsIDOMGlobalPropertyInitializer implementation.
init: function(aWindow) {
if (DEBUG) debug("Calling init().");
this.initDOMRequestHelper(aWindow, kMessages);
let principal = aWindow.document.nodePrincipal;
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
this._pageURL = principal.URI.spec;
this._started = false;
this._closed = false;
this._messageQueue = [];
},
// WebIDL implementation for constructor.
__init: function(aMessagePortID) {
if (DEBUG) {
debug("Calling __init(): aMessagePortID: " + aMessagePortID);
}
this._messagePortID = aMessagePortID;
cpmm.sendAsyncMessage("InterAppMessagePort:Register",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL,
pageURL: this._pageURL });
},
// DOMRequestIpcHelper implementation.
uninit: function() {
if (DEBUG) debug("Calling uninit().");
// When the message port is uninitialized, we need to disentangle the
// coupling ports, as if the close() method had been called.
if (this._closed) {
if (DEBUG) debug("close() has been called. Don't need to close again.");
return;
}
this.close();
},
postMessage: function(aMessage) {
if (DEBUG) debug("Calling postMessage().");
if (this._closed) {
if (DEBUG) debug("close() has been called. Cannot post message.");
return;
}
cpmm.sendAsyncMessage("InterAppMessagePort:PostMessage",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL,
message: aMessage });
},
start: function() {
// Begin dispatching messages received on the port.
if (DEBUG) debug("Calling start().");
if (this._closed) {
if (DEBUG) debug("close() has been called. Cannot call start().");
return;
}
if (this._started) {
if (DEBUG) debug("start() has been called. Don't need to start again.");
return;
}
// When a port's port message queue is enabled, the event loop must use it
// as one of its task sources.
this._started = true;
while (this._messageQueue.length) {
let message = this._messageQueue.shift();
this._dispatchMessage(message);
}
},
close: function() {
// Disconnecting the port, so that it is no longer active.
if (DEBUG) debug("Calling close().");
if (this._closed) {
if (DEBUG) debug("close() has been called. Don't need to close again.");
return;
}
this._closed = true;
this._messageQueue.length = 0;
// When this method called on a local port that is entangled with another
// port, must cause the user agent to disentangle the coupling ports.
cpmm.sendAsyncMessage("InterAppMessagePort:Unregister",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL });
},
get onmessage() {
if (DEBUG) debug("Getting onmessage handler.");
return this.__DOM_IMPL__.getEventHandler("onmessage");
},
set onmessage(aHandler) {
if (DEBUG) debug("Setting onmessage handler.");
this.__DOM_IMPL__.setEventHandler("onmessage", aHandler);
// The first time a MessagePort object's onmessage IDL attribute is set,
// the port's message queue must be enabled, as if the start() method had
// been called.
if (this._started) {
if (DEBUG) debug("start() has been called. Don't need to start again.");
return;
}
this.start();
},
_dispatchMessage: function _dispatchMessage(aMessage) {
let wrappedMessage = ObjectWrapper.wrap(aMessage, this._window);
if (DEBUG) {
debug("_dispatchMessage: wrappedMessage: " +
JSON.stringify(wrappedMessage));
}
let event = new this._window
.MozInterAppMessageEvent("message",
{ data: wrappedMessage });
this.__DOM_IMPL__.dispatchEvent(event);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
if (message.manifestURL != this._manifestURL ||
message.pageURL != this._pageURL ||
message.messagePortID != this._messagePortID) {
if (DEBUG) debug("The message doesn't belong to this page. Returning.");
return;
}
switch (aMessage.name) {
case "InterAppMessagePort:OnMessage":
if (this._closed) {
if (DEBUG) debug("close() has been called. Drop the message.");
return;
}
if (!this._started) {
if (DEBUG) debug("Not yet called start(). Queue up the message.");
this._messageQueue.push(message.message);
return;
}
this._dispatchMessage(message.message);
break;
default:
if (DEBUG) debug("Error! Shouldn't fall into this case.");
break;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppMessagePort,
InterAppMessageEvent]);

10
dom/apps/src/Makefile.in Normal file
View File

@ -0,0 +1,10 @@
# 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/.
LOCAL_INCLUDES += \
-I$(topsrcdir)/js/xpconnect/wrappers \
$(NULL)
include $(topsrcdir)/dom/dom-config.mk
include $(topsrcdir)/config/rules.mk

View File

@ -309,6 +309,8 @@ WebappsApplication.prototype = {
init: function(aWindow, aApp) {
this._window = aWindow;
let principal = this._window.document.nodePrincipal;
this._appStatus = principal.appStatus;
this.origin = aApp.origin;
this._manifest = aApp.manifest;
this._updateManifest = aApp.updateManifest;
@ -342,7 +344,10 @@ WebappsApplication.prototype = {
"Webapps:Launch:Return:OK",
"Webapps:Launch:Return:KO",
"Webapps:PackageEvent",
"Webapps:ClearBrowserData:Return"]);
"Webapps:ClearBrowserData:Return",
"Webapps:Connect:Return:OK",
"Webapps:Connect:Return:KO",
"Webapps:GetConnections:Return:OK"]);
cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
["Webapps:OfflineCache",
@ -460,6 +465,33 @@ WebappsApplication.prototype = {
return request;
},
connect: function(aKeyword, aRules) {
return this.createPromise(function (aResolve, aReject) {
cpmm.sendAsyncMessage("Webapps:Connect",
{ keyword: aKeyword,
rules: aRules,
manifestURL: this.manifestURL,
outerWindowID: this._id,
appStatus: this._appStatus,
requestID: this.getPromiseResolverId({
resolve: aResolve,
reject: aReject
})});
}.bind(this));
},
getConnections: function() {
return this.createPromise(function (aResolve, aReject) {
cpmm.sendAsyncMessage("Webapps:GetConnections",
{ manifestURL: this.manifestURL,
outerWindowID: this._id,
requestID: this.getPromiseResolverId({
resolve: aResolve,
reject: aReject
})});
}.bind(this));
},
uninit: function() {
this._onprogress = null;
cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
@ -479,7 +511,14 @@ WebappsApplication.prototype = {
receiveMessage: function(aMessage) {
let msg = aMessage.json;
let req = this.takeRequest(msg.requestID);
let req;
if (aMessage.name == "Webapps:Connect:Return:OK" ||
aMessage.name == "Webapps:Connect:Return:KO" ||
aMessage.name == "Webapps:GetConnections:Return:OK") {
req = this.takePromiseResolver(msg.requestID);
} else {
req = this.takeRequest(msg.requestID);
}
// ondownload* callbacks should be triggered on all app instances
if ((msg.oid != this._id || !req) &&
@ -603,6 +642,28 @@ WebappsApplication.prototype = {
case "Webapps:ClearBrowserData:Return":
Services.DOMRequest.fireSuccess(req, null);
break;
case "Webapps:Connect:Return:OK":
let messagePorts = [];
msg.messagePortIDs.forEach(function(aPortID) {
let port = new this._window.MozInterAppMessagePort(aPortID);
messagePorts.push(port);
}, this);
req.resolve(messagePorts);
break;
case "Webapps:Connect:Return:KO":
req.reject("No connections registered");
break;
case "Webapps:GetConnections:Return:OK":
let connections = [];
msg.connections.forEach(function(aConnection) {
let connection =
new this._window.MozInterAppConnection(aConnection.keyword,
aConnection.pubAppManifestURL,
aConnection.subAppManifestURL);
connections.push(connection);
}, this);
req.resolve(connections);
break;
}
},

View File

@ -60,6 +60,11 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyGetter(this, "interAppCommService", function() {
return Cc["@mozilla.org/inter-app-communication-service;1"]
.getService(Ci.nsIInterAppCommService);
});
XPCOMUtils.defineLazyGetter(this, "msgmgr", function() {
return Cc["@mozilla.org/system-message-internal;1"]
.getService(Ci.nsISystemMessagesInternal);
@ -530,6 +535,8 @@ this.DOMApplicationRegistry = {
// |aEntryPoint| is either the entry_point name or the null in which case we
// use the root of the manifest.
//
// TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...).
_registerSystemMessagesForEntryPoint: function(aManifest, aApp, aEntryPoint) {
let root = aManifest;
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
@ -571,6 +578,69 @@ this.DOMApplicationRegistry = {
});
},
// |aEntryPoint| is either the entry_point name or the null in which case we
// use the root of the manifest.
//
// TODO Bug 908094 Refine _registerInterAppConnectionsForEntryPoint(...).
_registerInterAppConnectionsForEntryPoint: function(aManifest, aApp,
aEntryPoint) {
let root = aManifest;
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
root = aManifest.entry_points[aEntryPoint];
}
let connections = root.connections;
if (!connections) {
return;
}
if ((typeof connections) !== "object") {
debug("|connections| is not an object. Skipping: " + connections);
return;
}
let manifest = new ManifestHelper(aManifest, aApp.origin);
let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint),
null, null);
let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
for (let keyword in connections) {
let connection = connections[keyword];
// Resolve the handler path from origin. If |handler_path| is absent,
// use |launch_path| as default.
let fullHandlerPath;
let handlerPath = connection.handler_path;
if (handlerPath) {
try {
fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
} catch(e) {
debug("Connection's handler path is invalid. Skipping: keyword: " +
keyword + " handler_path: " + handlerPath);
continue;
}
}
let handlerPageURI = fullHandlerPath
? Services.io.newURI(fullHandlerPath, null, null)
: launchPathURI;
if (SystemMessagePermissionsChecker
.isSystemMessagePermittedToRegister("connection",
aApp.origin,
aManifest)) {
msgmgr.registerPage("connection", handlerPageURI, manifestURI);
}
interAppCommService.
registerConnection(keyword,
handlerPageURI,
manifestURI,
connection.description,
AppsUtils.getAppManifestStatus(manifest),
connection.rules);
}
},
_registerSystemMessages: function(aManifest, aApp) {
this._registerSystemMessagesForEntryPoint(aManifest, aApp, null);
@ -583,6 +653,19 @@ this.DOMApplicationRegistry = {
}
},
_registerInterAppConnections: function(aManifest, aApp) {
this._registerInterAppConnectionsForEntryPoint(aManifest, aApp, null);
if (!aManifest.entry_points) {
return;
}
for (let entryPoint in aManifest.entry_points) {
this._registerInterAppConnectionsForEntryPoint(aManifest, aApp,
entryPoint);
}
},
// |aEntryPoint| is either the entry_point name or the null in which case we
// use the root of the manifest.
_createActivitiesToRegister: function(aManifest, aApp, aEntryPoint, aRunUpdate) {
@ -745,6 +828,7 @@ this.DOMApplicationRegistry = {
app.redirects = this.sanitizeRedirects(manifest.redirects);
}
this._registerSystemMessages(manifest, app);
this._registerInterAppConnections(manifest, app);
appsToRegister.push({ manifest: manifest, app: app });
}, this);
this._registerActivitiesForApps(appsToRegister, aRunUpdate);
@ -1396,6 +1480,7 @@ this.DOMApplicationRegistry = {
}
this._registerSystemMessages(aNewManifest, aApp);
this._registerActivities(aNewManifest, aApp, true);
this._registerInterAppConnections(aNewManifest, aApp);
} else {
// Nothing else to do but notifying we're ready.
this.notifyAppsRegistryReady();

View File

@ -4,9 +4,21 @@
# 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/.
EXPORTS.mozilla.dom += [
'InterAppComm.h',
]
CPP_SOURCES += [
'InterAppComm.cpp',
]
EXTRA_COMPONENTS += [
'AppsService.js',
'AppsService.manifest',
'InterAppComm.manifest',
'InterAppCommService.js',
'InterAppConnection.js',
'InterAppMessagePort.js',
'Webapps.js',
'Webapps.manifest',
]
@ -25,3 +37,8 @@ EXTRA_PP_JS_MODULES += [
'Webapps.jsm',
]
FAIL_ON_WARNINGS = True
LIBXUL_LIBRARY = True
LIBRARY_NAME = 'dom_apps_s'

View File

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "CompositionStringSynthesizer.h"
#include "nsContentUtils.h"
#include "nsIDocShell.h"
#include "nsIFrame.h"
#include "nsIPresShell.h"
#include "nsIWidget.h"
#include "nsPIDOMWindow.h"
#include "nsView.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS1(CompositionStringSynthesizer,
nsICompositionStringSynthesizer)
CompositionStringSynthesizer::CompositionStringSynthesizer(
nsPIDOMWindow* aWindow)
{
mWindow = do_GetWeakReference(aWindow);
ClearInternal();
}
CompositionStringSynthesizer::~CompositionStringSynthesizer()
{
}
void
CompositionStringSynthesizer::ClearInternal()
{
mString.Truncate();
mClauses.Clear();
mCaret.mRangeType = 0;
}
nsIWidget*
CompositionStringSynthesizer::GetWidget()
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
if (!window) {
return nullptr;
}
nsIDocShell *docShell = window->GetDocShell();
if (!docShell) {
return nullptr;
}
nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
if (!presShell) {
return nullptr;
}
nsIFrame* frame = presShell->GetRootFrame();
if (!frame) {
return nullptr;
}
return frame->GetView()->GetNearestWidget(nullptr);
}
NS_IMETHODIMP
CompositionStringSynthesizer::SetString(const nsAString& aString)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
mString = aString;
return NS_OK;
}
NS_IMETHODIMP
CompositionStringSynthesizer::AppendClause(uint32_t aLength,
uint32_t aAttribute)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
switch (aAttribute) {
case ATTR_RAWINPUT:
case ATTR_SELECTEDRAWTEXT:
case ATTR_CONVERTEDTEXT:
case ATTR_SELECTEDCONVERTEDTEXT: {
nsTextRange textRange;
textRange.mStartOffset =
mClauses.IsEmpty() ? 0 : mClauses[mClauses.Length() - 1].mEndOffset;
textRange.mEndOffset = textRange.mStartOffset + aLength;
textRange.mRangeType = aAttribute;
mClauses.AppendElement(textRange);
return NS_OK;
}
default:
return NS_ERROR_INVALID_ARG;
}
}
NS_IMETHODIMP
CompositionStringSynthesizer::SetCaret(uint32_t aOffset, uint32_t aLength)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
mCaret.mStartOffset = aOffset;
mCaret.mEndOffset = mCaret.mStartOffset + aLength;
mCaret.mRangeType = NS_TEXTRANGE_CARETPOSITION;
return NS_OK;
}
NS_IMETHODIMP
CompositionStringSynthesizer::DispatchEvent(bool* aDefaultPrevented)
{
NS_ENSURE_ARG_POINTER(aDefaultPrevented);
nsCOMPtr<nsIWidget> widget = GetWidget();
NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
if (!nsContentUtils::IsCallerChrome()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (!mClauses.IsEmpty() &&
mClauses[mClauses.Length()-1].mEndOffset != mString.Length()) {
NS_WARNING("Sum of length of the all clauses must be same as the string "
"length");
ClearInternal();
return NS_ERROR_ILLEGAL_VALUE;
}
if (mCaret.mRangeType == NS_TEXTRANGE_CARETPOSITION) {
if (mCaret.mEndOffset > mString.Length()) {
NS_WARNING("Caret position is out of the composition string");
ClearInternal();
return NS_ERROR_ILLEGAL_VALUE;
}
mClauses.AppendElement(mCaret);
}
nsTextEvent textEvent(true, NS_TEXT_TEXT, widget);
textEvent.time = PR_IntervalNow();
textEvent.theText = mString;
textEvent.rangeCount = mClauses.Length();
textEvent.rangeArray = mClauses.Elements();
// XXX How should we set false for this on b2g?
textEvent.mFlags.mIsSynthesizedForTests = true;
nsEventStatus status = nsEventStatus_eIgnore;
nsresult rv = widget->DispatchEvent(&textEvent, status);
*aDefaultPrevented = (status == nsEventStatus_eConsumeNoDefault);
ClearInternal();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,45 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#ifndef mozilla_dom_compositionstringsynthesizer_h__
#define mozilla_dom_compositionstringsynthesizer_h__
#include "nsICompositionStringSynthesizer.h"
#include "nsGUIEvent.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWeakReference.h"
#include "mozilla/Attributes.h"
class nsIWidget;
class nsPIDOMWindow;
namespace mozilla {
namespace dom {
class CompositionStringSynthesizer MOZ_FINAL :
public nsICompositionStringSynthesizer
{
public:
CompositionStringSynthesizer(nsPIDOMWindow* aWindow);
~CompositionStringSynthesizer();
NS_DECL_ISUPPORTS
NS_DECL_NSICOMPOSITIONSTRINGSYNTHESIZER
private:
nsWeakPtr mWindow; // refers an instance of nsPIDOMWindow
nsString mString;
nsAutoTArray<nsTextRange, 10> mClauses;
nsTextRange mCaret;
nsIWidget* GetWidget();
void ClearInternal();
};
} // namespace dom
} // namespace mozilla
#endif // #ifndef mozilla_dom_compositionstringsynthesizer_h__

View File

@ -65,6 +65,7 @@ EXPORTS.mozilla.dom += [
CPP_SOURCES += [
'BarProps.cpp',
'CompositionStringSynthesizer.cpp',
'Crypto.cpp',
'DOMCursor.cpp',
'DOMError.cpp',

View File

@ -11,6 +11,7 @@
#include "nsIDOMEvent.h"
#include "nsDOMWindowUtils.h"
#include "nsQueryContentEventResult.h"
#include "CompositionStringSynthesizer.h"
#include "nsGlobalWindow.h"
#include "nsIDocument.h"
#include "nsFocusManager.h"
@ -1816,82 +1817,21 @@ nsDOMWindowUtils::SendCompositionEvent(const nsAString& aType,
return NS_OK;
}
static void
AppendClause(int32_t aClauseLength, uint32_t aClauseAttr,
nsTArray<nsTextRange>* aRanges)
{
NS_PRECONDITION(aRanges, "aRange is null");
if (aClauseLength == 0) {
return;
}
nsTextRange range;
range.mStartOffset = aRanges->Length() == 0 ? 0 :
aRanges->ElementAt(aRanges->Length() - 1).mEndOffset + 1;
range.mEndOffset = range.mStartOffset + aClauseLength;
NS_ASSERTION(range.mStartOffset <= range.mEndOffset, "range is invalid");
NS_PRECONDITION(aClauseAttr == NS_TEXTRANGE_RAWINPUT ||
aClauseAttr == NS_TEXTRANGE_SELECTEDRAWTEXT ||
aClauseAttr == NS_TEXTRANGE_CONVERTEDTEXT ||
aClauseAttr == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT,
"aClauseAttr is invalid value");
range.mRangeType = aClauseAttr;
aRanges->AppendElement(range);
}
NS_IMETHODIMP
nsDOMWindowUtils::SendTextEvent(const nsAString& aCompositionString,
int32_t aFirstClauseLength,
uint32_t aFirstClauseAttr,
int32_t aSecondClauseLength,
uint32_t aSecondClauseAttr,
int32_t aThirdClauseLength,
uint32_t aThirdClauseAttr,
int32_t aCaretStart,
int32_t aCaretLength)
nsDOMWindowUtils::CreateCompositionStringSynthesizer(
nsICompositionStringSynthesizer** aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
*aResult = nullptr;
if (!nsContentUtils::IsCallerChrome()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
nsTextEvent textEvent(true, NS_TEXT_TEXT, widget);
InitEvent(textEvent);
nsAutoTArray<nsTextRange, 4> textRanges;
NS_ENSURE_TRUE(aFirstClauseLength >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aSecondClauseLength >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aThirdClauseLength >= 0, NS_ERROR_INVALID_ARG);
AppendClause(aFirstClauseLength, aFirstClauseAttr, &textRanges);
AppendClause(aSecondClauseLength, aSecondClauseAttr, &textRanges);
AppendClause(aThirdClauseLength, aThirdClauseAttr, &textRanges);
int32_t len = aFirstClauseLength + aSecondClauseLength + aThirdClauseLength;
NS_ENSURE_TRUE(len == 0 || uint32_t(len) == aCompositionString.Length(),
NS_ERROR_FAILURE);
if (aCaretStart >= 0) {
nsTextRange range;
range.mStartOffset = aCaretStart;
range.mEndOffset = range.mStartOffset + aCaretLength;
range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
textRanges.AppendElement(range);
}
textEvent.theText = aCompositionString;
textEvent.rangeCount = textRanges.Length();
textEvent.rangeArray = textRanges.Elements();
textEvent.mFlags.mIsSynthesizedForTests = true;
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&textEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
NS_ADDREF(*aResult = new CompositionStringSynthesizer(window));
return NS_OK;
}

View File

@ -254,7 +254,7 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
NS_ENSURE_SUCCESS(rv, rv);
if (reportViolation) {
// TODO : FIX DATA in violation report.
// TODO : need actual script sample in violation report.
NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP");
// Get the calling location.
@ -268,9 +268,9 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
NS_ConvertUTF8toUTF16(aFileName),
scriptSample,
lineNum);
NS_ConvertUTF8toUTF16(aFileName),
scriptSample,
lineNum);
}
if (!allowsEval) {

View File

@ -5,9 +5,6 @@
const { 'classes': Cc, 'interfaces': Ci } = Components;
// Instantiate nsIDOMScriptObjectFactory so that DOMException is usable in xpcshell
Components.classesByID["{9eb760f0-4380-11d2-b328-00805f8a3859}"].getService(Ci.nsISupports);
function assert_equals(a, b, msg) {
dump("assert_equals(" + a + ", " + b + ", \"" + msg + "\")");
do_check_eq(a, b, Components.stack.caller);

View File

@ -8,7 +8,6 @@
#include "nsIConsoleService.h"
#include "nsIDiskSpaceWatcher.h"
#include "nsIDOMScriptObjectFactory.h"
#include "nsIFile.h"
#include "nsIFileStorage.h"
#include "nsIObserverService.h"
@ -40,8 +39,6 @@ USING_INDEXEDDB_NAMESPACE
using namespace mozilla::dom;
USING_QUOTA_NAMESPACE
static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
BEGIN_INDEXEDDB_NAMESPACE
class FileManagerInfo
@ -606,11 +603,6 @@ IndexedDatabaseManager::InitWindowless(const jsval& aObj, JSContext* aCx)
return NS_ERROR_FAILURE;
}
// Instantiating this class will register exception providers so even
// in xpcshell we will get typed (dom) exceptions, instead of general
// exceptions.
nsCOMPtr<nsIDOMScriptObjectFactory> sof(do_GetService(kDOMSOF_CID));
JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, obj));
NS_ASSERTION(global, "What?! No global!");

View File

@ -11,6 +11,7 @@ XPIDL_SOURCES += [
'nsIDOMApplicationRegistry.idl',
'nsIDOMApplicationRegistry2.idl',
'nsIDOMMozApplicationEvent.idl',
'nsIInterAppCommService.idl',
]
XPIDL_MODULE = 'dom_apps'

View File

@ -7,7 +7,7 @@
interface nsIDOMDOMRequest;
[scriptable, uuid(8bdeef38-e9cd-46f8-b8de-ed9e6b4d01ea)]
[scriptable, uuid(4081390c-08cf-11e3-9200-b3c0a8744b20)]
interface mozIDOMApplication : nsISupports
{
readonly attribute jsval manifest;
@ -90,6 +90,16 @@ interface mozIDOMApplication : nsISupports
* onsuccess will be called once data is actually cleared.
*/
nsIDOMDOMRequest clearBrowserData();
/**
* Inter-App Communication APIs.
*
* https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
*/
nsISupports connect(in DOMString keyword,
[optional] in jsval rules); // nsISupports is a Promise.
nsISupports getConnections(); // nsISupports is a Promise.
};
[scriptable, uuid(cf742022-5ba3-11e2-868f-03310341b006)]

View File

@ -0,0 +1,40 @@
/* 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/. */
#include "domstubs.idl"
interface nsIURI;
/**
* Implemented by the contract id @mozilla.org/inter-app-communication-service;1
*
* This interface contains helpers for Inter-App Communication API [1] related
* purposes. A singleton service of this interface will be instantiated during
* the system boot-up, which plays the role of the central service receiving
* messages from and interacting with the content processes.
*
* [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
*/
[scriptable, uuid(7fdd8b68-0b0a-11e3-9b4c-afbc236da250)]
interface nsIInterAppCommService : nsISupports
{
/*
* Registration of a page that wants to be connected to other apps through
* the Inter-App Communication API.
*
* @param keyword The connection's keyword.
* @param handlerPageURI The URI of the handler's page.
* @param manifestURI The webapp's manifest URI.
* @param description The connection's description.
* @param appStatus The app status can be Ci.nsIPrincipal.APP_STATUS_[
* NOT_INSTALLED, INSTALLED, PRIVILEGED, CERTIFIED].
* @param rules The connection's rules.
*/
void registerConnection(in DOMString keyword,
in nsIURI handlerPageURI,
in nsIURI manifestURI,
in DOMString description,
in unsigned short appStatus,
in jsval rules);
};

View File

@ -7,6 +7,7 @@
XPIDL_SOURCES += [
'domstubs.idl',
'nsIBrowserDOMWindow.idl',
'nsICompositionStringSynthesizer.idl',
'nsIContentPermissionPrompt.idl',
'nsIContentPrefService.idl',
'nsIContentPrefService2.idl',

View File

@ -0,0 +1,53 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "nsISupports.idl"
/**
* Stores composition clauses information and caret information for synthesizing
* composition string.
*/
[scriptable, uuid(9a7d7851-8c0a-4061-9edc-60d6693f86c9)]
interface nsICompositionStringSynthesizer : nsISupports
{
/**
* Set composition string or committed string.
*/
void setString(in AString aString);
// NOTE: These values must be same to NS_TEXTRANGE_* in nsGUIEvent.h
const unsigned long ATTR_RAWINPUT = 0x02;
const unsigned long ATTR_SELECTEDRAWTEXT = 0x03;
const unsigned long ATTR_CONVERTEDTEXT = 0x04;
const unsigned long ATTR_SELECTEDCONVERTEDTEXT = 0x05;
/**
* Append a clause.
*
* TODO: Should be able to specify custom clause style.
*/
void appendClause(in unsigned long aLength,
in unsigned long aAttribute);
/**
* Set caret information.
*/
void setCaret(in unsigned long aOffset,
in unsigned long aLength);
/**
* Synthesize composition string with given information by dispatching
* a proper event.
*
* If clauses have never been set, this dispatches a commit event.
* If clauses are not filled all over the composition string, this throw an
* error.
*
* After dispatching event, this clears all the information about the
* composition string. So, you can reuse this instance.
*/
bool dispatchEvent();
};

View File

@ -41,8 +41,9 @@ interface nsIDOMClientRect;
interface nsIURI;
interface nsIDOMEventTarget;
interface nsIRunnable;
interface nsICompositionStringSynthesizer;
[scriptable, uuid(d18a8d69-7609-4165-ae20-af8aead36833)]
[scriptable, uuid(dd45c6ae-9d80-46ef-86d7-f2795a48a77b)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -787,44 +788,12 @@ interface nsIDOMWindowUtils : nsISupports {
in AString aLocale);
/**
* Synthesize a text event to the window.
* Creating synthesizer of composition string on the window.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without chrome privileges.
*
* Currently, this method doesn't support 4 or more clauses composition
* string.
*
* @param aCompositionString composition string
* @param a*ClauseLengh the length of nth clause, set 0 when you
* don't need second or third clause.
* @param a*ClauseAttr the attribute of nth clause, uese following
* const values.
* @param aCaretStart the caret position in the composition string,
* if you set negative value, this method don't
* set the caret position to the event.
* @param aCaretLength the caret length, if this is one or more,
* the caret will be wide caret, otherwise,
* it's collapsed.
* XXX nsEditor doesn't support wide caret yet.
*/
// NOTE: These values must be same to NS_TEXTRANGE_* in nsGUIEvent.h
const unsigned long COMPOSITION_ATTR_RAWINPUT = 0x02;
const unsigned long COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
const unsigned long COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
const unsigned long COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
void sendTextEvent(in AString aCompositionString,
in long aFirstClauseLength,
in unsigned long aFirstClauseAttr,
in long aSecondClauseLength,
in unsigned long aSecondClauseAttr,
in long aThirdClauseLength,
in unsigned long aThirdClauseAttr,
in long aCaretStart,
in long aCaretLength);
nsICompositionStringSynthesizer createCompositionStringSynthesizer();
/**
* Synthesize a query content event. Note that the result value returned here

View File

@ -55,6 +55,7 @@ this.SystemMessagePermissionsTable = {
"bluetooth-opp-transfer-start": {
"bluetooth": []
},
"connection": { },
"headset-button": { },
"icc-stkcommand": {
"settings": ["read", "write"]

View File

@ -369,11 +369,14 @@ nsGeolocationRequest::Notify(nsITimer* aTimer)
{
MOZ_ASSERT(!mShutdown, "timeout after shutdown");
NotifyError(nsIDOMGeoPositionError::TIMEOUT);
if (!mIsWatchPositionRequest) {
Shutdown();
mLocator->RemoveRequest(this);
} else if (!mShutdown) {
}
NotifyError(nsIDOMGeoPositionError::TIMEOUT);
if (!mShutdown) {
SetTimeoutTimer();
}
@ -534,6 +537,11 @@ nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
}
mLocator->SetCachedPosition(wrapped);
if (!mIsWatchPositionRequest) {
// Cancel timer and position updates in case the position
// callback spins the event loop
Shutdown();
}
// Ensure that the proper context is on the stack (bug 452762)
nsCxPusher pusher;
@ -552,9 +560,10 @@ nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
callback->HandleEvent(aPosition);
}
if (!mIsWatchPositionRequest) {
Shutdown();
} else if (!mShutdown) { // The handler may have called clearWatch
if (!mShutdown) {
// For watch requests, the handler may have called clearWatch
MOZ_ASSERT(mIsWatchPositionRequest,
"non-shutdown getCurrentPosition request after callback!");
SetTimeoutTimer();
}
}

View File

@ -19,6 +19,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Sntp.jsm");
var RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
@ -58,8 +59,10 @@ const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kSysMsgListenerReadyObserverTopic = "system-message-listener-ready";
const kSysClockChangeObserverTopic = "system-clock-change";
const kScreenStateChangedTopic = "screen-state-changed";
const kTimeNitzAutomaticUpdateEnabled = "time.nitz.automatic-update.enabled";
const kTimeNitzAvailable = "time.nitz.available";
const kClockAutoUpdateEnabled = "time.clock.automatic-update.enabled";
const kClockAutoUpdateAvailable = "time.clock.automatic-update.available";
const kTimezoneAutoUpdateEnabled = "time.timezone.automatic-update.enabled";
const kTimezoneAutoUpdateAvailable = "time.timezone.automatic-update.available";
const kCellBroadcastSearchList = "ril.cellbroadcast.searchlist";
const kCellBroadcastDisabled = "ril.cellbroadcast.disabled";
const kPrefenceChangedObserverTopic = "nsPref:changed";
@ -709,12 +712,19 @@ function RadioInterface(options) {
lock.get("ril.data.enabled", this);
lock.get("ril.data.apnSettings", this);
// Read the 'time.nitz.automatic-update.enabled' setting to see if
// we need to adjust the system clock time and time zone by NITZ.
lock.get(kTimeNitzAutomaticUpdateEnabled, this);
// Read the 'time.clock.automatic-update.enabled' setting to see if
// we need to adjust the system clock time by NITZ or SNTP.
lock.get(kClockAutoUpdateEnabled, this);
// Set "time.nitz.available" to false when starting up.
this.setNitzAvailable(false);
// Read the 'time.timezone.automatic-update.enabled' setting to see if
// we need to adjust the system timezone by NITZ.
lock.get(kTimezoneAutoUpdateEnabled, this);
// Set "time.clock.automatic-update.available" to false when starting up.
this.setClockAutoUpdateAvailable(false);
// Set "time.timezone.automatic-update.available" to false when starting up.
this.setTimezoneAutoUpdateAvailable(false);
// Read the Cell Broadcast Search List setting, string of integers or integer
// ranges separated by comma, to set listening channels.
@ -726,11 +736,20 @@ function RadioInterface(options) {
Services.obs.addObserver(this, kSysClockChangeObserverTopic, false);
Services.obs.addObserver(this, kScreenStateChangedTopic, false);
Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
Services.prefs.addObserver(kCellBroadcastDisabled, this, false);
this.portAddressedSmsApps = {};
this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
this._sntp = new Sntp(this.setClockBySntp.bind(this),
Services.prefs.getIntPref('network.sntp.maxRetryCount'),
Services.prefs.getIntPref('network.sntp.refreshPeriod'),
Services.prefs.getIntPref('network.sntp.timeout'),
Services.prefs.getCharPref('network.sntp.pools').split(';'),
Services.prefs.getIntPref('network.sntp.port'));
}
RadioInterface.prototype = {
classID: RADIOINTERFACE_CID,
@ -1860,22 +1879,35 @@ RadioInterface.prototype = {
},
/**
* Set the setting value of "time.nitz.available".
* Set the setting value of "time.clock.automatic-update.available".
*/
setNitzAvailable: function setNitzAvailable(value) {
gSettingsService.createLock().set(kTimeNitzAvailable, value, null,
setClockAutoUpdateAvailable: function setClockAutoUpdateAvailable(value) {
gSettingsService.createLock().set(kClockAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
* Set the NITZ message in our system time.
* Set the setting value of "time.timezone.automatic-update.available".
*/
setNitzTime: function setNitzTime(message) {
setTimezoneAutoUpdateAvailable: function setTimezoneAutoUpdateAvailable(value) {
gSettingsService.createLock().set(kTimezoneAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
* Set the system clock by NITZ.
*/
setClockByNitz: function setClockByNitz(message) {
// To set the system clock time. Note that there could be a time diff
// between when the NITZ was received and when the time is actually set.
gTimeService.set(
message.networkTimeInMS + (Date.now() - message.receiveTimeInMS));
},
/**
* Set the system time zone by NITZ.
*/
setTimezoneByNitz: function setTimezoneByNitz(message) {
// To set the sytem timezone. Note that we need to convert the time zone
// value to a UTC repesentation string in the format of "UTC(+/-)hh:mm".
// Ex, time zone -480 is "UTC-08:00"; time zone 630 is "UTC+10:30".
@ -1898,15 +1930,36 @@ RadioInterface.prototype = {
*/
handleNitzTime: function handleNitzTime(message) {
// Got the NITZ info received from the ril_worker.
this.setNitzAvailable(true);
this.setClockAutoUpdateAvailable(true);
this.setTimezoneAutoUpdateAvailable(true);
// Cache the latest NITZ message whenever receiving it.
this._lastNitzMessage = message;
// Set the received NITZ time if the setting is enabled.
if (this._nitzAutomaticUpdateEnabled) {
this.setNitzTime(message);
// Set the received NITZ clock if the setting is enabled.
if (this._clockAutoUpdateEnabled) {
this.setClockByNitz(message);
}
// Set the received NITZ timezone if the setting is enabled.
if (this._timezoneAutoUpdateEnabled) {
this.setTimezoneByNitz(message);
}
},
/**
* Set the system clock by SNTP.
*/
setClockBySntp: function setClockBySntp(offset) {
// Got the SNTP info.
this.setClockAutoUpdateAvailable(true);
if (!this._clockAutoUpdateEnabled) {
return;
}
if (this._lastNitzMessage) {
debug("SNTP: NITZ available, discard SNTP");
return;
}
gTimeService.set(Date.now() + offset);
},
handleIccMbdn: function handleIccMbdn(message) {
@ -2023,11 +2076,24 @@ RadioInterface.prototype = {
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
Services.obs.removeObserver(this, kSysClockChangeObserverTopic);
Services.obs.removeObserver(this, kScreenStateChangedTopic);
Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic);
Services.prefs.removeObserver(kCellBroadcastDisabled, this);
break;
case kSysClockChangeObserverTopic:
let offset = parseInt(data, 10);
if (this._lastNitzMessage) {
this._lastNitzMessage.receiveTimeInMS += parseInt(data, 10);
this._lastNitzMessage.receiveTimeInMS += offset;
}
this._sntp.updateOffset(offset);
break;
case kNetworkInterfaceStateChangedTopic:
let network = subject.QueryInterface(Ci.nsINetworkInterface);
if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
// Check SNTP when we have data connection, this may not take
// effect immediately before the setting get enabled.
if (this._sntp.isExpired()) {
this._sntp.request();
}
}
break;
case kScreenStateChangedTopic:
@ -2053,28 +2119,51 @@ RadioInterface.prototype = {
apnSettings: null,
// Flag to determine whether to use NITZ. It corresponds to the
// 'time.nitz.automatic-update.enabled' setting from the UI.
_nitzAutomaticUpdateEnabled: null,
// Flag to determine whether to update system clock automatically. It
// corresponds to the 'time.clock.automatic-update.enabled' setting.
_clockAutoUpdateEnabled: null,
// Flag to determine whether to update system timezone automatically. It
// corresponds to the 'time.clock.automatic-update.enabled' setting.
_timezoneAutoUpdateEnabled: null,
// Remember the last NITZ message so that we can set the time based on
// the network immediately when users enable network-based time.
_lastNitzMessage: null,
// Object that handles SNTP.
_sntp: null,
// Cell Broadcast settings values.
_cellBroadcastSearchListStr: null,
handleSettingsChange: function handleSettingsChange(aName, aResult, aMessage) {
// Don't allow any content processes to modify the setting
// "time.nitz.available" except for the chrome process.
let isNitzAvailable = (this._lastNitzMessage !== null);
if (aName === kTimeNitzAvailable && aMessage !== "fromInternalSetting" &&
aResult !== isNitzAvailable) {
if (DEBUG) {
this.debug("Content processes cannot modify 'time.nitz.available'. Restore!");
// "time.clock.automatic-update.available" except for the chrome process.
if (aName === kClockAutoUpdateAvailable &&
aMessage !== "fromInternalSetting") {
let isClockAutoUpdateAvailable = this._lastNitzMessage !== null ||
this._sntp.isAvailable();
if (aResult !== isClockAutoUpdateAvailable) {
debug("Content processes cannot modify 'time.clock.automatic-update.available'. Restore!");
// Restore the setting to the current value.
this.setClockAutoUpdateAvailable(isClockAutoUpdateAvailable);
}
}
// Don't allow any content processes to modify the setting
// "time.timezone.automatic-update.available" except for the chrome
// process.
if (aName === kTimezoneAutoUpdateAvailable &&
aMessage !== "fromInternalSetting") {
let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null;
if (aResult !== isTimezoneAutoUpdateAvailable) {
if (DEBUG) {
this.debug("Content processes cannot modify 'time.timezone.automatic-update.available'. Restore!");
}
// Restore the setting to the current value.
this.setTimezoneAutoUpdateAvailable(isTimezoneAutoUpdateAvailable);
}
// Restore the setting to the current value.
this.setNitzAvailable(isNitzAvailable);
}
this.handle(aName, aResult);
@ -2117,12 +2206,34 @@ RadioInterface.prototype = {
this.updateRILNetworkInterface();
}
break;
case kTimeNitzAutomaticUpdateEnabled:
this._nitzAutomaticUpdateEnabled = aResult;
case kClockAutoUpdateEnabled:
this._clockAutoUpdateEnabled = aResult;
if (!this._clockAutoUpdateEnabled) {
break;
}
// Set the latest cached NITZ time if the setting is enabled.
if (this._nitzAutomaticUpdateEnabled && this._lastNitzMessage) {
this.setNitzTime(this._lastNitzMessage);
// Set the latest cached NITZ time if it's available.
if (this._lastNitzMessage) {
this.setClockByNitz(this._lastNitzMessage);
} else if (gNetworkManager.active && gNetworkManager.active.state ==
Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
// Set the latest cached SNTP time if it's available.
if (!this._sntp.isExpired()) {
this.setClockBySntp(this._sntp.getOffset());
} else {
// Or refresh the SNTP.
this._sntp.request();
}
}
break;
case kTimezoneAutoUpdateEnabled:
this._timezoneAutoUpdateEnabled = aResult;
if (this._timezoneAutoUpdateEnabled) {
// Apply the latest cached NITZ for timezone if it's available.
if (this._timezoneAutoUpdateEnabled && this._lastNitzMessage) {
this.setTimezoneByNitz(this._lastNitzMessage);
}
}
break;
case kCellBroadcastSearchList:

View File

@ -11,6 +11,7 @@ MOCHITEST_FILES = \
test_clearWatch.html \
test_clearWatch_invalid.html \
test_geolocation_is_undefined_when_pref_is_off.html \
test_handlerSpinsEventLoop.html \
test_manyCurrentConcurrent.html \
test_manyCurrentSerial.html \
test_manyWatchConcurrent.html \

View File

@ -0,0 +1,66 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=911595
-->
<head>
<title>Test for spinning the event loop inside position handlers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="geolocation_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=911595 ">Mozilla Bug 911595</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/*
* In bug 911595 , spinning the event loop from inside position
* handlers could cause both success and error callbacks to be
* fired for the same request if that request has a small timeout.
*/
SimpleTest.waitForExplicitFinish();
resume_geolocationProvider(function() {
force_prompt(true, test1);
});
function spinEventLoopAndSetTimeout() {
if (successCallbackCalled || errorCallbackCalled) {
// this should only be called once from either callback
return;
}
window.showModalDialog("javascript:window.close()");
setTimeout(function() {
ok(successCallbackCalled != errorCallbackCalled, "Ensure only one callback is called");
SimpleTest.finish();
}, 5);
}
var successCallbackCalled = false;
function successCallback(position) {
spinEventLoopAndSetTimeout();
successCallbackCalled = true;
}
var errorCallbackCalled = false;
function errorCallback(error) {
spinEventLoopAndSetTimeout();
errorCallbackCalled = true;
}
function test1() {
navigator.geolocation.getCurrentPosition(successCallback, errorCallback, {timeout: 1});
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,15 @@
/* 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/. */
[HeaderFile="mozilla/dom/InterAppComm.h",
Func="mozilla::dom::InterAppComm::EnabledForScope",
Constructor(DOMString keyword, DOMString publisher, DOMString subsriber),
JSImplementation="@mozilla.org/dom/inter-app-connection;1"]
interface MozInterAppConnection {
readonly attribute DOMString keyword;
readonly attribute DOMString publisher;
readonly attribute DOMString subscriber;
void cancel();
};

View File

@ -0,0 +1,13 @@
/* 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/. */
[HeaderFile="mozilla/dom/InterAppComm.h",
Func="mozilla::dom::InterAppComm::EnabledForScope",
Constructor(DOMString keyword, MozInterAppMessagePort port),
JSImplementation="@mozilla.org/dom/inter-app-connection-request;1"]
interface MozInterAppConnectionRequest {
readonly attribute DOMString keyword;
readonly attribute MozInterAppMessagePort port;
};

View File

@ -0,0 +1,16 @@
/* 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/. */
dictionary MozInterAppMessageEventInit : EventInit {
any data;
};
[HeaderFile="mozilla/dom/InterAppComm.h",
Func="mozilla::dom::InterAppComm::EnabledForScope",
Constructor(DOMString type,
optional MozInterAppMessageEventInit eventInitDict),
JSImplementation="@mozilla.org/dom/inter-app-message-event;1"]
interface MozInterAppMessageEvent : Event {
readonly attribute any data;
};

View File

@ -0,0 +1,24 @@
/* 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/. */
// TODO Bug 907060 Per off-line discussion, after the MessagePort is done
// at Bug 643325, we will start to refactorize the common logic of both
// Inter-App Communication and Shared Worker. For now, we hope to design an
// MozInterAppMessagePort to meet the timeline, which still follows exactly
// the same interface and semantic as the MessagePort is. In the future,
// we can then align it back to MessagePort with backward compatibility.
[HeaderFile="mozilla/dom/InterAppComm.h",
Func="mozilla::dom::InterAppComm::EnabledForScope",
Constructor(DOMString messagePortID),
JSImplementation="@mozilla.org/dom/inter-app-message-port;1"]
interface MozInterAppMessagePort : EventTarget {
void postMessage(any message);
void start();
void close();
attribute EventHandler onmessage;
};

View File

@ -186,6 +186,10 @@ WEBIDL_FILES = [
'ImageData.webidl',
'ImageDocument.webidl',
'InspectorUtils.webidl',
'InterAppConnection.webidl',
'InterAppConnectionRequest.webidl',
'InterAppMessageEvent.webidl',
'InterAppMessagePort.webidl',
'KeyboardEvent.webidl',
'KeyEvent.webidl',
'LinkStyle.webidl',

View File

@ -674,8 +674,10 @@ public:
if (csp) {
NS_NAMED_LITERAL_STRING(scriptSample,
"Call to eval() or related function blocked by CSP.");
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
mFileName, scriptSample, mLineNum);
if (mWorkerPrivate->GetReportCSPViolations()) {
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
mFileName, scriptSample, mLineNum);
}
}
nsRefPtr<LogViolationDetailsResponseRunnable> response =

View File

@ -219,7 +219,6 @@ function runTests()
}
// IME
const nsIDOMWindowUtils = Components.interfaces.nsIDOMWindowUtils;
// start composition
synthesizeComposition({ type: "compositionstart" });
// input first character
@ -229,7 +228,7 @@ function runTests()
{ "string": "\u3089",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }

View File

@ -53,7 +53,9 @@ GrallocImage::GrallocImage()
GrallocImage::~GrallocImage()
{
if (mGraphicBuffer.get()) {
// If we have a texture client, the latter takes over the responsibility to
// unlock the GraphicBufferLocked.
if (mGraphicBuffer.get() && !mTextureClient) {
mGraphicBuffer->Unlock();
if (mBufferAllocated) {
ImageBridgeChild *ibc = ImageBridgeChild::GetSingleton();
@ -294,6 +296,7 @@ GrallocImage::GetTextureClient()
mTextureClient = new GrallocTextureClientOGL(actor,
gfx::ToIntSize(mSize),
flags);
mTextureClient->SetGraphicBufferLocked(mGraphicBuffer);
}
return mTextureClient;
}

View File

@ -177,21 +177,25 @@ ImageContainer::SetCurrentImageInternal(Image *aImage)
}
void
ImageContainer::SetCurrentImage(Image *aImage)
ImageContainer::ClearCurrentImage()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
if (IsAsync()) {
if (aImage) {
ImageBridgeChild::DispatchImageClientUpdate(mImageClient, this);
} else {
// here we used to have a SetIdle() call on the image bridge to tell
// the compositor that the video element is not going to be seen for
// moment and that it can release its shared memory. It was causing
// crashes so it has been removed.
// This may be reimplemented after 858914 lands.
}
SetCurrentImageInternal(nullptr);
}
void
ImageContainer::SetCurrentImage(Image *aImage)
{
if (IsAsync() && !aImage) {
// Let ImageClient to release all TextureClients.
ImageBridgeChild::FlushImage(mImageClient, this);
return;
}
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
if (IsAsync()) {
ImageBridgeChild::DispatchImageClientUpdate(mImageClient, this);
}
SetCurrentImageInternal(aImage);
}

View File

@ -333,6 +333,14 @@ public:
*/
void SetCurrentImage(Image* aImage);
/**
* Clear the current image.
* This function is expect to be called only from a CompositableClient
* that belongs to ImageBridgeChild. Created to prevent dead lock.
* See Bug 901224.
*/
void ClearCurrentImage();
/**
* Set an Image as the current image to display. The Image must have
* been created by this ImageContainer.

View File

@ -1173,9 +1173,13 @@ Layer::Dump(FILE* aFile, const char* aPrefix, bool aDumpHtml)
fprintf(aFile, ">");
}
DumpSelf(aFile, aPrefix);
#ifdef MOZ_DUMP_PAINTING
if (AsLayerComposite() && AsLayerComposite()->GetCompositableHost()) {
AsLayerComposite()->GetCompositableHost()->Dump(aFile, aPrefix, aDumpHtml);
}
#endif
if (aDumpHtml) {
fprintf(aFile, "</a>");
}

View File

@ -337,6 +337,10 @@ ClientLayerManager::ForwardTransaction()
// glue code here to find the TextureClient and invoke a callback to
// let the camera know that the gralloc buffer is not used anymore on
// the compositor side and that it can reuse it.
const ReplyTextureRemoved& rep = reply.get_ReplyTextureRemoved();
CompositableClient* compositable
= static_cast<CompositableChild*>(rep.compositableChild())->GetCompositableClient();
compositable->OnReplyTextureRemoved(rep.textureId());
break;
}

View File

@ -32,9 +32,26 @@ CompositableClient::~CompositableClient()
{
MOZ_COUNT_DTOR(CompositableClient);
Destroy();
FlushTexturesToRemoveCallbacks();
MOZ_ASSERT(mTexturesToRemove.Length() == 0, "would leak textures pending for deletion");
}
void
CompositableClient::FlushTexturesToRemoveCallbacks()
{
std::map<uint64_t,TextureClientData*>::iterator it
= mTexturesToRemoveCallbacks.begin();
std::map<uint64_t,TextureClientData*>::iterator stop
= mTexturesToRemoveCallbacks.end();
for (; it != stop; ++it) {
it->second->DeallocateSharedData(GetForwarder());
delete it->second;
}
mTexturesToRemoveCallbacks.clear();
}
LayersBackend
CompositableClient::GetCompositorBackendType() const
{
@ -206,16 +223,36 @@ void
CompositableClient::RemoveTextureClient(TextureClient* aClient)
{
MOZ_ASSERT(aClient);
mTexturesToRemove.AppendElement(aClient->GetID());
mTexturesToRemove.AppendElement(TextureIDAndFlags(aClient->GetID(),
aClient->GetFlags()));
if (!(aClient->GetFlags() & TEXTURE_DEALLOCATE_HOST)) {
TextureClientData* data = aClient->DropTextureData();
if (data) {
mTexturesToRemoveCallbacks[aClient->GetID()] = data;
}
}
aClient->ClearID();
aClient->MarkInvalid();
}
void
CompositableClient::OnReplyTextureRemoved(uint64_t aTextureID)
{
std::map<uint64_t,TextureClientData*>::iterator it
= mTexturesToRemoveCallbacks.find(aTextureID);
if (it != mTexturesToRemoveCallbacks.end()) {
it->second->DeallocateSharedData(GetForwarder());
delete it->second;
mTexturesToRemoveCallbacks.erase(it);
}
}
void
CompositableClient::OnTransaction()
{
for (unsigned i = 0; i < mTexturesToRemove.Length(); ++i) {
mForwarder->RemoveTexture(this, mTexturesToRemove[i]);
const TextureIDAndFlags& texture = mTexturesToRemove[i];
mForwarder->RemoveTexture(this, texture.mID, texture.mFlags);
}
mTexturesToRemove.Clear();
}

View File

@ -8,6 +8,7 @@
#include <stdint.h> // for uint64_t
#include <vector> // for vector
#include <map> // for map
#include "mozilla/Assertions.h" // for MOZ_CRASH
#include "mozilla/RefPtr.h" // for TemporaryRef, RefCounted
#include "mozilla/gfx/Types.h" // for SurfaceFormat
@ -27,6 +28,7 @@ class ImageBridgeChild;
class CompositableForwarder;
class CompositableChild;
class SurfaceDescriptor;
class TextureClientData;
/**
* CompositableClient manages the texture-specific logic for composite layers,
@ -137,9 +139,19 @@ public:
*/
virtual void OnDetach() {}
void OnReplyTextureRemoved(uint64_t aTextureID);
void FlushTexturesToRemoveCallbacks();
protected:
struct TextureIDAndFlags {
TextureIDAndFlags(uint64_t aID, TextureFlags aFlags)
: mID(aID), mFlags(aFlags) {}
uint64_t mID;
TextureFlags mFlags;
};
// The textures to destroy in the next transaction;
nsTArray<uint64_t> mTexturesToRemove;
nsTArray<TextureIDAndFlags> mTexturesToRemove;
std::map<uint64_t, TextureClientData*> mTexturesToRemoveCallbacks;
uint64_t mNextTextureID;
CompositableChild* mCompositableChild;
CompositableForwarder* mForwarder;

View File

@ -97,6 +97,28 @@ TextureInfo ImageClientSingle::GetTextureInfo() const
return TextureInfo(COMPOSITABLE_IMAGE);
}
void
ImageClientSingle::FlushImage()
{
if (mFrontBuffer) {
RemoveTextureClient(mFrontBuffer);
mFrontBuffer = nullptr;
}
}
void
ImageClientBuffered::FlushImage()
{
if (mFrontBuffer) {
RemoveTextureClient(mFrontBuffer);
mFrontBuffer = nullptr;
}
if (mBackBuffer) {
RemoveTextureClient(mBackBuffer);
mBackBuffer = nullptr;
}
}
bool
ImageClientSingle::UpdateImage(ImageContainer* aContainer,
uint32_t aContentFlags)

View File

@ -62,6 +62,11 @@ public:
virtual already_AddRefed<Image> CreateImage(const uint32_t *aFormats,
uint32_t aNumFormats) = 0;
/**
* Synchronously remove all the textures used by the image client.
*/
virtual void FlushImage() {}
protected:
ImageClient(CompositableForwarder* aFwd, CompositableType aType);
@ -96,6 +101,9 @@ public:
virtual already_AddRefed<Image> CreateImage(const uint32_t *aFormats,
uint32_t aNumFormats) MOZ_OVERRIDE;
virtual void FlushImage() MOZ_OVERRIDE;
protected:
RefPtr<TextureClient> mFrontBuffer;
// Some layers may want to enforce some flags to all their textures
@ -117,6 +125,8 @@ public:
virtual void OnDetach() MOZ_OVERRIDE;
virtual void FlushImage() MOZ_OVERRIDE;
protected:
RefPtr<TextureClient> mBackBuffer;
};

View File

@ -35,6 +35,78 @@ using namespace mozilla::gl;
namespace mozilla {
namespace layers {
class ShmemTextureClientData : public TextureClientData
{
public:
ShmemTextureClientData(ipc::Shmem& aShmem)
: mShmem(aShmem)
{
MOZ_COUNT_CTOR(ShmemTextureClientData);
}
~ShmemTextureClientData()
{
MOZ_COUNT_CTOR(ShmemTextureClientData);
}
virtual void DeallocateSharedData(ISurfaceAllocator* allocator)
{
allocator->DeallocShmem(mShmem);
mShmem = ipc::Shmem();
}
private:
ipc::Shmem mShmem;
};
class MemoryTextureClientData : public TextureClientData
{
public:
MemoryTextureClientData(uint8_t* aBuffer)
: mBuffer(aBuffer)
{
MOZ_COUNT_CTOR(MemoryTextureClientData);
}
~MemoryTextureClientData()
{
MOZ_ASSERT(!mBuffer, "Forgot to deallocate the shared texture data?");
MOZ_COUNT_CTOR(MemoryTextureClientData);
}
virtual void DeallocateSharedData(ISurfaceAllocator*)
{
delete[] mBuffer;
}
private:
uint8_t* mBuffer;
};
TextureClientData*
MemoryTextureClient::DropTextureData()
{
if (!mBuffer) {
return nullptr;
}
TextureClientData* result = new MemoryTextureClientData(mBuffer);
MarkInvalid();
mBuffer = nullptr;
return result;
}
TextureClientData*
ShmemTextureClient::DropTextureData()
{
if (!mShmem.IsReadable()) {
return nullptr;
}
TextureClientData* result = new ShmemTextureClientData(mShmem);
MarkInvalid();
mShmem = ipc::Shmem();
return result;
}
TextureClient::TextureClient(TextureFlags aFlags)
: mID(0)
, mFlags(aFlags)
@ -51,15 +123,11 @@ TextureClient::ShouldDeallocateInDestructor() const
if (!IsAllocated()) {
return false;
}
if (GetFlags() & TEXTURE_DEALLOCATE_CLIENT) {
return true;
}
// If we're meant to be deallocated by the host,
// but we haven't been shared yet, then we should
// deallocate on the client instead.
return (GetFlags() & TEXTURE_DEALLOCATE_HOST) &&
!IsSharedWithCompositor();
return !IsSharedWithCompositor();
}
bool

View File

@ -69,6 +69,28 @@ public:
StereoMode aStereoMode) = 0;
};
/**
* Holds the shared data of a TextureClient, to be destroyed later.
*
* TextureClient's destructor initiates the destruction sequence of the
* texture client/host pair. If the shared data is to be deallocated on the
* host side, there is nothing to do.
* On the other hand, if the client data must be deallocated on the client
* side, the CompositableClient will ask the TextureClient to drop its shared
* data in the form of a TextureClientData object. The compositable will keep
* this object until it has received from the host side the confirmation that
* the compositor is not using the texture and that it is completely safe to
* deallocate the shared data.
*
* See:
* - CompositableClient::RemoveTextureClient
* - CompositableClient::OnReplyTextureRemoved
*/
class TextureClientData {
public:
virtual void DeallocateSharedData(ISurfaceAllocator* allocator) = 0;
virtual ~TextureClientData() {}
};
/**
* TextureClient is a thin abstraction over texture data that need to be shared
@ -144,6 +166,8 @@ public:
virtual gfx::IntSize GetSize() const = 0;
virtual TextureClientData* DropTextureData() = 0;
TextureFlags GetFlags() const { return mFlags; }
/**
@ -268,6 +292,8 @@ public:
virtual bool IsAllocated() const MOZ_OVERRIDE { return mAllocated; }
virtual TextureClientData* DropTextureData() MOZ_OVERRIDE;
ISurfaceAllocator* GetAllocator() const;
ipc::Shmem& GetShmem() { return mShmem; }
@ -301,6 +327,8 @@ public:
virtual bool IsAllocated() const MOZ_OVERRIDE { return mBuffer != nullptr; }
virtual TextureClientData* DropTextureData() MOZ_OVERRIDE;
protected:
uint8_t* mBuffer;
size_t mBufSize;

View File

@ -106,7 +106,7 @@ CanvasLayerComposite::RenderLayer(const nsIntPoint& aOffset,
CompositableHost*
CanvasLayerComposite::GetCompositableHost()
{
if (mImageHost->IsAttached()) {
if ( mImageHost && mImageHost->IsAttached()) {
return mImageHost.get();
}

View File

@ -261,7 +261,7 @@ public:
void AddTextureHost(TextureHost* aTexture);
virtual void UseTextureHost(TextureHost* aTexture) {}
void RemoveTextureHost(uint64_t aTextureID);
virtual void RemoveTextureHost(uint64_t aTextureID);
TextureHost* GetTextureHost(uint64_t aTextureID);
protected:

View File

@ -42,6 +42,15 @@ ImageHost::UseTextureHost(TextureHost* aTexture)
mFrontBuffer = aTexture;
}
void
ImageHost::RemoveTextureHost(uint64_t aTextureID)
{
CompositableHost::RemoveTextureHost(aTextureID);
if (mFrontBuffer && mFrontBuffer->GetID() == aTextureID) {
mFrontBuffer = nullptr;
}
}
TextureHost*
ImageHost::GetTextureHost()
{

View File

@ -57,6 +57,8 @@ public:
virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
virtual void RemoveTextureHost(uint64_t aTextureID) MOZ_OVERRIDE;
virtual TextureHost* GetTextureHost() MOZ_OVERRIDE;
virtual void SetPictureRect(const nsIntRect& aPictureRect) MOZ_OVERRIDE

View File

@ -158,7 +158,7 @@ ThebesLayerComposite::RenderLayer(const nsIntPoint& aOffset,
CompositableHost*
ThebesLayerComposite::GetCompositableHost()
{
if (mBuffer->IsAttached()) {
if ( mBuffer && mBuffer->IsAttached()) {
return mBuffer.get();
}

View File

@ -169,7 +169,7 @@ public:
*/
virtual void RemoveTexture(CompositableClient* aCompositable,
uint64_t aTextureID,
TextureFlags aFlags = TEXTURE_FLAGS_DEFAULT) = 0;
TextureFlags aFlags) = 0;
/**
* Tell the CompositableHost on the compositor side what texture to use for

View File

@ -122,9 +122,17 @@ ImageBridgeChild::RemoveTexture(CompositableClient* aCompositable,
uint64_t aTexture,
TextureFlags aFlags)
{
mTxn->AddNoSwapEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
aTexture,
aFlags));
if (aFlags & TEXTURE_DEALLOCATE_HOST) {
// if deallocation happens on the host side, we don't need the transaction
// to be synchronous.
mTxn->AddNoSwapEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
aTexture,
aFlags));
} else {
mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
aTexture,
aFlags));
}
}
void
@ -393,6 +401,52 @@ void ImageBridgeChild::DispatchImageClientUpdate(ImageClient* aClient,
nsRefPtr<ImageContainer> >(&UpdateImageClientNow, aClient, aContainer));
}
static void FlushImageSync(ImageClient* aClient, ImageContainer* aContainer, ReentrantMonitor* aBarrier, bool* aDone)
{
ImageBridgeChild::FlushImageNow(aClient, aContainer);
ReentrantMonitorAutoEnter autoMon(*aBarrier);
*aDone = true;
aBarrier->NotifyAll();
}
//static
void ImageBridgeChild::FlushImage(ImageClient* aClient, ImageContainer* aContainer)
{
if (InImageBridgeChildThread()) {
FlushImageNow(aClient, aContainer);
return;
}
ReentrantMonitor barrier("CreateImageClient Lock");
ReentrantMonitorAutoEnter autoMon(barrier);
bool done = false;
sImageBridgeChildSingleton->GetMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(&FlushImageSync, aClient, aContainer, &barrier, &done));
// should stop the thread until the ImageClient has been created on
// the other thread
while (!done) {
barrier.Wait();
}
}
//static
void ImageBridgeChild::FlushImageNow(ImageClient* aClient, ImageContainer* aContainer)
{
MOZ_ASSERT(aClient);
sImageBridgeChildSingleton->BeginTransaction();
if (aContainer) {
aContainer->ClearCurrentImage();
}
aClient->FlushImage();
aClient->OnTransaction();
sImageBridgeChildSingleton->EndTransaction();
aClient->FlushTexturesToRemoveCallbacks();
}
void
ImageBridgeChild::BeginTransaction()
{
@ -454,6 +508,10 @@ ImageBridgeChild::EndTransaction()
// This would be, for instance, the place to implement a mechanism to
// notify the B2G camera that the gralloc buffer is not used by the
// compositor anymore and that it can be recycled.
const ReplyTextureRemoved& rep = reply.get_ReplyTextureRemoved();
CompositableClient* compositable
= static_cast<CompositableChild*>(rep.compositableChild())->GetCompositableClient();
compositable->OnReplyTextureRemoved(rep.textureId());
break;
}
default:

View File

@ -239,6 +239,15 @@ public:
static void DispatchReleaseImageClient(ImageClient* aClient);
static void DispatchImageClientUpdate(ImageClient* aClient, ImageContainer* aContainer);
/**
* Flush all Images sent to CompositableHost.
*/
static void FlushImage(ImageClient* aClient, ImageContainer* aContainer);
/**
* Must be called on the ImageBridgeChild's thread.
*/
static void FlushImageNow(ImageClient* aClient, ImageContainer* aContainer);
// CompositableForwarder

View File

@ -79,7 +79,10 @@ public:
mClientBounds = aClientBounds;
mTargetOrientation = aOrientation;
}
void MarkSyncTransaction()
{
mSwapRequired = true;
}
void AddEdit(const Edit& aEdit)
{
NS_ABORT_IF_FALSE(!Finished(), "forgot BeginTransaction?");
@ -405,10 +408,12 @@ ShadowLayerForwarder::RemoveTexture(CompositableClient* aCompositable,
uint64_t aTexture,
TextureFlags aFlags)
{
mTxn->AddEdit(OpRemoveTexture(nullptr,
aCompositable->GetIPDLActor(),
aTexture,
aFlags));
mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
aTexture,
aFlags));
if (!(aFlags & TEXTURE_DEALLOCATE_HOST)) {
mTxn->MarkSyncTransaction();
}
}
void
@ -426,7 +431,6 @@ ShadowLayerForwarder::UpdatedTexture(CompositableClient* aCompositable,
mTxn->AddNoSwapPaint(OpUpdateTexture(nullptr, aCompositable->GetIPDLActor(),
aTexture->GetID(),
region));
}
}

View File

@ -10,6 +10,7 @@
#include "mozilla/layers/CompositableForwarder.h"
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/layers/ShadowLayerUtilsGralloc.h"
#include "GrallocImages.h"
#include "gfx2DGlue.h"
namespace mozilla {
@ -17,6 +18,75 @@ namespace layers {
using namespace android;
class GraphicBufferLockedTextureClientData : public TextureClientData {
public:
GraphicBufferLockedTextureClientData(GraphicBufferLocked* aBufferLocked)
: mBufferLocked(aBufferLocked)
{
MOZ_COUNT_CTOR(GrallocTextureClientData);
}
~GraphicBufferLockedTextureClientData()
{
MOZ_COUNT_DTOR(GrallocTextureClientData);
MOZ_ASSERT(!mBufferLocked, "Forgot to unlock the GraphicBufferLocked?");
}
virtual void DeallocateSharedData(ISurfaceAllocator*) MOZ_OVERRIDE
{
mBufferLocked->Unlock();
mBufferLocked = nullptr;
}
private:
RefPtr<GraphicBufferLocked> mBufferLocked;
};
class GrallocTextureClientData : public TextureClientData {
public:
GrallocTextureClientData(GrallocBufferActor* aActor)
: mGrallocActor(aActor)
{
MOZ_COUNT_CTOR(GrallocTextureClientData);
}
~GrallocTextureClientData()
{
MOZ_COUNT_DTOR(GrallocTextureClientData);
MOZ_ASSERT(!mGrallocActor, "Forgot to unlock the GraphicBufferLocked?");
}
virtual void DeallocateSharedData(ISurfaceAllocator* allocator) MOZ_OVERRIDE
{
// We just need to wrap the actor in a SurfaceDescriptor because that's what
// ISurfaceAllocator uses as input, we don't care about the other parameters.
SurfaceDescriptor sd = SurfaceDescriptorGralloc(nullptr, mGrallocActor,
nsIntSize(0,0), false, false);
allocator->DestroySharedSurface(&sd);
mGrallocActor = nullptr;
}
private:
GrallocBufferActor* mGrallocActor;
};
TextureClientData*
GrallocTextureClientOGL::DropTextureData()
{
if (mBufferLocked) {
TextureClientData* result = new GraphicBufferLockedTextureClientData(mBufferLocked);
mBufferLocked = nullptr;
mGrallocActor = nullptr;
mGraphicBuffer = nullptr;
return result;
} else {
TextureClientData* result = new GrallocTextureClientData(mGrallocActor);
mGrallocActor = nullptr;
mGraphicBuffer = nullptr;
return result;
}
}
GrallocTextureClientOGL::GrallocTextureClientOGL(GrallocBufferActor* aActor,
gfx::IntSize aSize,
TextureFlags aFlags)
@ -41,6 +111,20 @@ GrallocTextureClientOGL::GrallocTextureClientOGL(CompositableClient* aCompositab
GrallocTextureClientOGL::~GrallocTextureClientOGL()
{
MOZ_COUNT_DTOR(GrallocTextureClientOGL);
if (ShouldDeallocateInDestructor()) {
// If the buffer has never been shared we must deallocate it or it would
// leak.
if (mBufferLocked) {
mBufferLocked->Unlock();
} else {
MOZ_ASSERT(mCompositable);
// We just need to wrap the actor in a SurfaceDescriptor because that's what
// ISurfaceAllocator uses as input, we don't care about the other parameters.
SurfaceDescriptor sd = SurfaceDescriptorGralloc(nullptr, mGrallocActor,
nsIntSize(0,0), false, false);
mCompositable->GetForwarder()->DestroySharedSurface(&sd);
}
}
}
void
@ -54,6 +138,12 @@ GrallocTextureClientOGL::InitWith(GrallocBufferActor* aActor, gfx::IntSize aSize
mSize = aSize;
}
void
GrallocTextureClientOGL::SetGraphicBufferLocked(GraphicBufferLocked* aBufferLocked)
{
mBufferLocked = aBufferLocked;
}
bool
GrallocTextureClientOGL::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
{

View File

@ -15,6 +15,8 @@
namespace mozilla {
namespace layers {
class GraphicBufferLocked;
/**
* A TextureClient implementation based on android::GraphicBuffer (also referred to
* as "gralloc").
@ -50,6 +52,8 @@ public:
virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) MOZ_OVERRIDE;
virtual TextureClientData* DropTextureData() MOZ_OVERRIDE;
void InitWith(GrallocBufferActor* aActor, gfx::IntSize aSize);
gfx::IntSize GetSize() const MOZ_OVERRIDE { return mSize; }
@ -88,6 +92,8 @@ public:
virtual size_t GetBufferSize() const MOZ_OVERRIDE;
void SetGraphicBufferLocked(GraphicBufferLocked* aBufferLocked);
protected:
/**
@ -95,6 +101,8 @@ protected:
*/
GrallocBufferActor* mGrallocActor;
RefPtr<GraphicBufferLocked> mBufferLocked;
android::sp<android::GraphicBuffer> mGraphicBuffer;
/**

View File

@ -41,6 +41,15 @@ public:
virtual gfx::IntSize GetSize() const { return mSize; }
virtual TextureClientData* DropTextureData() MOZ_OVERRIDE
{
// XXX - right now the code paths using this are managing the shared texture
// data, although they should use a TextureClientData for this to ensure that
// the destruction sequence is race-free.
MarkInvalid();
return nullptr;
}
protected:
gl::SharedTextureHandle mHandle;
gfx::IntSize mSize;

View File

@ -78,10 +78,12 @@ ifeq ($(OS_TARGET),Android) # {
LOCAL_INCLUDES += -I$(srcdir)/src/third_party/libevent/android
else # } else {
LOCAL_INCLUDES += -I$(srcdir)/src/third_party/libevent/linux
CSRCS += \
epoll_sub.c \
$(NULL)
endif # }
CSRCS += \
epoll.c \
epoll_sub.c \
$(NULL)
else # } else (OS_BSD) {

View File

@ -51,6 +51,7 @@ RPCChannel::~RPCChannel()
{
MOZ_COUNT_DTOR(RPCChannel);
RPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
Clear();
}
void

View File

@ -151,11 +151,9 @@ EXTRA_DSO_LDOPTS += $(NSPR_LIBS)
# Define keyword generator before rules.mk, see bug 323979 comment 50
HOST_CPPSRCS += jskwgen.cpp
HOST_SIMPLE_PROGRAMS += host_jskwgen$(HOST_BIN_SUFFIX)
GARBAGE += jsautokw.h host_jskwgen$(HOST_BIN_SUFFIX)
HOST_CPPSRCS += jsoplengen.cpp
HOST_SIMPLE_PROGRAMS += host_jsoplengen$(HOST_BIN_SUFFIX)
GARBAGE += jsautooplen.h host_jsoplengen$(HOST_BIN_SUFFIX)

View File

@ -2249,7 +2249,6 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
return null();
}
if (fun->isNamedLambda()) {
if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) {
Definition *dn = p.value().get<FullParseHandler>();
@ -2263,6 +2262,9 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
if (!pc->generateFunctionBindings(context, alloc, bindings))
return null();
if (!FoldConstants(context, &pn, this))
return null();
return pn;
}

View File

@ -9,13 +9,6 @@
#include "gc/Barrier.h"
#include "jscompartment.h"
#include "gc/Marking.h"
#include "gc/StoreBuffer.h"
#include "vm/String-inl.h"
namespace js {
inline const Value &

View File

@ -0,0 +1,14 @@
var a = [];
var count = 0;
a.valueOf = function() {
++count;
}
function f(a) {
6 - a;
}
f(3);
for (var i=0; i<10; i++)
f(a);
assertEq(count, 10);

View File

@ -0,0 +1,5 @@
function f() {
assertEq(typeof eval("this"), "object");
}
for (var i=0; i<5; i++)
f();

View File

@ -5289,7 +5289,7 @@ static const RegisterSet NonVolatileRegs =
static void
LoadAsmJSActivationIntoRegister(MacroAssembler &masm, Register reg)
{
masm.movePtr(ImmWord(GetIonContext()->runtime), reg);
masm.movePtr(ImmPtr(GetIonContext()->runtime), reg);
size_t offset = offsetof(JSRuntime, mainThread) +
PerThreadData::offsetOfAsmJSActivationStackReadOnly();
masm.loadPtr(Address(reg, offset), reg);
@ -5671,16 +5671,16 @@ GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescript
AssertStackAlignment(masm);
switch (exit.sig().retType().which()) {
case RetType::Void:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_Ignore)));
masm.call(ImmPtr(InvokeFromAsmJS_Ignore));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
break;
case RetType::Signed:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_ToInt32)));
masm.call(ImmPtr(InvokeFromAsmJS_ToInt32));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxInt32(argv, ReturnReg);
break;
case RetType::Double:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_ToNumber)));
masm.call(ImmPtr(InvokeFromAsmJS_ToNumber));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.loadDouble(argv, ReturnFloatReg);
break;
@ -5722,16 +5722,16 @@ GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescript
AssertStackAlignment(masm);
switch (exit.sig().retType().which()) {
case RetType::Void:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_Ignore)));
masm.call(ImmPtr(InvokeFromAsmJS_Ignore));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
break;
case RetType::Signed:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_ToInt32)));
masm.call(ImmPtr(InvokeFromAsmJS_ToInt32));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxInt32(argv, ReturnReg);
break;
case RetType::Double:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, &InvokeFromAsmJS_ToNumber)));
masm.call(ImmPtr(InvokeFromAsmJS_ToNumber));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
#if defined(JS_CPU_ARM) && !defined(JS_CPU_ARM_HARDFP)
masm.loadValue(argv, softfpReturnOperand);
@ -5818,12 +5818,12 @@ GenerateOOLConvert(ModuleCompiler &m, RetType retType, Label *throwLabel)
// Call
switch (retType.which()) {
case RetType::Signed:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void *, &ValueToInt32)));
masm.call(ImmPtr(ValueToInt32));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg);
break;
case RetType::Double:
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void *, &ValueToNumber)));
masm.call(ImmPtr(ValueToNumber));
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
#if defined(JS_CPU_ARM) && !defined(JS_CPU_ARM_HARDFP)
masm.loadValue(Address(StackPointer, offsetToArgv), softfpReturnOperand);
@ -6051,8 +6051,8 @@ GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel)
LoadAsmJSActivationIntoRegister(masm, IntArgReg0);
LoadJSContextFromActivation(masm, IntArgReg0, IntArgReg0);
#endif
void (*pf)(JSContext*) = js_ReportOverRecursed;
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, pf)));
void (*reportOverRecursed)(JSContext*) = js_ReportOverRecursed;
masm.call(ImmPtr(reportOverRecursed));
masm.jump(throwLabel);
return !masm.oom();
@ -6109,8 +6109,7 @@ GenerateOperationCallbackExit(ModuleCompiler &m, Label *throwLabel)
LoadJSContextFromActivation(masm, activation, IntArgReg0);
#endif
bool (*pf)(JSContext*) = js_HandleExecutionInterrupt;
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, pf)));
masm.call(ImmPtr(js_HandleExecutionInterrupt));
masm.branchIfFalseBool(ReturnReg, throwLabel);
// Restore the StackPointer to it's position before the call.
@ -6141,8 +6140,7 @@ GenerateOperationCallbackExit(ModuleCompiler &m, Label *throwLabel)
masm.loadPtr(Address(IntArgReg0, AsmJSActivation::offsetOfContext()), IntArgReg0);
masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllMask))); // save all FP registers
bool (*pf)(JSContext*) = js_HandleExecutionInterrupt;
masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, pf)));
masm.call(ImmPtr(js_HandleExecutionInterrupt));
masm.branchIfFalseBool(ReturnReg, throwLabel);
// Restore the machine state to before the interrupt. this will set the pc!

View File

@ -190,8 +190,8 @@ BaselineCompiler::compile()
size_t icEntry = icLoadLabels_[i].icEntry;
ICEntry *entryAddr = &(baselineScript->icEntry(icEntry));
Assembler::patchDataWithValueCheck(CodeLocationLabel(code, label),
ImmWord(uintptr_t(entryAddr)),
ImmWord(uintptr_t(-1)));
ImmPtr(entryAddr),
ImmPtr((void*)-1));
}
if (modifiesArguments_)
@ -236,7 +236,7 @@ BaselineCompiler::emitPrologue()
// the callee, NULL is stored for now so that GC doesn't choke on
// a bogus ScopeChain value in the frame.
if (function())
masm.storePtr(ImmWord((uintptr_t)0), frame.addressOfScopeChain());
masm.storePtr(ImmPtr(NULL), frame.addressOfScopeChain());
else
masm.storePtr(R1.scratchReg(), frame.addressOfScopeChain());
@ -345,7 +345,7 @@ BaselineCompiler::emitOutOfLinePostBarrierSlot()
#endif
masm.setupUnalignedABICall(2, scratch);
masm.movePtr(ImmWord(cx->runtime()), scratch);
masm.movePtr(ImmPtr(cx->runtime()), scratch);
masm.passABIArg(scratch);
masm.passABIArg(objReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier));
@ -527,7 +527,7 @@ BaselineCompiler::emitUseCountIncrement()
masm.branchPtr(Assembler::Equal,
Address(scriptReg, JSScript::offsetOfIonScript()),
ImmWord(ION_COMPILING_SCRIPT), &skipCall);
ImmPtr(ION_COMPILING_SCRIPT), &skipCall);
// Call IC.
ICUseCount_Fallback::Compiler stubCompiler(cx);
@ -969,7 +969,7 @@ BaselineCompiler::emit_JSOP_THIS()
frame.pushThis();
// In strict mode function or self-hosted function, |this| is left alone.
if (!function() || function()->strict() || function()->isSelfHostedBuiltin())
if (function() && (function()->strict() || function()->isSelfHostedBuiltin()))
return true;
Label skipIC;
@ -2067,7 +2067,7 @@ BaselineCompiler::emitInitPropGetterSetter()
pushArg(R0.scratchReg());
pushArg(ImmGCPtr(script->getName(pc)));
pushArg(R1.scratchReg());
pushArg(ImmWord(pc));
pushArg(ImmPtr(pc));
if (!callVM(InitPropGetterSetterInfo))
return false;
@ -2111,7 +2111,7 @@ BaselineCompiler::emitInitElemGetterSetter()
pushArg(R0);
masm.extractObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg());
pushArg(R0.scratchReg());
pushArg(ImmWord(pc));
pushArg(ImmPtr(pc));
if (!callVM(InitElemGetterSetterInfo))
return false;
@ -2538,7 +2538,7 @@ bool
BaselineCompiler::emit_JSOP_DEBUGGER()
{
prepareVMCall();
pushArg(ImmWord(pc));
pushArg(ImmPtr(pc));
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
pushArg(R0.scratchReg());
@ -2645,7 +2645,7 @@ BaselineCompiler::emit_JSOP_TOID()
pushArg(R0);
pushArg(R1);
pushArg(ImmWord(pc));
pushArg(ImmPtr(pc));
pushArg(ImmGCPtr(script));
if (!callVM(ToIdInfo))

View File

@ -685,7 +685,7 @@ ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, Reg
saveRegs = GeneralRegisterSet::Intersect(saveRegs, GeneralRegisterSet::Volatile());
masm.PushRegsInMask(saveRegs);
masm.setupUnalignedABICall(2, scratch);
masm.movePtr(ImmWord(cx->runtime()), scratch);
masm.movePtr(ImmPtr(cx->runtime()), scratch);
masm.passABIArg(scratch);
masm.passABIArg(obj);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier));
@ -971,7 +971,7 @@ ICUseCount_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
leaveStubFrame(masm);
// If no IonCode was found, then skip just exit the IC.
masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmWord((void*) NULL), &noCompiledCode);
masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmPtr(NULL), &noCompiledCode);
}
// Get a scratch register.
@ -3198,7 +3198,7 @@ GenerateDOMProxyChecks(JSContext *cx, MacroAssembler &masm, Register object,
// expando object at all, in which case the presence of a non-undefined
// expando value in the incoming object is automatically a failure.
masm.loadPtr(*checkExpandoShapeAddr, scratch);
masm.branchPtr(Assembler::Equal, scratch, ImmWord((void*)NULL), &failDOMProxyCheck);
masm.branchPtr(Assembler::Equal, scratch, ImmPtr(NULL), &failDOMProxyCheck);
// Otherwise, ensure that the incoming object has an object for its expando value and that
// the shape matches.
@ -4285,7 +4285,7 @@ ICGetElem_String::Compiler::generateStubCode(MacroAssembler &masm)
&failure);
// Load static string.
masm.movePtr(ImmWord(&cx->runtime()->staticStrings.unitStaticTable), str);
masm.movePtr(ImmPtr(&cx->runtime()->staticStrings.unitStaticTable), str);
masm.loadPtr(BaseIndex(str, scratchReg, ScalePointer), str);
// Return.
@ -4484,7 +4484,7 @@ ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler &masm)
masm.loadPtr(BaseIndex(scratchReg, tempReg, ScaleFromElemWidth(sizeof(size_t))), scratchReg);
// Don't bother testing specific bit, if any bit is set in the word, fail.
masm.branchPtr(Assembler::NotEqual, scratchReg, ImmWord((size_t)0), &failureReconstructInputs);
masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(NULL), &failureReconstructInputs);
// Load the value. use scratchReg and tempReg to form a ValueOperand to load into.
masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData);
@ -6098,9 +6098,11 @@ ICGetProp_TypedArrayLength::Compiler::generateStubCode(MacroAssembler &masm)
// Implement the negated version of JSObject::isTypedArray predicate.
masm.loadObjClass(obj, scratch);
masm.branchPtr(Assembler::Below, scratch, ImmWord(&TypedArrayObject::classes[0]), &failure);
masm.branchPtr(Assembler::Below, scratch, ImmPtr(&TypedArrayObject::classes[0]),
&failure);
masm.branchPtr(Assembler::AboveOrEqual, scratch,
ImmWord(&TypedArrayObject::classes[ScalarTypeRepresentation::TYPE_MAX]), &failure);
ImmPtr(&TypedArrayObject::classes[ScalarTypeRepresentation::TYPE_MAX]),
&failure);
// Load length from fixed slot.
masm.loadValue(Address(obj, TypedArrayObject::lengthOffset()), R0);
@ -7696,7 +7698,7 @@ ICCallStubCompiler::guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs,
failure);
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
masm.branchPtr(Assembler::NotEqual, callee, ImmWord((void*) js_fun_apply), failure);
masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(js_fun_apply), failure);
// Load the |thisv|, ensure that it's a scripted function with a valid baseline or ion
// script, or a native function.
@ -7916,7 +7918,7 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler &masm)
masm.loadBaselineOrIonRaw(callee, code, SequentialExecution, &failure);
} else {
Address scriptCode(callee, JSScript::offsetOfBaselineOrIonRaw());
masm.branchPtr(Assembler::Equal, scriptCode, ImmWord((void *)NULL), &failure);
masm.branchPtr(Assembler::Equal, scriptCode, ImmPtr(NULL), &failure);
}
// We no longer need R1.

Some files were not shown because too many files have changed in this diff Show More