зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to fx-team.
This commit is contained in:
Коммит
e22b5c232b
7
CLOBBER
7
CLOBBER
|
@ -18,4 +18,9 @@
|
||||||
# Modifying this file will now automatically clobber the buildbot machines \o/
|
# Modifying this file will now automatically clobber the buildbot machines \o/
|
||||||
#
|
#
|
||||||
|
|
||||||
Bug 887836 - webidl changes require a Windows clobber.
|
# Are you updating CLOBBER because you think it's needed for your WebIDL
|
||||||
|
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||||
|
# don't change CLOBBER for WebIDL changes any more.
|
||||||
|
Bug 928195 rewrote WebIDL build system integration from the ground up. This
|
||||||
|
will hopefully be the last required clobber due to WebIDLs poorly interacting
|
||||||
|
with the build system.
|
||||||
|
|
|
@ -548,14 +548,34 @@ HyperTextAccessible::DOMPointToHypertextOffset(nsINode* aNode,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
bool
|
||||||
HyperTextAccessible::HypertextOffsetsToDOMRange(int32_t aStartHTOffset,
|
HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
|
||||||
int32_t aEndHTOffset,
|
|
||||||
nsRange* aRange)
|
nsRange* aRange)
|
||||||
{
|
{
|
||||||
// If the given offsets are 0 and associated editor is empty then return
|
DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
|
||||||
// collapsed range with editor root element as range container.
|
if (!startPoint.node)
|
||||||
if (aStartHTOffset == 0 && aEndHTOffset == 0) {
|
return false;
|
||||||
|
|
||||||
|
aRange->SetStart(startPoint.node, startPoint.idx);
|
||||||
|
if (aStartOffset == aEndOffset) {
|
||||||
|
aRange->SetEnd(startPoint.node, startPoint.idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
|
||||||
|
if (!endPoint.node)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
aRange->SetEnd(endPoint.node, endPoint.idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOMPoint
|
||||||
|
HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
|
||||||
|
{
|
||||||
|
// 0 offset is valid even if no children. In this case the associated editor
|
||||||
|
// is empty so return a DOM point for editor root element.
|
||||||
|
if (aOffset == 0) {
|
||||||
nsCOMPtr<nsIEditor> editor = GetEditor();
|
nsCOMPtr<nsIEditor> editor = GetEditor();
|
||||||
if (editor) {
|
if (editor) {
|
||||||
bool isEmpty = false;
|
bool isEmpty = false;
|
||||||
|
@ -565,40 +585,36 @@ HyperTextAccessible::HypertextOffsetsToDOMRange(int32_t aStartHTOffset,
|
||||||
editor->GetRootElement(getter_AddRefs(editorRootElm));
|
editor->GetRootElement(getter_AddRefs(editorRootElm));
|
||||||
|
|
||||||
nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
|
nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
|
||||||
if (editorRoot) {
|
return DOMPoint(editorRoot, 0);
|
||||||
aRange->SetStart(editorRoot, 0);
|
|
||||||
aRange->SetEnd(editorRoot, 0);
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t childIdx = GetChildIndexAtOffset(aOffset);
|
||||||
|
if (childIdx == -1)
|
||||||
|
return DOMPoint();
|
||||||
|
|
||||||
|
Accessible* child = GetChildAt(childIdx);
|
||||||
|
int32_t innerOffset = aOffset - GetChildOffset(childIdx);
|
||||||
|
|
||||||
|
// A text leaf case. The point is inside the text node.
|
||||||
|
if (child->IsTextLeaf()) {
|
||||||
|
nsIContent* content = child->GetContent();
|
||||||
|
int32_t idx = 0;
|
||||||
|
if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
|
||||||
|
innerOffset, &idx)))
|
||||||
|
return DOMPoint();
|
||||||
|
|
||||||
|
return DOMPoint(content, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsRefPtr<Accessible> startAcc, endAcc;
|
// Case of embedded object. The point is either before or after the element.
|
||||||
int32_t startOffset = aStartHTOffset, endOffset = aEndHTOffset;
|
NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
|
||||||
nsIFrame *startFrame = nullptr, *endFrame = nullptr;
|
nsINode* node = child->GetNode();
|
||||||
|
nsINode* parentNode = node->GetParentNode();
|
||||||
startFrame = GetPosAndText(startOffset, endOffset, nullptr, &endFrame,
|
return parentNode ?
|
||||||
getter_AddRefs(startAcc), getter_AddRefs(endAcc));
|
DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
|
||||||
if (!startAcc || !endAcc)
|
DOMPoint();
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
|
|
||||||
DOMPoint startPoint, endPoint;
|
|
||||||
nsresult rv = GetDOMPointByFrameOffset(startFrame, startOffset, startAcc,
|
|
||||||
&startPoint);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
rv = aRange->SetStart(startPoint.node, startPoint.idx);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
if (aStartHTOffset == aEndHTOffset)
|
|
||||||
return aRange->SetEnd(startPoint.node, startPoint.idx);
|
|
||||||
|
|
||||||
rv = GetDOMPointByFrameOffset(endFrame, endOffset, endAcc, &endPoint);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
return aRange->SetEnd(endPoint.node, endPoint.idx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t
|
int32_t
|
||||||
|
@ -1580,7 +1596,8 @@ HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
|
||||||
if (!range)
|
if (!range)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
HypertextOffsetsToDOMRange(startOffset, endOffset, range);
|
if (!OffsetsToDOMRange(startOffset, endOffset, range))
|
||||||
|
return false;
|
||||||
|
|
||||||
// If new range was created then add it, otherwise notify selection listeners
|
// If new range was created then add it, otherwise notify selection listeners
|
||||||
// that existing selection range was changed.
|
// that existing selection range was changed.
|
||||||
|
@ -1610,8 +1627,7 @@ HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
|
||||||
uint32_t aScrollType)
|
uint32_t aScrollType)
|
||||||
{
|
{
|
||||||
nsRefPtr<nsRange> range = new nsRange(mContent);
|
nsRefPtr<nsRange> range = new nsRange(mContent);
|
||||||
nsresult rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset, range);
|
if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
|
||||||
if (NS_SUCCEEDED(rv))
|
|
||||||
nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
|
nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1629,8 +1645,7 @@ HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
|
||||||
this);
|
this);
|
||||||
|
|
||||||
nsRefPtr<nsRange> range = new nsRange(mContent);
|
nsRefPtr<nsRange> range = new nsRange(mContent);
|
||||||
nsresult rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset, range);
|
if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
|
||||||
if (NS_FAILED(rv))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
nsPresContext* presContext = frame->PresContext();
|
nsPresContext* presContext = frame->PresContext();
|
||||||
|
@ -1658,7 +1673,7 @@ HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
|
||||||
int16_t hPercent = offsetPointX * 100 / size.width;
|
int16_t hPercent = offsetPointX * 100 / size.width;
|
||||||
int16_t vPercent = offsetPointY * 100 / size.height;
|
int16_t vPercent = offsetPointY * 100 / size.height;
|
||||||
|
|
||||||
rv = nsCoreUtils::ScrollSubstringTo(frame, range, vPercent, hPercent);
|
nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, vPercent, hPercent);
|
||||||
if (NS_FAILED(rv))
|
if (NS_FAILED(rv))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@ namespace mozilla {
|
||||||
namespace a11y {
|
namespace a11y {
|
||||||
|
|
||||||
struct DOMPoint {
|
struct DOMPoint {
|
||||||
|
DOMPoint() : node(nullptr), idx(0) { }
|
||||||
|
DOMPoint(nsINode* aNode, int32_t aIdx) : node(aNode), idx(aIdx) { }
|
||||||
|
|
||||||
nsINode* node;
|
nsINode* node;
|
||||||
int32_t idx;
|
int32_t idx;
|
||||||
};
|
};
|
||||||
|
@ -128,16 +131,25 @@ public:
|
||||||
bool aIsEndOffset = false) const;
|
bool aIsEndOffset = false) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn a start and end hypertext offsets into DOM range.
|
* Convert start and end hypertext offsets into DOM range.
|
||||||
*
|
*
|
||||||
* @param aStartHTOffset [in] the given start hypertext offset
|
* @param aStartOffset [in] the given start hypertext offset
|
||||||
* @param aEndHTOffset [in] the given end hypertext offset
|
* @param aEndOffset [in] the given end hypertext offset
|
||||||
* @param aRange [out] the range whose bounds to set
|
* @param aRange [in, out] the range whose bounds to set
|
||||||
|
* @return true if conversion was successful
|
||||||
*/
|
*/
|
||||||
nsresult HypertextOffsetsToDOMRange(int32_t aStartHTOffset,
|
bool OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
|
||||||
int32_t aEndHTOffset,
|
|
||||||
nsRange* aRange);
|
nsRange* aRange);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given offset into DOM point.
|
||||||
|
*
|
||||||
|
* If offset is at text leaf then DOM point is (text node, offsetInTextNode),
|
||||||
|
* if before embedded object then (parent node, indexInParent), if after then
|
||||||
|
* (parent node, indexInParent + 1).
|
||||||
|
*/
|
||||||
|
DOMPoint OffsetToDOMPoint(int32_t aOffset);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the used ARIA role (if any) allows the hypertext accessible
|
* Return true if the used ARIA role (if any) allows the hypertext accessible
|
||||||
* to expose text interfaces.
|
* to expose text interfaces.
|
||||||
|
|
|
@ -118,8 +118,8 @@ this.AccessFu = {
|
||||||
Output.start();
|
Output.start();
|
||||||
TouchAdapter.start();
|
TouchAdapter.start();
|
||||||
|
|
||||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||||
Services.obs.addObserver(this, 'Accessibility:NextObject', false);
|
Services.obs.addObserver(this, 'Accessibility:NextObject', false);
|
||||||
Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
|
Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
|
||||||
Services.obs.addObserver(this, 'Accessibility:Focus', false);
|
Services.obs.addObserver(this, 'Accessibility:Focus', false);
|
||||||
|
@ -162,8 +162,8 @@ this.AccessFu = {
|
||||||
Utils.win.removeEventListener('TabClose', this);
|
Utils.win.removeEventListener('TabClose', this);
|
||||||
Utils.win.removeEventListener('TabSelect', this);
|
Utils.win.removeEventListener('TabSelect', this);
|
||||||
|
|
||||||
Services.obs.removeObserver(this, 'remote-browser-frame-shown');
|
Services.obs.removeObserver(this, 'remote-browser-shown');
|
||||||
Services.obs.removeObserver(this, 'in-process-browser-or-app-frame-shown');
|
Services.obs.removeObserver(this, 'inprocess-browser-shown');
|
||||||
Services.obs.removeObserver(this, 'Accessibility:NextObject');
|
Services.obs.removeObserver(this, 'Accessibility:NextObject');
|
||||||
Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
|
Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
|
||||||
Services.obs.removeObserver(this, 'Accessibility:Focus');
|
Services.obs.removeObserver(this, 'Accessibility:Focus');
|
||||||
|
@ -304,11 +304,15 @@ this.AccessFu = {
|
||||||
case 'Accessibility:MoveByGranularity':
|
case 'Accessibility:MoveByGranularity':
|
||||||
this.Input.moveByGranularity(JSON.parse(aData));
|
this.Input.moveByGranularity(JSON.parse(aData));
|
||||||
break;
|
break;
|
||||||
case 'remote-browser-frame-shown':
|
case 'remote-browser-shown':
|
||||||
case 'in-process-browser-or-app-frame-shown':
|
case 'inprocess-browser-shown':
|
||||||
{
|
{
|
||||||
let mm = aSubject.QueryInterface(Ci.nsIFrameLoader).messageManager;
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
this._handleMessageManager(mm);
|
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
||||||
|
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._handleMessageManager(frameLoader.messageManager);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -856,3 +856,9 @@ pref("osfile.reset_worker_delay", 5000);
|
||||||
|
|
||||||
// The URL of the Firefox Accounts auth server backend
|
// The URL of the Firefox Accounts auth server backend
|
||||||
pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
|
pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
|
||||||
|
|
||||||
|
// APZC preferences.
|
||||||
|
//
|
||||||
|
// Gaia relies heavily on scroll events for now, so lets fire them
|
||||||
|
// more often than the default value (100).
|
||||||
|
pref("apz.asyncscroll.throttle", 40);
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This defines the look-and-feel styling of the error pages.
|
||||||
|
* (see: netError.xhtml)
|
||||||
|
*
|
||||||
|
* Original styling by William Price <bugzilla@mob.rice.edu>
|
||||||
|
* Updated for mobile by: Wes Johnston <wjohnston@mozilla.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 8px 8px;
|
||||||
|
font-family: "Nokia Sans", Tahoma, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 0px 0px 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage {
|
||||||
|
background-color: #CEE6F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage.certerror {
|
||||||
|
background-color: #EFD400;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage.blockedsite {
|
||||||
|
background-color: #BF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorTitle {
|
||||||
|
background: url("chrome://browser/content/images/errorpage-warning.png") left center no-repeat;
|
||||||
|
/* Scaled by .666 of their actual size */
|
||||||
|
background-size: 40px 40px;
|
||||||
|
background-origin: content-box;
|
||||||
|
min-height: 60px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 500px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage.certerror #errorTitle {
|
||||||
|
background-image: url("chrome://browser/content/images/errorpage-larry-black.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage.blockedsite #errorTitle {
|
||||||
|
background-image: url("chrome://browser/content/images/errorpage-larry-white.png");
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorTitleText {
|
||||||
|
padding: 0px 0px 0px 50px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPageContainer {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 20px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 500px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorShortDesc > p:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorShortDesc > p {
|
||||||
|
overflow: auto;
|
||||||
|
border-bottom: 1px solid #999999;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorPage.blockedsite #errorShortDesc > p {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#securityOverrideDiv {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[collapsed] {
|
||||||
|
padding-left: 15px;
|
||||||
|
background-image: url("chrome://browser/skin/images/arrowright-16.png");
|
||||||
|
background-size: 11px 11px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: left 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[collapsed="true"] {
|
||||||
|
background-image: url("chrome://browser/skin/images/arrowright-16.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
div[collapsed="false"] {
|
||||||
|
background-image: url("chrome://browser/skin/images/arrowdown-16.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
div[collapsed="true"] > p,
|
||||||
|
div[collapsed="true"] > div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.3em !important;
|
||||||
|
}
|
|
@ -21,9 +21,10 @@ Cu.import('resource://gre/modules/ErrorPage.jsm');
|
||||||
Cu.import('resource://gre/modules/NetworkStatsService.jsm');
|
Cu.import('resource://gre/modules/NetworkStatsService.jsm');
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// identity
|
// Identity
|
||||||
Cu.import('resource://gre/modules/SignInToWebsite.jsm');
|
Cu.import('resource://gre/modules/SignInToWebsite.jsm');
|
||||||
SignInToWebsiteController.init();
|
SignInToWebsiteController.init();
|
||||||
|
Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
|
||||||
|
|
||||||
Cu.import('resource://gre/modules/DownloadsAPI.jsm');
|
Cu.import('resource://gre/modules/DownloadsAPI.jsm');
|
||||||
|
|
||||||
|
@ -614,6 +615,8 @@ var shell = {
|
||||||
|
|
||||||
this.sendEvent(window, 'ContentStart');
|
this.sendEvent(window, 'ContentStart');
|
||||||
|
|
||||||
|
Services.obs.notifyObservers(null, 'content-start', null);
|
||||||
|
|
||||||
#ifdef MOZ_WIDGET_GONK
|
#ifdef MOZ_WIDGET_GONK
|
||||||
Cu.import('resource://gre/modules/OperatorApps.jsm');
|
Cu.import('resource://gre/modules/OperatorApps.jsm');
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -25,9 +25,11 @@ chrome.jar:
|
||||||
|
|
||||||
% override chrome://global/skin/media/videocontrols.css chrome://browser/content/touchcontrols.css
|
% override chrome://global/skin/media/videocontrols.css chrome://browser/content/touchcontrols.css
|
||||||
% override chrome://global/content/aboutCertError.xhtml chrome://browser/content/aboutCertError.xhtml
|
% override chrome://global/content/aboutCertError.xhtml chrome://browser/content/aboutCertError.xhtml
|
||||||
|
% override chrome://global/skin/netError.css chrome://browser/content/netError.css
|
||||||
|
|
||||||
content/ErrorPage.js (content/ErrorPage.js)
|
content/ErrorPage.js (content/ErrorPage.js)
|
||||||
content/aboutCertError.xhtml (content/aboutCertError.xhtml)
|
content/aboutCertError.xhtml (content/aboutCertError.xhtml)
|
||||||
|
content/netError.css (content/netError.css)
|
||||||
content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
|
content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
|
||||||
content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
|
content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
|
||||||
content/images/errorpage-warning.png (content/images/errorpage-warning.png)
|
content/images/errorpage-warning.png (content/images/errorpage-warning.png)
|
||||||
|
|
|
@ -72,6 +72,10 @@ component {637b0f77-2429-49a0-915f-abf5d0db8b9a} WebappsUpdateTimer.js
|
||||||
contract @mozilla.org/b2g/webapps-update-timer;1 {637b0f77-2429-49a0-915f-abf5d0db8b9a}
|
contract @mozilla.org/b2g/webapps-update-timer;1 {637b0f77-2429-49a0-915f-abf5d0db8b9a}
|
||||||
category update-timer WebappsUpdateTimer @mozilla.org/b2g/webapps-update-timer;1,getService,background-update-timer,webapps.update.interval,86400
|
category update-timer WebappsUpdateTimer @mozilla.org/b2g/webapps-update-timer;1,getService,background-update-timer,webapps.update.interval,86400
|
||||||
|
|
||||||
|
# FxAccountsUIGlue.js
|
||||||
|
component {51875c14-91d7-4b8c-b65d-3549e101228c} FxAccountsUIGlue.js
|
||||||
|
contract @mozilla.org/fxaccounts/fxaccounts-ui-glue;1 {51875c14-91d7-4b8c-b65d-3549e101228c}
|
||||||
|
|
||||||
# HelperAppDialog.js
|
# HelperAppDialog.js
|
||||||
component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
|
component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
|
||||||
contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
|
contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
|
||||||
|
|
|
@ -150,12 +150,16 @@ let ErrorPage = {
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function errorPageInit() {
|
init: function errorPageInit() {
|
||||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function errorPageObserve(aSubject, aTopic, aData) {
|
observe: function errorPageObserve(aSubject, aTopic, aData) {
|
||||||
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
||||||
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
|
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mm = frameLoader.messageManager;
|
let mm = frameLoader.messageManager;
|
||||||
|
|
||||||
// This won't happen from dom/ipc/preload.js in non-OOP builds.
|
// This won't happen from dom/ipc/preload.js in non-OOP builds.
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some specific (certified) apps need to get access to certain Firefox Accounts
|
||||||
|
* functionality that allows them to manage accounts (this is mostly sign up,
|
||||||
|
* sign in, logout and delete) and get information about the currently existing
|
||||||
|
* ones.
|
||||||
|
*
|
||||||
|
* This service listens for requests coming from these apps, triggers the
|
||||||
|
* appropriate Fx Accounts flows and send reponses back to the UI.
|
||||||
|
*
|
||||||
|
* The communication mechanism is based in mozFxAccountsContentEvent (for
|
||||||
|
* messages coming from the UI) and mozFxAccountsChromeEvent (for messages
|
||||||
|
* sent from the chrome side) custom events.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["FxAccountsMgmtService"];
|
||||||
|
|
||||||
|
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
|
||||||
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
|
||||||
|
"resource://gre/modules/FxAccountsManager.jsm");
|
||||||
|
|
||||||
|
this.FxAccountsMgmtService = {
|
||||||
|
|
||||||
|
_sendChromeEvent: function(aMsg) {
|
||||||
|
if (!this._shell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._shell.sendCustomEvent("mozFxAccountsChromeEvent", aMsg);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onFullfill: function(aMsgId, aData) {
|
||||||
|
this._sendChromeEvent({
|
||||||
|
id: aMsgId,
|
||||||
|
data: aData ? aData : null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onReject: function(aMsgId, aReason) {
|
||||||
|
this._sendChromeEvent({
|
||||||
|
id: aMsgId,
|
||||||
|
error: aReason ? aReason : null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
Services.obs.addObserver(this, "content-start", false);
|
||||||
|
},
|
||||||
|
|
||||||
|
observe: function(aSubject, aTopic, aData) {
|
||||||
|
this._shell = Services.wm.getMostRecentWindow("navigator:browser").shell;
|
||||||
|
let content = this._shell.contentBrowser.contentWindow;
|
||||||
|
content.addEventListener("mozFxAccountsContentEvent",
|
||||||
|
FxAccountsMgmtService);
|
||||||
|
Services.obs.removeObserver(this, "content-start");
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEvent: function(aEvent) {
|
||||||
|
let msg = aEvent.detail;
|
||||||
|
log.debug("Got content msg " + JSON.stringify(msg));
|
||||||
|
let self = FxAccountsMgmtService;
|
||||||
|
|
||||||
|
if (!msg.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = msg.data;
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(data.method) {
|
||||||
|
case "getAccounts":
|
||||||
|
FxAccountsManager.getAccount().then(
|
||||||
|
account => {
|
||||||
|
// We only expose the email and verification status so far.
|
||||||
|
self._onFullfill(msg.id, account);
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
self._onReject(msg.id, reason);
|
||||||
|
}
|
||||||
|
).then(null, Components.utils.reportError);
|
||||||
|
break;
|
||||||
|
case "logout":
|
||||||
|
FxAccountsManager.signOut().then(
|
||||||
|
() => {
|
||||||
|
self._onFullfill(msg.id);
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
self._onReject(msg.id, reason);
|
||||||
|
}
|
||||||
|
).then(null, Components.utils.reportError);
|
||||||
|
break;
|
||||||
|
case "queryAccount":
|
||||||
|
FxAccountsManager.queryAccount(data.accountId).then(
|
||||||
|
result => {
|
||||||
|
self._onFullfill(msg.id, result);
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
self._onReject(msg.id, reason);
|
||||||
|
}
|
||||||
|
).then(null, Components.utils.reportError);
|
||||||
|
break;
|
||||||
|
case "signIn":
|
||||||
|
case "signUp":
|
||||||
|
FxAccountsManager[data.method](data.accountId, data.password).then(
|
||||||
|
user => {
|
||||||
|
self._onFullfill(msg.id, user);
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
self._onReject(msg.id, reason);
|
||||||
|
}
|
||||||
|
).then(null, Components.utils.reportError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FxAccountsMgmtService.init();
|
|
@ -0,0 +1,73 @@
|
||||||
|
/* 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 { interfaces: Ci, utils: Cu } = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Promise.jsm");
|
||||||
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
||||||
|
"@mozilla.org/uuid-generator;1",
|
||||||
|
"nsIUUIDGenerator");
|
||||||
|
|
||||||
|
function FxAccountsUIGlue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
FxAccountsUIGlue.prototype = {
|
||||||
|
|
||||||
|
_browser: Services.wm.getMostRecentWindow("navigator:browser"),
|
||||||
|
|
||||||
|
signInFlow: function() {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let content = this._browser.getContentWindow();
|
||||||
|
if (!content) {
|
||||||
|
deferred.reject("InternalErrorNoContent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = uuidgen.generateUUID().toString();
|
||||||
|
|
||||||
|
content.addEventListener("mozFxAccountsRPContentEvent",
|
||||||
|
function onContentEvent(result) {
|
||||||
|
let msg = result.detail;
|
||||||
|
if (!msg || !msg.id || msg.id != id) {
|
||||||
|
deferred.reject("InternalErrorWrongContentEvent");
|
||||||
|
content.removeEventListener("mozFxAccountsRPContentEvent",
|
||||||
|
onContentEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Got content event " + JSON.stringify(msg));
|
||||||
|
|
||||||
|
if (msg.error) {
|
||||||
|
deferred.reject(msg);
|
||||||
|
} else {
|
||||||
|
deferred.resolve(msg.result);
|
||||||
|
}
|
||||||
|
content.removeEventListener("mozFxAccountsRPContentEvent",
|
||||||
|
onContentEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
let detail = {
|
||||||
|
method: "openFlow",
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
log.debug("Send chrome event " + JSON.stringify(detail));
|
||||||
|
this._browser.shell.sendCustomEvent("mozFxAccountsRPChromeEvent", detail);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
classID: Components.ID("{51875c14-91d7-4b8c-b65d-3549e101228c}"),
|
||||||
|
|
||||||
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFxAccountsUIGlue])
|
||||||
|
};
|
||||||
|
|
||||||
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FxAccountsUIGlue]);
|
|
@ -13,6 +13,7 @@ EXTRA_COMPONENTS += [
|
||||||
'ContentHandler.js',
|
'ContentHandler.js',
|
||||||
'ContentPermissionPrompt.js',
|
'ContentPermissionPrompt.js',
|
||||||
'FilePicker.js',
|
'FilePicker.js',
|
||||||
|
'FxAccountsUIGlue.js',
|
||||||
'HelperAppDialog.js',
|
'HelperAppDialog.js',
|
||||||
'MailtoProtocolHandler.js',
|
'MailtoProtocolHandler.js',
|
||||||
'PaymentGlue.js',
|
'PaymentGlue.js',
|
||||||
|
@ -36,6 +37,7 @@ if CONFIG['MOZ_UPDATER']:
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
'ErrorPage.jsm',
|
'ErrorPage.jsm',
|
||||||
|
'FxAccountsMgmtService.jsm',
|
||||||
'SignInToWebsite.jsm',
|
'SignInToWebsite.jsm',
|
||||||
'TelURIParser.jsm',
|
'TelURIParser.jsm',
|
||||||
'WebappsUpdater.jsm',
|
'WebappsUpdater.jsm',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"revision": "9271d94f35a54995e4442da711e51cff0244741d",
|
"revision": "62c0ad5b88f15d5da1cc2496b9534087dbc5d015",
|
||||||
"repo_path": "/integration/gaia-central"
|
"repo_path": "/integration/gaia-central"
|
||||||
}
|
}
|
||||||
|
|
|
@ -789,6 +789,7 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
|
||||||
@BINPATH@/components/TelProtocolHandler.js
|
@BINPATH@/components/TelProtocolHandler.js
|
||||||
@BINPATH@/components/B2GAboutRedirector.js
|
@BINPATH@/components/B2GAboutRedirector.js
|
||||||
@BINPATH@/components/FilePicker.js
|
@BINPATH@/components/FilePicker.js
|
||||||
|
@BINPATH@/components/FxAccountsUIGlue.js
|
||||||
@BINPATH@/components/HelperAppDialog.js
|
@BINPATH@/components/HelperAppDialog.js
|
||||||
@BINPATH@/components/DownloadsUI.js
|
@BINPATH@/components/DownloadsUI.js
|
||||||
|
|
||||||
|
@ -796,6 +797,8 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
|
||||||
@BINPATH@/components/DataStoreService.js
|
@BINPATH@/components/DataStoreService.js
|
||||||
@BINPATH@/components/dom_datastore.xpt
|
@BINPATH@/components/dom_datastore.xpt
|
||||||
|
|
||||||
|
@BINPATH@/components/services_fxaccounts.xpt
|
||||||
|
|
||||||
#ifdef MOZ_WEBSPEECH
|
#ifdef MOZ_WEBSPEECH
|
||||||
@BINPATH@/components/dom_webspeechsynth.xpt
|
@BINPATH@/components/dom_webspeechsynth.xpt
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -752,10 +752,9 @@ WriteBitmap(nsIFile* aFile, imgIContainer* aImage)
|
||||||
{
|
{
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
|
|
||||||
nsRefPtr<gfxASurface> surface;
|
nsRefPtr<gfxASurface> surface =
|
||||||
aImage->GetFrame(imgIContainer::FRAME_FIRST,
|
aImage->GetFrame(imgIContainer::FRAME_FIRST,
|
||||||
imgIContainer::FLAG_SYNC_DECODE,
|
imgIContainer::FLAG_SYNC_DECODE);
|
||||||
getter_AddRefs(surface));
|
|
||||||
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
|
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
nsRefPtr<gfxImageSurface> image(surface->GetAsReadableARGB32ImageSurface());
|
nsRefPtr<gfxImageSurface> image(surface->GetAsReadableARGB32ImageSurface());
|
||||||
|
|
|
@ -14,13 +14,13 @@ function test() {
|
||||||
let events = win.EVENTS;
|
let events = win.EVENTS;
|
||||||
let variables = win.DebuggerView.Variables;
|
let variables = win.DebuggerView.Variables;
|
||||||
|
|
||||||
// Allow this generator function to yield first.
|
|
||||||
executeSoon(() => debuggee.test());
|
|
||||||
yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
|
|
||||||
|
|
||||||
// Wait for the hierarchy to be committed by the VariablesViewController.
|
// Wait for the hierarchy to be committed by the VariablesViewController.
|
||||||
let committed = promise.defer();
|
let committed = promise.defer();
|
||||||
variables.oncommit = committed.resolve;
|
variables.oncommit = committed.resolve;
|
||||||
|
|
||||||
|
// Allow this generator function to yield first.
|
||||||
|
executeSoon(() => debuggee.test());
|
||||||
|
yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
|
||||||
yield committed.promise;
|
yield committed.promise;
|
||||||
|
|
||||||
let firstScope = variables.getScopeAtIndex(0);
|
let firstScope = variables.getScopeAtIndex(0);
|
||||||
|
|
|
@ -13,6 +13,8 @@ mock.pth:python/mock-1.0.0
|
||||||
mozilla.pth:build
|
mozilla.pth:build
|
||||||
mozilla.pth:config
|
mozilla.pth:config
|
||||||
mozilla.pth:xpcom/typelib/xpt/tools
|
mozilla.pth:xpcom/typelib/xpt/tools
|
||||||
|
mozilla.pth:dom/bindings
|
||||||
|
mozilla.pth:dom/bindings/parser
|
||||||
moztreedocs.pth:tools/docs
|
moztreedocs.pth:tools/docs
|
||||||
copy:build/buildconfig.py
|
copy:build/buildconfig.py
|
||||||
packages.txt:testing/mozbase/packages.txt
|
packages.txt:testing/mozbase/packages.txt
|
||||||
|
|
|
@ -111,7 +111,7 @@ interface nsIContentViewManager : nsISupports
|
||||||
readonly attribute nsIContentView rootContentView;
|
readonly attribute nsIContentView rootContentView;
|
||||||
};
|
};
|
||||||
|
|
||||||
[scriptable, builtinclass, uuid(5b9949dc-56f1-47b6-b6d2-3785bb90ed6d)]
|
[scriptable, builtinclass, uuid(a723673b-a26e-4cc6-ae23-ec70df9d97c9)]
|
||||||
interface nsIFrameLoader : nsISupports
|
interface nsIFrameLoader : nsISupports
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -272,6 +272,11 @@ interface nsIFrameLoader : nsISupports
|
||||||
* have a notion of visibility in the parent process when frames are OOP.
|
* have a notion of visibility in the parent process when frames are OOP.
|
||||||
*/
|
*/
|
||||||
[infallible] attribute boolean visible;
|
[infallible] attribute boolean visible;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find out whether the owner content really is a browser or app frame
|
||||||
|
*/
|
||||||
|
readonly attribute boolean ownerIsBrowserOrAppFrame;
|
||||||
};
|
};
|
||||||
|
|
||||||
%{C++
|
%{C++
|
||||||
|
|
|
@ -948,9 +948,9 @@ nsFrameLoader::ShowRemoteFrame(const nsIntSize& size,
|
||||||
EnsureMessageManager();
|
EnsureMessageManager();
|
||||||
|
|
||||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||||
if (OwnerIsBrowserOrAppFrame() && os && !mRemoteBrowserInitialized) {
|
if (os && !mRemoteBrowserInitialized) {
|
||||||
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
||||||
"remote-browser-frame-shown", nullptr);
|
"remote-browser-shown", nullptr);
|
||||||
mRemoteBrowserInitialized = true;
|
mRemoteBrowserInitialized = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1409,6 +1409,14 @@ nsFrameLoader::OwnerIsBrowserOrAppFrame()
|
||||||
return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
|
return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The xpcom getter version
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsFrameLoader::GetOwnerIsBrowserOrAppFrame(bool* aResult)
|
||||||
|
{
|
||||||
|
*aResult = OwnerIsBrowserOrAppFrame();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
nsFrameLoader::OwnerIsAppFrame()
|
nsFrameLoader::OwnerIsAppFrame()
|
||||||
{
|
{
|
||||||
|
@ -1677,19 +1685,17 @@ nsFrameLoader::MaybeCreateDocShell()
|
||||||
mDocShell->SetIsBrowserInsideApp(containingAppId);
|
mDocShell->SetIsBrowserInsideApp(containingAppId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OwnerIsBrowserOrAppFrame()) {
|
|
||||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||||
if (os) {
|
if (os) {
|
||||||
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
||||||
"in-process-browser-or-app-frame-shown", nullptr);
|
"inprocess-browser-shown", nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mMessageManager) {
|
if (OwnerIsBrowserOrAppFrame() && mMessageManager) {
|
||||||
mMessageManager->LoadFrameScript(
|
mMessageManager->LoadFrameScript(
|
||||||
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
|
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
|
||||||
/* allowDelayedLoad = */ true);
|
/* allowDelayedLoad = */ true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1471,13 +1471,6 @@ GK_ATOM(restart, "restart")
|
||||||
GK_ATOM(to, "to")
|
GK_ATOM(to, "to")
|
||||||
GK_ATOM(XML, "XML")
|
GK_ATOM(XML, "XML")
|
||||||
|
|
||||||
// internal MathML attributes: different from columnalign_, columnlines_,
|
|
||||||
// rowalign_ and rowlines_
|
|
||||||
GK_ATOM(_moz_math_columnalign_, "_moz-math-columnalign")
|
|
||||||
GK_ATOM(_moz_math_columnline_, "_moz-math-columnline")
|
|
||||||
GK_ATOM(_moz_math_rowalign_, "_moz-math-rowalign")
|
|
||||||
GK_ATOM(_moz_math_rowline_, "_moz-math-rowline")
|
|
||||||
|
|
||||||
GK_ATOM(abs_, "abs")
|
GK_ATOM(abs_, "abs")
|
||||||
GK_ATOM(accent_, "accent")
|
GK_ATOM(accent_, "accent")
|
||||||
GK_ATOM(accentunder_, "accentunder")
|
GK_ATOM(accentunder_, "accentunder")
|
||||||
|
|
|
@ -789,8 +789,11 @@ nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
AutoPushJSContext cx(context->GetNativeContext());
|
|
||||||
JS::Rooted<JSObject*> global(cx, unrootedGlobal);
|
JSContext* unpushedCx = context->GetNativeContext();
|
||||||
|
JSAutoRequest ar(unpushedCx);
|
||||||
|
JS::Rooted<JSObject*> global(unpushedCx, unrootedGlobal);
|
||||||
|
AutoPushJSContext cx(unpushedCx);
|
||||||
|
|
||||||
JS::CompileOptions options(cx);
|
JS::CompileOptions options(cx);
|
||||||
FillCompileOptionsForRequest(aRequest, global, &options);
|
FillCompileOptionsForRequest(aRequest, global, &options);
|
||||||
|
@ -1002,8 +1005,11 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
AutoPushJSContext cx(context->GetNativeContext());
|
|
||||||
JS::Rooted<JSObject*> global(cx, unrootedGlobal);
|
JSContext* unpushedCx = context->GetNativeContext();
|
||||||
|
JSAutoRequest ar(unpushedCx);
|
||||||
|
JS::Rooted<JSObject*> global(unpushedCx, unrootedGlobal);
|
||||||
|
AutoPushJSContext cx(unpushedCx);
|
||||||
|
|
||||||
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
|
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
|
||||||
context->SetProcessingScriptTag(true);
|
context->SetProcessingScriptTag(true);
|
||||||
|
|
|
@ -1890,7 +1890,7 @@ CanvasRenderingContext2D::Arc(double x, double y, double r,
|
||||||
|
|
||||||
EnsureWritablePath();
|
EnsureWritablePath();
|
||||||
|
|
||||||
ArcToBezier(this, Point(x, y), r, startAngle, endAngle, anticlockwise);
|
ArcToBezier(this, Point(x, y), Size(r, r), startAngle, endAngle, anticlockwise);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -1347,7 +1347,9 @@ nsEventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc)
|
||||||
// XXX sXBL/XBL2 issue -- do we really want the owner here? What
|
// XXX sXBL/XBL2 issue -- do we really want the owner here? What
|
||||||
// if that's the XBL document?
|
// if that's the XBL document?
|
||||||
doc = node->OwnerDoc();
|
doc = node->OwnerDoc();
|
||||||
MOZ_ASSERT(!doc->IsLoadedAsData(), "Should not get in here at all");
|
if (doc->IsLoadedAsData()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// We want to allow compiling an event handler even in an unloaded
|
// We want to allow compiling an event handler even in an unloaded
|
||||||
// document, so use GetScopeObject here, not GetScriptHandlingObject.
|
// document, so use GetScopeObject here, not GetScriptHandlingObject.
|
||||||
|
|
|
@ -84,6 +84,7 @@ skip-if = true # Disabled due to timeouts.
|
||||||
[test_bug855741.html]
|
[test_bug855741.html]
|
||||||
[test_bug864040.html]
|
[test_bug864040.html]
|
||||||
[test_bug930374-content.html]
|
[test_bug930374-content.html]
|
||||||
|
[test_bug944847.html]
|
||||||
skip-if = toolkit == "gonk"
|
skip-if = toolkit == "gonk"
|
||||||
[test_clickevent_on_input.html]
|
[test_clickevent_on_input.html]
|
||||||
[test_continuous_wheel_events.html]
|
[test_continuous_wheel_events.html]
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=944847
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug 944847</title>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
/** Test for Bug 944847 **/
|
||||||
|
|
||||||
|
var e1 = document.createElement("div");
|
||||||
|
is(e1.onclick, null);
|
||||||
|
e1.setAttribute("onclick", "");
|
||||||
|
isnot(e1.onclick, null);
|
||||||
|
|
||||||
|
var e2 = document.implementation.createHTMLDocument(null, null).createElement("div");
|
||||||
|
is(e2.onclick, null);
|
||||||
|
e2.setAttribute("onclick", "");
|
||||||
|
is(e2.onclick, null);
|
||||||
|
|
||||||
|
var e3 = document.createElement("div");
|
||||||
|
is(e3.onclick, null);
|
||||||
|
e3.setAttribute("onclick", "");
|
||||||
|
e2.ownerDocument.adoptNode(e3);
|
||||||
|
is(e3.onclick, null);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944847">Mozilla Bug 944847</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -40,7 +40,7 @@ void VideoFrameContainer::SetCurrentFrame(const gfxIntSize& aIntrinsicSize,
|
||||||
mIntrinsicSizeChanged = true;
|
mIntrinsicSizeChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
gfxIntSize oldFrameSize = mImageContainer->GetCurrentSize();
|
gfx::IntSize oldFrameSize = mImageContainer->GetCurrentSize();
|
||||||
TimeStamp lastPaintTime = mImageContainer->GetPaintTime();
|
TimeStamp lastPaintTime = mImageContainer->GetPaintTime();
|
||||||
if (!lastPaintTime.IsNull() && !mPaintTarget.IsNull()) {
|
if (!lastPaintTime.IsNull() && !mPaintTarget.IsNull()) {
|
||||||
mPaintDelay = lastPaintTime - mPaintTarget;
|
mPaintDelay = lastPaintTime - mPaintTarget;
|
||||||
|
@ -57,7 +57,7 @@ void VideoFrameContainer::SetCurrentFrame(const gfxIntSize& aIntrinsicSize,
|
||||||
mImageContainer->UnlockCurrentImage();
|
mImageContainer->UnlockCurrentImage();
|
||||||
|
|
||||||
mImageContainer->SetCurrentImage(aImage);
|
mImageContainer->SetCurrentImage(aImage);
|
||||||
gfxIntSize newFrameSize = mImageContainer->GetCurrentSize();
|
gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
|
||||||
if (oldFrameSize != newFrameSize) {
|
if (oldFrameSize != newFrameSize) {
|
||||||
mImageSizeChanged = true;
|
mImageSizeChanged = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@ VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||||
while (!iter.IsEnded()) {
|
while (!iter.IsEnded()) {
|
||||||
VideoChunk chunk = *iter;
|
VideoChunk chunk = *iter;
|
||||||
if (!chunk.IsNull()) {
|
if (!chunk.IsNull()) {
|
||||||
gfxIntSize imgsize = chunk.mFrame.GetImage()->GetSize();
|
gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize();
|
||||||
nsresult rv = Init(imgsize.width, imgsize.height, aTrackRate);
|
nsresult rv = Init(imgsize.width, imgsize.height, aTrackRate);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
|
LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "MediaPluginReader.h"
|
#include "MediaPluginReader.h"
|
||||||
#include "mozilla/TimeStamp.h"
|
#include "mozilla/TimeStamp.h"
|
||||||
#include "mozilla/dom/TimeRanges.h"
|
#include "mozilla/dom/TimeRanges.h"
|
||||||
|
#include "mozilla/gfx/Point.h"
|
||||||
#include "MediaResource.h"
|
#include "MediaResource.h"
|
||||||
#include "VideoUtils.h"
|
#include "VideoUtils.h"
|
||||||
#include "MediaPluginDecoder.h"
|
#include "MediaPluginDecoder.h"
|
||||||
|
@ -172,7 +173,7 @@ bool MediaPluginReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||||
|
|
||||||
nsAutoPtr<VideoData> v;
|
nsAutoPtr<VideoData> v;
|
||||||
if (currentImage) {
|
if (currentImage) {
|
||||||
gfxIntSize frameSize = currentImage->GetSize();
|
gfx::IntSize frameSize = currentImage->GetSize();
|
||||||
if (frameSize.width != mInitialFrame.width ||
|
if (frameSize.width != mInitialFrame.width ||
|
||||||
frameSize.height != mInitialFrame.height) {
|
frameSize.height != mInitialFrame.height) {
|
||||||
// Frame size is different from what the container reports. This is legal,
|
// Frame size is different from what the container reports. This is legal,
|
||||||
|
|
|
@ -174,8 +174,8 @@ AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
|
||||||
uint32_t aLength, float aSampleRate,
|
uint32_t aLength, float aSampleRate,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (aSampleRate < 8000 || aSampleRate > 96000 || !aLength) {
|
if (aSampleRate < 8000 || aSampleRate > 192000 || !aLength || !aNumberOfChannels) {
|
||||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,15 +81,21 @@ addLoadEvent(function() {
|
||||||
|
|
||||||
expectException(function() {
|
expectException(function() {
|
||||||
context.createBuffer(2, 2048, 7999);
|
context.createBuffer(2, 2048, 7999);
|
||||||
}, DOMException.NOT_SUPPORTED_ERR);
|
}, DOMException.INDEX_SIZE_ERR);
|
||||||
expectException(function() {
|
expectException(function() {
|
||||||
context.createBuffer(2, 2048, 96001);
|
context.createBuffer(2, 2048, 192001);
|
||||||
}, DOMException.NOT_SUPPORTED_ERR);
|
}, DOMException.INDEX_SIZE_ERR);
|
||||||
context.createBuffer(2, 2048, 8000); // no exception
|
context.createBuffer(2, 2048, 8000); // no exception
|
||||||
context.createBuffer(2, 2048, 96000); // no exception
|
context.createBuffer(2, 2048, 192000); // no exception
|
||||||
|
context.createBuffer(32, 2048, 48000); // no exception
|
||||||
|
// Null length
|
||||||
expectException(function() {
|
expectException(function() {
|
||||||
context.createBuffer(2, 0, 48000);
|
context.createBuffer(2, 0, 48000);
|
||||||
}, DOMException.NOT_SUPPORTED_ERR);
|
}, DOMException.INDEX_SIZE_ERR);
|
||||||
|
// Null number of channels
|
||||||
|
expectException(function() {
|
||||||
|
context.createBuffer(0, 2048, 48000);
|
||||||
|
}, DOMException.INDEX_SIZE_ERR);
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,6 @@ var gTest = {
|
||||||
|
|
||||||
var source = context.createBufferSource();
|
var source = context.createBufferSource();
|
||||||
source.buffer = buffer;
|
source.buffer = buffer;
|
||||||
|
|
||||||
var sp = context.createScriptProcessor(2048 * 4, 1);
|
|
||||||
source.start(0);
|
source.start(0);
|
||||||
source.loop = true;
|
source.loop = true;
|
||||||
source.loopStart = buffer.duration * 0.25;
|
source.loopStart = buffer.duration * 0.25;
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
<pre id="test">
|
<pre id="test">
|
||||||
<script class="testbody" type="text/javascript">
|
<script class="testbody" type="text/javascript">
|
||||||
|
|
||||||
|
try {
|
||||||
var ctx = new AudioContext();
|
var ctx = new AudioContext();
|
||||||
ctx.createBuffer(0, 1, ctx.sampleRate);
|
ctx.createBuffer(0, 1, ctx.sampleRate);
|
||||||
|
} catch (e) {
|
||||||
ok(true, "The test should not crash during CC");
|
ok(true, "The test should not crash during CC");
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "nsPresContext.h"
|
#include "nsPresContext.h"
|
||||||
#include "gfxImageSurface.h"
|
#include "gfxImageSurface.h"
|
||||||
#include "gfxContext.h"
|
#include "gfxContext.h"
|
||||||
|
#include "gfx2DGlue.h"
|
||||||
#include "ImageContainer.h"
|
#include "ImageContainer.h"
|
||||||
#include "Layers.h"
|
#include "Layers.h"
|
||||||
#include "nsIInterfaceRequestorUtils.h"
|
#include "nsIInterfaceRequestorUtils.h"
|
||||||
|
@ -154,8 +155,8 @@ NotifyPull(MediaStreamGraph*, SourceMediaStream* aSource, mozilla::TrackID aID,
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
// nullptr images are allowed
|
// nullptr images are allowed
|
||||||
if (image) {
|
if (image) {
|
||||||
gfxIntSize size = image->GetSize();
|
gfx::IntSize size = image->GetSize();
|
||||||
segment.AppendFrame(image.forget(), delta, size);
|
segment.AppendFrame(image.forget(), delta, gfx::ThebesIntSize(size));
|
||||||
} else {
|
} else {
|
||||||
segment.AppendFrame(nullptr, delta, gfxIntSize(0,0));
|
segment.AppendFrame(nullptr, delta, gfxIntSize(0,0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,15 @@ public:
|
||||||
return (mIsActive || mIsFrozen);
|
return (mIsActive || mIsFrozen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the animation is active.
|
||||||
|
*
|
||||||
|
* @return true if the animation is active, false otherwise.
|
||||||
|
*/
|
||||||
|
bool IsActive() const {
|
||||||
|
return mIsActive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this animation will replace the passed in result rather than
|
* Indicates if this animation will replace the passed in result rather than
|
||||||
* adding to it. Animations that replace the underlying value may be called
|
* adding to it. Animations that replace the underlying value may be called
|
||||||
|
|
|
@ -27,7 +27,11 @@ public:
|
||||||
explicit nsSMILRepeatCount(double aCount)
|
explicit nsSMILRepeatCount(double aCount)
|
||||||
: mCount(kNotSet) { SetCount(aCount); }
|
: mCount(kNotSet) { SetCount(aCount); }
|
||||||
|
|
||||||
operator double() const { return mCount; }
|
operator double() const {
|
||||||
|
MOZ_ASSERT(IsDefinite(),
|
||||||
|
"Converting indefinite or unset repeat count to double");
|
||||||
|
return mCount;
|
||||||
|
}
|
||||||
bool IsDefinite() const {
|
bool IsDefinite() const {
|
||||||
return mCount != kNotSet && mCount != kIndefinite;
|
return mCount != kNotSet && mCount != kIndefinite;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,8 +112,9 @@ namespace
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
// Helper class: AutoIntervalUpdateBatcher
|
// Helper class: AutoIntervalUpdateBatcher
|
||||||
|
|
||||||
// RAII helper to set the mDeferIntervalUpdates flag on an nsSMILTimedElement
|
// Stack-based helper class to set the mDeferIntervalUpdates flag on an
|
||||||
// and perform the UpdateCurrentInterval when the object is destroyed.
|
// nsSMILTimedElement and perform the UpdateCurrentInterval when the object is
|
||||||
|
// destroyed.
|
||||||
//
|
//
|
||||||
// If several of these objects are allocated on the stack, the update will not
|
// If several of these objects are allocated on the stack, the update will not
|
||||||
// be performed until the last object for a given nsSMILTimedElement is
|
// be performed until the last object for a given nsSMILTimedElement is
|
||||||
|
@ -146,6 +147,31 @@ private:
|
||||||
bool mDidSetFlag;
|
bool mDidSetFlag;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Helper class: AutoIntervalUpdater
|
||||||
|
|
||||||
|
// Stack-based helper class to call UpdateCurrentInterval when it is destroyed
|
||||||
|
// which helps avoid bugs where we forget to call UpdateCurrentInterval in the
|
||||||
|
// case of early returns (e.g. due to parse errors).
|
||||||
|
//
|
||||||
|
// This can be safely used in conjunction with AutoIntervalUpdateBatcher; any
|
||||||
|
// calls to UpdateCurrentInterval made by this class will simply be deferred if
|
||||||
|
// there is an AutoIntervalUpdateBatcher on the stack.
|
||||||
|
class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AutoIntervalUpdater(nsSMILTimedElement& aTimedElement)
|
||||||
|
: mTimedElement(aTimedElement) { }
|
||||||
|
|
||||||
|
~AutoIntervalUpdater()
|
||||||
|
{
|
||||||
|
mTimedElement.UpdateCurrentInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nsSMILTimedElement& mTimedElement;
|
||||||
|
};
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
// Templated helper functions
|
// Templated helper functions
|
||||||
|
|
||||||
|
@ -667,14 +693,26 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
|
||||||
NS_ASSERTION(aContainerTime >= beginTime,
|
NS_ASSERTION(aContainerTime >= beginTime,
|
||||||
"Sample time should not precede current interval");
|
"Sample time should not precede current interval");
|
||||||
nsSMILTime activeTime = aContainerTime - beginTime;
|
nsSMILTime activeTime = aContainerTime - beginTime;
|
||||||
|
|
||||||
|
// The 'min' attribute can cause the active interval to be longer than
|
||||||
|
// the 'repeating interval'.
|
||||||
|
// In that extended period we apply the fill mode.
|
||||||
|
if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) {
|
||||||
|
if (mClient && mClient->IsActive()) {
|
||||||
|
mClient->Inactivate(mFillMode == FILL_FREEZE);
|
||||||
|
}
|
||||||
|
SampleFillValue();
|
||||||
|
} else {
|
||||||
SampleSimpleTime(activeTime);
|
SampleSimpleTime(activeTime);
|
||||||
|
|
||||||
// We register our repeat times as milestones (except when we're
|
// We register our repeat times as milestones (except when we're
|
||||||
// seeking) so we should get a sample at exactly the time we repeat.
|
// seeking) so we should get a sample at exactly the time we repeat.
|
||||||
// (And even when we are seeking we want to update
|
// (And even when we are seeking we want to update
|
||||||
// mCurrentRepeatIteration so we do that first before testing the seek
|
// mCurrentRepeatIteration so we do that first before testing the
|
||||||
// state.)
|
// seek state.)
|
||||||
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
|
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
|
||||||
if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
|
if (
|
||||||
|
ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
|
||||||
mCurrentRepeatIteration != prevRepeatIteration &&
|
mCurrentRepeatIteration != prevRepeatIteration &&
|
||||||
mCurrentRepeatIteration &&
|
mCurrentRepeatIteration &&
|
||||||
mSeekState == SEEK_NOT_SEEKING) {
|
mSeekState == SEEK_NOT_SEEKING) {
|
||||||
|
@ -683,6 +721,7 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_POSTACTIVE:
|
case STATE_POSTACTIVE:
|
||||||
|
@ -905,8 +944,10 @@ nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove)
|
||||||
nsresult
|
nsresult
|
||||||
nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
|
nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
|
||||||
{
|
{
|
||||||
nsSMILTimeValue duration;
|
// Update the current interval before returning
|
||||||
|
AutoIntervalUpdater updater(*this);
|
||||||
|
|
||||||
|
nsSMILTimeValue duration;
|
||||||
const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec);
|
const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec);
|
||||||
|
|
||||||
// SVG-specific: "For SVG's animation elements, if "media" is specified, the
|
// SVG-specific: "For SVG's animation elements, if "media" is specified, the
|
||||||
|
@ -926,7 +967,6 @@ nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
|
||||||
"Setting unresolved simple duration");
|
"Setting unresolved simple duration");
|
||||||
|
|
||||||
mSimpleDur = duration;
|
mSimpleDur = duration;
|
||||||
UpdateCurrentInterval();
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -941,8 +981,10 @@ nsSMILTimedElement::UnsetSimpleDuration()
|
||||||
nsresult
|
nsresult
|
||||||
nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
|
nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
|
||||||
{
|
{
|
||||||
nsSMILTimeValue duration;
|
// Update the current interval before returning
|
||||||
|
AutoIntervalUpdater updater(*this);
|
||||||
|
|
||||||
|
nsSMILTimeValue duration;
|
||||||
const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec);
|
const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec);
|
||||||
|
|
||||||
if (min.EqualsLiteral("media")) {
|
if (min.EqualsLiteral("media")) {
|
||||||
|
@ -957,7 +999,6 @@ nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
|
||||||
NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration");
|
NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration");
|
||||||
|
|
||||||
mMin = duration;
|
mMin = duration;
|
||||||
UpdateCurrentInterval();
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -972,8 +1013,10 @@ nsSMILTimedElement::UnsetMin()
|
||||||
nsresult
|
nsresult
|
||||||
nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
|
nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
|
||||||
{
|
{
|
||||||
nsSMILTimeValue duration;
|
// Update the current interval before returning
|
||||||
|
AutoIntervalUpdater updater(*this);
|
||||||
|
|
||||||
|
nsSMILTimeValue duration;
|
||||||
const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec);
|
const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec);
|
||||||
|
|
||||||
if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) {
|
if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) {
|
||||||
|
@ -988,7 +1031,6 @@ nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
mMax = duration;
|
mMax = duration;
|
||||||
UpdateCurrentInterval();
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -1023,15 +1065,16 @@ nsSMILTimedElement::UnsetRestart()
|
||||||
nsresult
|
nsresult
|
||||||
nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec)
|
nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec)
|
||||||
{
|
{
|
||||||
|
// Update the current interval before returning
|
||||||
|
AutoIntervalUpdater updater(*this);
|
||||||
|
|
||||||
nsSMILRepeatCount newRepeatCount;
|
nsSMILRepeatCount newRepeatCount;
|
||||||
|
|
||||||
if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) {
|
if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) {
|
||||||
mRepeatCount = newRepeatCount;
|
mRepeatCount = newRepeatCount;
|
||||||
UpdateCurrentInterval();
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
mRepeatCount.Unset();
|
mRepeatCount.Unset();
|
||||||
UpdateCurrentInterval();
|
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,6 +1088,9 @@ nsSMILTimedElement::UnsetRepeatCount()
|
||||||
nsresult
|
nsresult
|
||||||
nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
|
nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
|
||||||
{
|
{
|
||||||
|
// Update the current interval before returning
|
||||||
|
AutoIntervalUpdater updater(*this);
|
||||||
|
|
||||||
nsSMILTimeValue duration;
|
nsSMILTimeValue duration;
|
||||||
|
|
||||||
const nsAString& repeatDur =
|
const nsAString& repeatDur =
|
||||||
|
@ -1060,7 +1106,6 @@ nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
mRepeatDur = duration;
|
mRepeatDur = duration;
|
||||||
UpdateCurrentInterval();
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -1084,12 +1129,8 @@ nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec)
|
||||||
? nsSMILFillMode(temp.GetEnumValue())
|
? nsSMILFillMode(temp.GetEnumValue())
|
||||||
: FILL_REMOVE;
|
: FILL_REMOVE;
|
||||||
|
|
||||||
// Check if we're in a fill-able state: i.e. we've played at least one
|
// Update fill mode of client
|
||||||
// interval and are now between intervals or at the end of all intervals
|
if (mFillMode != previousFillMode && HasClientInFillRange()) {
|
||||||
bool isFillable = HasPlayed() &&
|
|
||||||
(mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE);
|
|
||||||
|
|
||||||
if (mClient && mFillMode != previousFillMode && isFillable) {
|
|
||||||
mClient->Inactivate(mFillMode == FILL_FREEZE);
|
mClient->Inactivate(mFillMode == FILL_FREEZE);
|
||||||
SampleFillValue();
|
SampleFillValue();
|
||||||
}
|
}
|
||||||
|
@ -1102,10 +1143,10 @@ nsSMILTimedElement::UnsetFillMode()
|
||||||
{
|
{
|
||||||
uint16_t previousFillMode = mFillMode;
|
uint16_t previousFillMode = mFillMode;
|
||||||
mFillMode = FILL_REMOVE;
|
mFillMode = FILL_REMOVE;
|
||||||
if ((mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) &&
|
if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) {
|
||||||
previousFillMode == FILL_FREEZE && mClient && HasPlayed())
|
|
||||||
mClient->Inactivate(false);
|
mClient->Inactivate(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent)
|
nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent)
|
||||||
|
@ -1803,11 +1844,7 @@ nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
|
||||||
NS_ABORT_IF_FALSE(aBegin.IsDefinite(),
|
NS_ABORT_IF_FALSE(aBegin.IsDefinite(),
|
||||||
"Indefinite or unresolved begin time in CalcActiveEnd");
|
"Indefinite or unresolved begin time in CalcActiveEnd");
|
||||||
|
|
||||||
if (mRepeatDur.IsIndefinite()) {
|
|
||||||
result.SetIndefinite();
|
|
||||||
} else {
|
|
||||||
result = GetRepeatDuration();
|
result = GetRepeatDuration();
|
||||||
}
|
|
||||||
|
|
||||||
if (aEnd.IsDefinite()) {
|
if (aEnd.IsDefinite()) {
|
||||||
nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis();
|
nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis();
|
||||||
|
@ -1832,29 +1869,25 @@ nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
|
||||||
nsSMILTimeValue
|
nsSMILTimeValue
|
||||||
nsSMILTimedElement::GetRepeatDuration() const
|
nsSMILTimedElement::GetRepeatDuration() const
|
||||||
{
|
{
|
||||||
nsSMILTimeValue result;
|
nsSMILTimeValue multipliedDuration;
|
||||||
|
if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
|
||||||
if (mRepeatCount.IsDefinite() && mRepeatDur.IsDefinite()) {
|
multipliedDuration.SetMillis(
|
||||||
if (mSimpleDur.IsDefinite()) {
|
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis())));
|
||||||
nsSMILTime activeDur =
|
|
||||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()));
|
|
||||||
result.SetMillis(std::min(activeDur, mRepeatDur.GetMillis()));
|
|
||||||
} else {
|
} else {
|
||||||
result = mRepeatDur;
|
multipliedDuration.SetIndefinite();
|
||||||
}
|
|
||||||
} else if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
|
|
||||||
nsSMILTime activeDur =
|
|
||||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()));
|
|
||||||
result.SetMillis(activeDur);
|
|
||||||
} else if (mRepeatDur.IsDefinite()) {
|
|
||||||
result = mRepeatDur;
|
|
||||||
} else if (mRepeatCount.IsIndefinite()) {
|
|
||||||
result.SetIndefinite();
|
|
||||||
} else {
|
|
||||||
result = mSimpleDur;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
nsSMILTimeValue repeatDuration;
|
||||||
|
|
||||||
|
if (mRepeatDur.IsResolved()) {
|
||||||
|
repeatDuration = std::min(multipliedDuration, mRepeatDur);
|
||||||
|
} else if (mRepeatCount.IsSet()) {
|
||||||
|
repeatDuration = multipliedDuration;
|
||||||
|
} else {
|
||||||
|
repeatDuration = mSimpleDur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repeatDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsSMILTimeValue
|
nsSMILTimeValue
|
||||||
|
@ -1873,8 +1906,7 @@ nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const
|
||||||
if (aDuration > mMax) {
|
if (aDuration > mMax) {
|
||||||
result = mMax;
|
result = mMax;
|
||||||
} else if (aDuration < mMin) {
|
} else if (aDuration < mMin) {
|
||||||
nsSMILTimeValue repeatDur = GetRepeatDuration();
|
result = mMin;
|
||||||
result = mMin > repeatDur ? repeatDur : mMin;
|
|
||||||
} else {
|
} else {
|
||||||
result = aDuration;
|
result = aDuration;
|
||||||
}
|
}
|
||||||
|
@ -2068,6 +2100,9 @@ nsSMILTimedElement::SampleFillValue()
|
||||||
if (mFillMode != FILL_FREEZE || !mClient)
|
if (mFillMode != FILL_FREEZE || !mClient)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
nsSMILTime activeTime;
|
||||||
|
|
||||||
|
if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) {
|
||||||
const nsSMILInterval* prevInterval = GetPreviousInterval();
|
const nsSMILInterval* prevInterval = GetPreviousInterval();
|
||||||
NS_ABORT_IF_FALSE(prevInterval,
|
NS_ABORT_IF_FALSE(prevInterval,
|
||||||
"Attempting to sample fill value but there is no previous interval");
|
"Attempting to sample fill value but there is no previous interval");
|
||||||
|
@ -2076,9 +2111,25 @@ nsSMILTimedElement::SampleFillValue()
|
||||||
"Attempting to sample fill value but the endpoint of the previous "
|
"Attempting to sample fill value but the endpoint of the previous "
|
||||||
"interval is not resolved and fixed");
|
"interval is not resolved and fixed");
|
||||||
|
|
||||||
nsSMILTime activeTime = prevInterval->End()->Time().GetMillis() -
|
activeTime = prevInterval->End()->Time().GetMillis() -
|
||||||
prevInterval->Begin()->Time().GetMillis();
|
prevInterval->Begin()->Time().GetMillis();
|
||||||
|
|
||||||
|
// If the interval's repeat duration was shorter than its active duration,
|
||||||
|
// use the end of the repeat duration to determine the frozen animation's
|
||||||
|
// state.
|
||||||
|
nsSMILTimeValue repeatDuration = GetRepeatDuration();
|
||||||
|
if (repeatDuration.IsDefinite()) {
|
||||||
|
activeTime = std::min(repeatDuration.GetMillis(), activeTime);
|
||||||
|
}
|
||||||
|
} else if (mElementState == STATE_ACTIVE) {
|
||||||
|
// If we are being asked to sample the fill value while active we *must*
|
||||||
|
// have a repeat duration shorter than the active duration so use that.
|
||||||
|
MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
|
||||||
|
"Attempting to sample fill value of an active animation with "
|
||||||
|
"an indefinite repeat duration");
|
||||||
|
activeTime = GetRepeatDuration().GetMillis();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t repeatIteration;
|
uint32_t repeatIteration;
|
||||||
nsSMILTime simpleTime =
|
nsSMILTime simpleTime =
|
||||||
ActiveTimeToSimpleTime(activeTime, repeatIteration);
|
ActiveTimeToSimpleTime(activeTime, repeatIteration);
|
||||||
|
@ -2173,8 +2224,13 @@ nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
|
||||||
// Work out what comes next: the interval end or the next repeat iteration
|
// Work out what comes next: the interval end or the next repeat iteration
|
||||||
nsSMILTimeValue nextRepeat;
|
nsSMILTimeValue nextRepeat;
|
||||||
if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
|
if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
|
||||||
|
nsSMILTime nextRepeatActiveTime =
|
||||||
|
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis();
|
||||||
|
// Check that the repeat fits within the repeat duration
|
||||||
|
if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) {
|
||||||
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
|
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
|
||||||
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis());
|
nextRepeatActiveTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nsSMILTimeValue nextMilestone =
|
nsSMILTimeValue nextMilestone =
|
||||||
std::min(mCurrentInterval->End()->Time(), nextRepeat);
|
std::min(mCurrentInterval->End()->Time(), nextRepeat);
|
||||||
|
@ -2283,6 +2339,15 @@ nsSMILTimedElement::GetPreviousInterval() const
|
||||||
: mOldIntervals[mOldIntervals.Length()-1].get();
|
: mOldIntervals[mOldIntervals.Length()-1].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
nsSMILTimedElement::HasClientInFillRange() const
|
||||||
|
{
|
||||||
|
// Returns true if we have a client that is in the range where it will fill
|
||||||
|
return mClient &&
|
||||||
|
((mElementState != STATE_ACTIVE && HasPlayed()) ||
|
||||||
|
(mElementState == STATE_ACTIVE && !mClient->IsActive()));
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
nsSMILTimedElement::EndHasEventConditions() const
|
nsSMILTimedElement::EndHasEventConditions() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -512,6 +512,7 @@ protected:
|
||||||
const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
|
const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
|
||||||
const nsSMILInterval* GetPreviousInterval() const;
|
const nsSMILInterval* GetPreviousInterval() const;
|
||||||
bool HasPlayed() const { return !mOldIntervals.IsEmpty(); }
|
bool HasPlayed() const { return !mOldIntervals.IsEmpty(); }
|
||||||
|
bool HasClientInFillRange() const;
|
||||||
bool EndHasEventConditions() const;
|
bool EndHasEventConditions() const;
|
||||||
bool AreEndTimesDependentOn(
|
bool AreEndTimesDependentOn(
|
||||||
const nsSMILInstanceTime* aBase) const;
|
const nsSMILInstanceTime* aBase) const;
|
||||||
|
@ -615,6 +616,9 @@ protected:
|
||||||
bool mDoDeferredUpdate; // Set if an update to the current interval was
|
bool mDoDeferredUpdate; // Set if an update to the current interval was
|
||||||
// requested while mDeferIntervalUpdates was set
|
// requested while mDeferIntervalUpdates was set
|
||||||
|
|
||||||
|
// Stack-based helper class to call UpdateCurrentInterval when it is destroyed
|
||||||
|
class AutoIntervalUpdater;
|
||||||
|
|
||||||
// Recursion depth checking
|
// Recursion depth checking
|
||||||
uint8_t mDeleteCount;
|
uint8_t mDeleteCount;
|
||||||
uint8_t mUpdateIntervalRecursionDepth;
|
uint8_t mUpdateIntervalRecursionDepth;
|
||||||
|
|
|
@ -32,12 +32,15 @@ support-files =
|
||||||
[test_smilGetSimpleDuration.xhtml]
|
[test_smilGetSimpleDuration.xhtml]
|
||||||
[test_smilGetStartTime.xhtml]
|
[test_smilGetStartTime.xhtml]
|
||||||
[test_smilHyperlinking.xhtml]
|
[test_smilHyperlinking.xhtml]
|
||||||
|
[test_smilInvalidValues.html]
|
||||||
[test_smilKeySplines.xhtml]
|
[test_smilKeySplines.xhtml]
|
||||||
[test_smilKeyTimes.xhtml]
|
[test_smilKeyTimes.xhtml]
|
||||||
[test_smilKeyTimesPacedMode.xhtml]
|
[test_smilKeyTimesPacedMode.xhtml]
|
||||||
[test_smilMappedAttrFromBy.xhtml]
|
[test_smilMappedAttrFromBy.xhtml]
|
||||||
[test_smilMappedAttrFromTo.xhtml]
|
[test_smilMappedAttrFromTo.xhtml]
|
||||||
[test_smilMappedAttrPaced.xhtml]
|
[test_smilMappedAttrPaced.xhtml]
|
||||||
|
[test_smilMinTiming.html]
|
||||||
|
[test_smilRepeatDuration.html]
|
||||||
[test_smilRepeatTiming.xhtml]
|
[test_smilRepeatTiming.xhtml]
|
||||||
[test_smilReset.xhtml]
|
[test_smilReset.xhtml]
|
||||||
[test_smilRestart.xhtml]
|
[test_smilRestart.xhtml]
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=941315
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test invalid values cause the model to be updated (bug 941315)</title>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=941315">Mozilla Bug 941315</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
<svg width="100%" height="1" onload="this.pauseAnimations()">
|
||||||
|
<rect>
|
||||||
|
<animate id="a" dur="100s"/>
|
||||||
|
<animate id="b" dur="5s" begin="a.end"/>
|
||||||
|
</rect>
|
||||||
|
<circle cx="-100" cy="20" r="15" fill="blue" id="circle"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
var a = $('a'),
|
||||||
|
b = $('b');
|
||||||
|
|
||||||
|
// Animation doesn't start until onload
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
window.addEventListener("load", runTests, false);
|
||||||
|
|
||||||
|
// Make testing getStartTime easier
|
||||||
|
SVGAnimationElement.prototype.safeGetStartTime = function() {
|
||||||
|
try {
|
||||||
|
return this.getStartTime();
|
||||||
|
} catch(e) {
|
||||||
|
if (e.name == "InvalidStateError" &&
|
||||||
|
e.code == DOMException.INVALID_STATE_ERR) {
|
||||||
|
return 'none';
|
||||||
|
} else {
|
||||||
|
ok(false, "Unexpected exception: " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
[testSimpleDuration, testMin, testMax, testRepeatDur, testRepeatCount]
|
||||||
|
.forEach(function(test) {
|
||||||
|
ise(b.getStartTime(), 100, "initial state before running " + test.name);
|
||||||
|
test();
|
||||||
|
ise(b.getStartTime(), 100, "final state after running " + test.name);
|
||||||
|
});
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSimpleDuration() {
|
||||||
|
// Verify a valid value updates as expected
|
||||||
|
a.setAttribute("dur", "50s");
|
||||||
|
ise(b.safeGetStartTime(), 50, "valid simple duration");
|
||||||
|
|
||||||
|
// Check an invalid value also causes the model to be updated
|
||||||
|
a.setAttribute("dur", "abc"); // -> indefinite
|
||||||
|
ise(b.safeGetStartTime(), "none", "invalid simple duration");
|
||||||
|
|
||||||
|
// Restore state
|
||||||
|
a.setAttribute("dur", "100s");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMin() {
|
||||||
|
a.setAttribute("min", "200s");
|
||||||
|
ise(b.safeGetStartTime(), 200, "valid min duration");
|
||||||
|
|
||||||
|
a.setAttribute("min", "abc"); // -> indefinite
|
||||||
|
ise(b.safeGetStartTime(), 100, "invalid min duration");
|
||||||
|
|
||||||
|
a.removeAttribute("min");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMax() {
|
||||||
|
a.setAttribute("max", "50s");
|
||||||
|
ise(b.safeGetStartTime(), 50, "valid max duration");
|
||||||
|
|
||||||
|
a.setAttribute("max", "abc"); // -> indefinite
|
||||||
|
ise(b.safeGetStartTime(), 100, "invalid max duration");
|
||||||
|
|
||||||
|
a.removeAttribute("max");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRepeatDur() {
|
||||||
|
a.setAttribute("repeatDur", "200s");
|
||||||
|
ise(b.safeGetStartTime(), 200, "valid repeatDur duration");
|
||||||
|
|
||||||
|
a.setAttribute("repeatDur", "abc"); // -> indefinite
|
||||||
|
ise(b.safeGetStartTime(), 100, "invalid repeatDur duration");
|
||||||
|
|
||||||
|
a.removeAttribute("repeatDur");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRepeatCount() {
|
||||||
|
a.setAttribute("repeatCount", "2");
|
||||||
|
ise(b.safeGetStartTime(), 200, "valid repeatCount duration");
|
||||||
|
|
||||||
|
a.setAttribute("repeatCount", "abc"); // -> indefinite
|
||||||
|
ise(b.safeGetStartTime(), 100, "invalid repeatCount duration");
|
||||||
|
|
||||||
|
a.removeAttribute("repeatCount");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,93 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=948245
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug 948245</title>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=948245">Mozilla Bug 948245</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
<svg id="svg" onload="this.pauseAnimations()">
|
||||||
|
<rect fill="red" id="rect" x="0">
|
||||||
|
<animate attributeName="x" to="100" id="animation" dur="100s" min="200s"/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
// The 'min' attribute introduces a kind of additional state into the SMIL
|
||||||
|
// model. If the 'min' attribute extends the active duration, the additional
|
||||||
|
// time between the amount of time the animation normally runs for (called the
|
||||||
|
// 'repeat duration') and the extended active duration is filled using the
|
||||||
|
// fill mode.
|
||||||
|
//
|
||||||
|
// Below we refer to this period of time between the end of the repeat
|
||||||
|
// duration and the end of the active duration as the 'extended period'.
|
||||||
|
//
|
||||||
|
// This test verifies that as we jump in and out of these states we produce
|
||||||
|
// the correct values.
|
||||||
|
//
|
||||||
|
// The test animation above produces an active interval that is longer than
|
||||||
|
// the 'repeating duration' of the animation.
|
||||||
|
var rect = $('rect'),
|
||||||
|
animation = $('animation');
|
||||||
|
|
||||||
|
// Animation doesn't start until onload
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
window.addEventListener("load", runTests, false);
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
ok($('svg').animationsPaused(), "should be paused by <svg> load handler");
|
||||||
|
|
||||||
|
// In the extended period (t=150s) we should not be animating or filling
|
||||||
|
// since the default fill mode is "none".
|
||||||
|
animation.ownerSVGElement.setCurrentTime(150);
|
||||||
|
ise(rect.x.animVal.value, 0,
|
||||||
|
"Shouldn't fill in extended period with fill='none'");
|
||||||
|
|
||||||
|
// If we set the fill mode we should start filling.
|
||||||
|
animation.setAttribute("fill", "freeze");
|
||||||
|
ise(rect.x.animVal.value, 100,
|
||||||
|
"Should fill in extended period with fill='freeze'");
|
||||||
|
|
||||||
|
// If we unset the fill attribute we should stop filling.
|
||||||
|
animation.removeAttribute("fill");
|
||||||
|
ise(rect.x.animVal.value, 0, "Shouldn't fill after unsetting fill");
|
||||||
|
|
||||||
|
// If we jump back into the repeated interval (at t=50s) we should be
|
||||||
|
// animating.
|
||||||
|
animation.ownerSVGElement.setCurrentTime(50);
|
||||||
|
ise(rect.x.animVal.value, 50, "Should be active in repeating interval");
|
||||||
|
|
||||||
|
// If we jump to the boundary at the start of the extended period we should
|
||||||
|
// not be filling (since we removed the fill attribute above).
|
||||||
|
animation.ownerSVGElement.setCurrentTime(100);
|
||||||
|
ise(rect.x.animVal.value, 0,
|
||||||
|
"Shouldn't fill after seeking to boundary of extended period");
|
||||||
|
|
||||||
|
// If we apply a fill mode at this boundary point we should do regular fill
|
||||||
|
// behavior of using the last value in the interpolation range.
|
||||||
|
animation.setAttribute("fill", "freeze");
|
||||||
|
ise(rect.x.animVal.value, 100,
|
||||||
|
"Should fill at boundary to extended period");
|
||||||
|
|
||||||
|
// Check that if we seek past the interval we fill with the value at the end
|
||||||
|
// of the _repeat_duration_ not the value at the end of the
|
||||||
|
// _active_duration_.
|
||||||
|
animation.setAttribute("repeatCount", "1.5");
|
||||||
|
animation.ownerSVGElement.setCurrentTime(225);
|
||||||
|
ise(rect.x.animVal.value, 50,
|
||||||
|
"Should fill with the end of the repeat duration value");
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,139 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=948245
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for repeat duration calculation (Bug 948245)</title>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=948245">Mozilla Bug 948245</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
<svg id="svg" onload="this.pauseAnimations()">
|
||||||
|
<rect>
|
||||||
|
<animate id="a"/>
|
||||||
|
<animate id="b" begin="a.end"/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
// Tests the calculation of the repeat duration which is one of the steps
|
||||||
|
// towards determining the active duration.
|
||||||
|
//
|
||||||
|
// The repeat duration is determined by the following three attributes:
|
||||||
|
//
|
||||||
|
// dur: may be definite (e.g. '2s') or 'indefinite' (the default)
|
||||||
|
// repeatCount: may be definite (e.g. '2.5'), 'indefinite', or not set
|
||||||
|
// repeatDur: may be definite (e.g. '5s'), 'indefinite', or not set
|
||||||
|
//
|
||||||
|
// That leaves 18 combinations to test.
|
||||||
|
var testCases =
|
||||||
|
[
|
||||||
|
// 1. repeatDur: definite, repeatCount: definite, dur: definite
|
||||||
|
// (Two test cases here to ensure we get the minimum)
|
||||||
|
{ repeatDur: 15, repeatCount: 2, dur: 10, result: 15 },
|
||||||
|
{ repeatDur: 25, repeatCount: 2, dur: 10, result: 20 },
|
||||||
|
// 2. repeatDur: indefinite, repeatCount: definite, dur: definite
|
||||||
|
{ repeatDur: 'indefinite', repeatCount: 2, dur: 10, result: 20 },
|
||||||
|
// 3. repeatDur: not set, repeatCount: definite, dur: definite
|
||||||
|
{ repeatCount: 2, dur: 10, result: 20 },
|
||||||
|
// 4. repeatDur: definite, repeatCount: indefinite, dur: definite
|
||||||
|
{ repeatDur: 15, repeatCount: 'indefinite', dur: 10, result: 15 },
|
||||||
|
// 5. repeatDur: indefinite, repeatCount: indefinite, dur: definite
|
||||||
|
{ repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 10,
|
||||||
|
result: 'indefinite' },
|
||||||
|
// 6. repeatDur: not set, repeatCount: indefinite, dur: definite
|
||||||
|
{ repeatCount: 'indefinite', dur: 10, result: 'indefinite' },
|
||||||
|
// 7. repeatDur: definite, repeatCount: not set, dur: definite
|
||||||
|
{ repeatDur: 15, dur: 10, result: 15 },
|
||||||
|
// 8. repeatDur: indefinite, repeatCount: not set, dur: definite
|
||||||
|
{ repeatDur: 'indefinite', dur: 10, result: 'indefinite' },
|
||||||
|
// 9. repeatDur: not set, repeatCount: not set, dur: definite
|
||||||
|
{ dur: 10, result: 10 },
|
||||||
|
// 10. repeatDur: definite, repeatCount: definite, dur: indefinite
|
||||||
|
{ repeatDur: 15, repeatCount: 2, dur: 'indefinite', result: 15 },
|
||||||
|
// 11. repeatDur: indefinite, repeatCount: definite, dur: indefinite
|
||||||
|
{ repeatDur: 'indefinite', repeatCount: 2, dur: 'indefinite',
|
||||||
|
result: 'indefinite' },
|
||||||
|
// 12. repeatDur: not set, repeatCount: definite, dur: indefinite
|
||||||
|
{ repeatCount: 2, dur: 'indefinite', result: 'indefinite' },
|
||||||
|
// 13. repeatDur: definite, repeatCount: indefinite, dur: indefinite
|
||||||
|
{ repeatDur: 15, repeatCount: 'indefinite', dur: 'indefinite',
|
||||||
|
result: 15 },
|
||||||
|
// 14. repeatDur: indefinite, repeatCount: indefinite, dur: indefinite
|
||||||
|
{ repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 'indefinite',
|
||||||
|
result: 'indefinite' },
|
||||||
|
// 15. repeatDur: not set, repeatCount: indefinite, dur: indefinite
|
||||||
|
{ repeatCount: 'indefinite', dur: 'indefinite', result: 'indefinite' },
|
||||||
|
// 16. repeatDur: definite, repeatCount: not set, dur: indefinite
|
||||||
|
{ repeatDur: 15, dur: 'indefinite', result: 15 },
|
||||||
|
// 17. repeatDur: indefinite, repeatCount: not set, dur: indefinite
|
||||||
|
{ repeatDur: 'indefinite', dur: 'indefinite', result: 'indefinite' },
|
||||||
|
// 18. repeatDur: not set, repeatCount: not set, dur: indefinite
|
||||||
|
{ dur: 'indefinite', result: 'indefinite' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// We can test the repeat duration by setting these attributes on animation
|
||||||
|
// 'a' and checking the start time of 'b' which is defined to start when 'a'
|
||||||
|
// finishes.
|
||||||
|
//
|
||||||
|
// Since 'a' has no end/min/max attributes the end of its active interval
|
||||||
|
// should coincide with the end of its repeat duration.
|
||||||
|
//
|
||||||
|
// Sometimes the repeat duration is defined to be 'indefinite'. In this case
|
||||||
|
// calling getStartTime on b will throw an exception so we need to catch that
|
||||||
|
// exception and translate it to 'indefinite' as follows:
|
||||||
|
function getRepeatDuration() {
|
||||||
|
try {
|
||||||
|
return $('b').getStartTime();
|
||||||
|
} catch(e) {
|
||||||
|
if (e.name == "InvalidStateError" &&
|
||||||
|
e.code == DOMException.INVALID_STATE_ERR) {
|
||||||
|
return 'indefinite';
|
||||||
|
} else {
|
||||||
|
ok(false, "Unexpected exception: " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation doesn't start until onload
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
window.addEventListener("load", runTests, false);
|
||||||
|
|
||||||
|
// Run through each of the test cases
|
||||||
|
function runTests() {
|
||||||
|
ok($('svg').animationsPaused(), "should be paused by <svg> load handler");
|
||||||
|
|
||||||
|
testCases.forEach(function(test) {
|
||||||
|
var a = $('a');
|
||||||
|
|
||||||
|
// Set the attributes
|
||||||
|
var msgPieces = [];
|
||||||
|
[ 'repeatDur', 'repeatCount', 'dur' ].forEach(function(attr) {
|
||||||
|
if (typeof test[attr] != "undefined") {
|
||||||
|
a.setAttribute(attr, test[attr].toString());
|
||||||
|
msgPieces.push(attr + ': ' + test[attr].toString());
|
||||||
|
} else {
|
||||||
|
a.removeAttribute(attr);
|
||||||
|
msgPieces.push(attr + ': <not set>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var msg = msgPieces.join(', ');
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
ise(getRepeatDuration(), test.result, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -95,6 +95,15 @@ SVGEllipseElement::GetLengthInfo()
|
||||||
void
|
void
|
||||||
SVGEllipseElement::ConstructPath(gfxContext *aCtx)
|
SVGEllipseElement::ConstructPath(gfxContext *aCtx)
|
||||||
{
|
{
|
||||||
|
if (!aCtx->IsCairo()) {
|
||||||
|
RefPtr<Path> path = BuildPath();
|
||||||
|
if (path) {
|
||||||
|
gfxPath gfxpath(path);
|
||||||
|
aCtx->SetPath(&gfxpath);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float x, y, rx, ry;
|
float x, y, rx, ry;
|
||||||
|
|
||||||
GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
|
GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
|
||||||
|
@ -116,7 +125,7 @@ SVGEllipseElement::BuildPath()
|
||||||
|
|
||||||
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
|
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
|
||||||
|
|
||||||
AppendEllipseToPath(pathBuilder, Point(x, y), Size(2.0*rx, 2.0*ry));
|
ArcToBezier(pathBuilder.get(), Point(x, y), Size(rx, ry), 0, Float(2*M_PI), false);
|
||||||
|
|
||||||
return pathBuilder->Finish();
|
return pathBuilder->Finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,9 +211,9 @@ SVGFEImageElement::GetPrimitiveDescription(nsSVGFilterInstance* aInstance,
|
||||||
|
|
||||||
nsRefPtr<gfxASurface> currentFrame;
|
nsRefPtr<gfxASurface> currentFrame;
|
||||||
if (imageContainer) {
|
if (imageContainer) {
|
||||||
|
currentFrame =
|
||||||
imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
|
imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||||
imgIContainer::FLAG_SYNC_DECODE,
|
imgIContainer::FLAG_SYNC_DECODE);
|
||||||
getter_AddRefs(currentFrame));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentFrame) {
|
if (!currentFrame) {
|
||||||
|
|
|
@ -1082,28 +1082,7 @@ nsXBLBinding::DoInitJSClass(JSContext *cx, JS::Handle<JSObject*> global,
|
||||||
bool
|
bool
|
||||||
nsXBLBinding::AllowScripts()
|
nsXBLBinding::AllowScripts()
|
||||||
{
|
{
|
||||||
if (!mPrototypeBinding->GetAllowScripts())
|
return mPrototypeBinding->GetAllowScripts();
|
||||||
return false;
|
|
||||||
|
|
||||||
// Nasty hack. Use the JSContext of the bound node, since the
|
|
||||||
// security manager API expects to get the docshell type from
|
|
||||||
// that. But use the nsIPrincipal of our document.
|
|
||||||
nsIScriptSecurityManager* mgr = nsContentUtils::GetSecurityManager();
|
|
||||||
if (!mgr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIDocument* doc = mBoundElement ? mBoundElement->OwnerDoc() : nullptr;
|
|
||||||
if (!doc) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(doc->GetInnerWindow());
|
|
||||||
if (!global || !global->GetGlobalJSObject()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mgr->ScriptAllowed(global->GetGlobalJSObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsXBLBinding*
|
nsXBLBinding*
|
||||||
|
|
|
@ -141,7 +141,7 @@ public:
|
||||||
JS::MutableHandle<JSObject*> aClassObject,
|
JS::MutableHandle<JSObject*> aClassObject,
|
||||||
bool* aNew);
|
bool* aNew);
|
||||||
|
|
||||||
bool AllowScripts(); // XXX make const
|
bool AllowScripts();
|
||||||
|
|
||||||
mozilla::dom::XBLChildrenElement* FindInsertionPointFor(nsIContent* aChild);
|
mozilla::dom::XBLChildrenElement* FindInsertionPointFor(nsIContent* aChild);
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,24 @@ nsXBLDocumentInfo::nsXBLDocumentInfo(nsIDocument* aDocument)
|
||||||
mScriptAccess = allow;
|
mScriptAccess = allow;
|
||||||
}
|
}
|
||||||
mIsChrome = true;
|
mIsChrome = true;
|
||||||
|
} else {
|
||||||
|
// If this binding isn't running with system principal, then it's running
|
||||||
|
// from a remote-XUL whitelisted domain. This is already a not-really-
|
||||||
|
// supported configuration (among other things, we don't use XBL scopes in
|
||||||
|
// that configuration for compatibility reasons). But we should still at
|
||||||
|
// least make an effort to prevent binding code from running if content
|
||||||
|
// script is disabled or if the source domain is blacklisted (since the
|
||||||
|
// source domain for remote XBL must always be the same as the source domain
|
||||||
|
// of the bound content).
|
||||||
|
//
|
||||||
|
// If we just ask the binding document if script is enabled, it will
|
||||||
|
// discover that it has no inner window, and return false. So instead, we
|
||||||
|
// short-circuit the normal compartment-managed script-disabling machinery,
|
||||||
|
// and query the policy for the URI directly.
|
||||||
|
bool allow;
|
||||||
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||||
|
nsresult rv = ssm->PolicyAllowsScript(uri, &allow);
|
||||||
|
mScriptAccess = NS_SUCCEEDED(rv) && allow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
already_AddRefed<nsIDocument> GetDocument()
|
already_AddRefed<nsIDocument> GetDocument()
|
||||||
{ nsCOMPtr<nsIDocument> copy = mDocument; return copy.forget(); }
|
{ nsCOMPtr<nsIDocument> copy = mDocument; return copy.forget(); }
|
||||||
|
|
||||||
bool GetScriptAccess() { return mScriptAccess; }
|
bool GetScriptAccess() const { return mScriptAccess; }
|
||||||
|
|
||||||
nsIURI* DocumentURI() { return mDocument->GetDocumentURI(); }
|
nsIURI* DocumentURI() { return mDocument->GetDocumentURI(); }
|
||||||
|
|
||||||
|
|
|
@ -214,7 +214,7 @@ nsXBLPrototypeBinding::SetBindingElement(nsIContent* aElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
nsXBLPrototypeBinding::GetAllowScripts()
|
nsXBLPrototypeBinding::GetAllowScripts() const
|
||||||
{
|
{
|
||||||
return mXBLDocInfoWeak->GetScriptAccess();
|
return mXBLDocInfoWeak->GetScriptAccess();
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public:
|
||||||
// binding URIs.
|
// binding URIs.
|
||||||
bool CompareBindingURI(nsIURI* aURI) const;
|
bool CompareBindingURI(nsIURI* aURI) const;
|
||||||
|
|
||||||
bool GetAllowScripts();
|
bool GetAllowScripts() const;
|
||||||
|
|
||||||
nsresult BindingAttached(nsIContent* aBoundElement);
|
nsresult BindingAttached(nsIContent* aBoundElement);
|
||||||
nsresult BindingDetached(nsIContent* aBoundElement);
|
nsresult BindingDetached(nsIContent* aBoundElement);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
support-files =
|
||||||
|
file_bug944407.xml
|
||||||
|
|
||||||
[test_bug378518.xul]
|
[test_bug378518.xul]
|
||||||
[test_bug398135.xul]
|
[test_bug398135.xul]
|
||||||
|
@ -6,3 +8,4 @@
|
||||||
[test_bug721452.xul]
|
[test_bug721452.xul]
|
||||||
[test_bug723676.xul]
|
[test_bug723676.xul]
|
||||||
[test_bug772966.xul]
|
[test_bug772966.xul]
|
||||||
|
[test_bug944407.xul]
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="deny" style="-moz-binding: url(file_bug944407.xml#testAllowScript)"></div>
|
||||||
|
<div id="allow" style="-moz-binding: url(chrome://mochitests/content/chrome/content/xbl/test/file_bug944407.xml#testAllowScript)"</div>
|
||||||
|
<script>/* Flush layout with a script tab - see bug 944407 comment 37. */</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<bindings id="testBindings" xmlns="http://www.mozilla.org/xbl"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||||
|
<binding id="testAllowScript">
|
||||||
|
<implementation>
|
||||||
|
<property name="someProp" onget="return 2;" readonly="true"></property>
|
||||||
|
<method name="someMethod"><body> return 3; </body></method>
|
||||||
|
<method name="startTest">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
// Make sure we only get constructed when we're loaded from a domain
|
||||||
|
// with script enabled.
|
||||||
|
is(this.id, 'allow', "XBL should only be bound when the origin of the binding allows scripts");
|
||||||
|
|
||||||
|
var t = this;
|
||||||
|
doFinish = function() {
|
||||||
|
// Take a moment to make sure that other constructors don't run when they shouldn't.
|
||||||
|
if (t.id == 'allow')
|
||||||
|
setTimeout(SpecialPowers.wrap(window.parent).finish, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTestEvent = function(target) {
|
||||||
|
ok(true, 'called event handler');
|
||||||
|
|
||||||
|
// First, dispatch an event to the anonymous content. The event
|
||||||
|
// handlers on the AC should run, but they won't until bug 948000
|
||||||
|
// is fixed. So the check here is a todo().
|
||||||
|
var e = new MouseEvent('click');
|
||||||
|
document.getAnonymousNodes(target)[1].dispatchEvent(e);
|
||||||
|
|
||||||
|
// Now, dispatch a key event to test key handlers and move the test along.
|
||||||
|
var k = document.createEvent('KeyboardEvent');
|
||||||
|
k.initEvent('keyup', true, true);
|
||||||
|
target.dispatchEvent(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the implementation.
|
||||||
|
is(this.someProp, 2, "Properties work");
|
||||||
|
is(this.someMethod(), 3, "Methods work");
|
||||||
|
|
||||||
|
// Kick over to the event handlers. This tests XBL event handlers,
|
||||||
|
// XBL key handlers, and event handlers on anonymous content.
|
||||||
|
this.dispatchEvent(new CustomEvent('testEvent'));
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<constructor>
|
||||||
|
<![CDATA[
|
||||||
|
win = XPCNativeWrapper.unwrap(window);
|
||||||
|
SpecialPowers = win.SpecialPowers;
|
||||||
|
ok = win.ok = SpecialPowers.wrap(window.parent).ok;
|
||||||
|
todo = win.todo = SpecialPowers.wrap(window.parent).todo;
|
||||||
|
is = win.is = SpecialPowers.wrap(window.parent).is;
|
||||||
|
info = win.info = SpecialPowers.wrap(window.parent).info;
|
||||||
|
|
||||||
|
info("Invoked constructor for " + this.id);
|
||||||
|
|
||||||
|
var t = this;
|
||||||
|
window.addEventListener('load', function loadListener() {
|
||||||
|
window.removeEventListener('load', loadListener);
|
||||||
|
// Wait two refresh-driver ticks to make sure that the constructor runs
|
||||||
|
// for both |allow| and |deny| if it's ever going to.
|
||||||
|
//
|
||||||
|
// See bug 944407 comment 37.
|
||||||
|
info("Invoked load listener for " + t.id);
|
||||||
|
window.requestAnimationFrame(function() { window.requestAnimationFrame(t.startTest.bind(t)); });
|
||||||
|
});
|
||||||
|
]]>
|
||||||
|
</constructor>
|
||||||
|
</implementation>
|
||||||
|
<handlers>
|
||||||
|
<handler event="testEvent" action="onTestEvent(this)" allowuntrusted="true"/>
|
||||||
|
<handler event="keyup" action="ok(true, 'called key handler'); doFinish();" allowuntrusted="true"/>
|
||||||
|
</handlers>
|
||||||
|
<content>Anonymous Content<html:div onclick="todo(true, 'called event handler on ac, this needs bug 948000');"></html:div><html:b style="display:none"><children/></html:b></content>
|
||||||
|
</binding>
|
||||||
|
</bindings>
|
|
@ -12,6 +12,8 @@ support-files =
|
||||||
file_bug591198_xbl.xml
|
file_bug591198_xbl.xml
|
||||||
file_bug821850.xhtml
|
file_bug821850.xhtml
|
||||||
file_bug844783.xhtml
|
file_bug844783.xhtml
|
||||||
|
file_bug944407.html
|
||||||
|
file_bug944407.xml
|
||||||
|
|
||||||
[test_bug310107.html]
|
[test_bug310107.html]
|
||||||
[test_bug366770.html]
|
[test_bug366770.html]
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||||
|
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=944407
|
||||||
|
-->
|
||||||
|
<window title="Mozilla Bug 944407"
|
||||||
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||||
|
|
||||||
|
<!-- test results are displayed in the html:body -->
|
||||||
|
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=944407"
|
||||||
|
target="_blank">Mozilla Bug 944407</a>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<!-- test code goes here -->
|
||||||
|
<script type="application/javascript">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
/** Test for XBL bindings with script disabled. **/
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
const Cu = Components.utils;
|
||||||
|
Cu.import('resource://gre/modules/Services.jsm');
|
||||||
|
|
||||||
|
function go() {
|
||||||
|
|
||||||
|
// Disable javascript, and load the frame.
|
||||||
|
function loadFrame() {
|
||||||
|
ok(!Services.prefs.getBoolPref('javascript.enabled'), "Javascript should be disabled");
|
||||||
|
$('ifr').setAttribute('src', 'http://mochi.test:8888/tests/content/xbl/test/file_bug944407.html');
|
||||||
|
}
|
||||||
|
SpecialPowers.pushPrefEnv({ set: [['javascript.enabled', false]] }, loadFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
addLoadEvent(go);
|
||||||
|
]]>
|
||||||
|
</script>
|
||||||
|
<iframe id='ifr' />
|
||||||
|
</window>
|
|
@ -1703,14 +1703,16 @@ Navigator::HasMobileMessageSupport(JSContext* /* unused */, JSObject* aGlobal)
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
bool
|
bool
|
||||||
Navigator::HasTelephonySupport(JSContext* /* unused */, JSObject* aGlobal)
|
Navigator::HasTelephonySupport(JSContext* cx, JSObject* aGlobal)
|
||||||
{
|
{
|
||||||
|
JS::Rooted<JSObject*> global(cx, aGlobal);
|
||||||
|
|
||||||
// First of all, the general pref has to be turned on.
|
// First of all, the general pref has to be turned on.
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
Preferences::GetBool("dom.telephony.enabled", &enabled);
|
Preferences::GetBool("dom.telephony.enabled", &enabled);
|
||||||
NS_ENSURE_TRUE(enabled, false);
|
NS_ENSURE_TRUE(enabled, false);
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
|
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
|
||||||
return win && CheckPermission(win, "telephony");
|
return win && CheckPermission(win, "telephony");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1854,8 +1856,10 @@ bool Navigator::HasInputMethodSupport(JSContext* /* unused */,
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
bool
|
bool
|
||||||
Navigator::HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal)
|
Navigator::HasDataStoreSupport(JSContext* cx, JSObject* aGlobal)
|
||||||
{
|
{
|
||||||
|
JS::Rooted<JSObject*> global(cx, aGlobal);
|
||||||
|
|
||||||
// First of all, the general pref has to be turned on.
|
// First of all, the general pref has to be turned on.
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
Preferences::GetBool("dom.datastore.enabled", &enabled);
|
Preferences::GetBool("dom.datastore.enabled", &enabled);
|
||||||
|
@ -1866,7 +1870,7 @@ Navigator::HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
|
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
|
||||||
if (!win) {
|
if (!win) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,7 +250,7 @@ public:
|
||||||
}
|
}
|
||||||
static bool HasMobileMessageSupport(JSContext* /* unused */,
|
static bool HasMobileMessageSupport(JSContext* /* unused */,
|
||||||
JSObject* aGlobal);
|
JSObject* aGlobal);
|
||||||
static bool HasTelephonySupport(JSContext* /* unused */,
|
static bool HasTelephonySupport(JSContext* cx,
|
||||||
JSObject* aGlobal);
|
JSObject* aGlobal);
|
||||||
static bool HasCameraSupport(JSContext* /* unused */,
|
static bool HasCameraSupport(JSContext* /* unused */,
|
||||||
JSObject* aGlobal);
|
JSObject* aGlobal);
|
||||||
|
@ -286,7 +286,7 @@ public:
|
||||||
|
|
||||||
static bool HasInputMethodSupport(JSContext* /* unused */, JSObject* aGlobal);
|
static bool HasInputMethodSupport(JSContext* /* unused */, JSObject* aGlobal);
|
||||||
|
|
||||||
static bool HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal);
|
static bool HasDataStoreSupport(JSContext* cx, JSObject* aGlobal);
|
||||||
|
|
||||||
nsPIDOMWindow* GetParentObject() const
|
nsPIDOMWindow* GetParentObject() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import cPickle
|
|
||||||
from Configuration import Configuration
|
|
||||||
from Codegen import CGBindingRoot, replaceFileIfChanged, CGEventRoot
|
|
||||||
from mozbuild.makeutil import Makefile
|
|
||||||
from mozbuild.pythonutil import iter_modules_in_path
|
|
||||||
from buildconfig import topsrcdir
|
|
||||||
|
|
||||||
|
|
||||||
def generate_binding_files(config, outputprefix, srcprefix, webidlfile,
|
|
||||||
generatedEventsWebIDLFiles):
|
|
||||||
"""
|
|
||||||
|config| Is the configuration object.
|
|
||||||
|outputprefix| is a prefix to use for the header guards and filename.
|
|
||||||
"""
|
|
||||||
|
|
||||||
depsname = ".deps/" + outputprefix + ".pp"
|
|
||||||
root = CGBindingRoot(config, outputprefix, webidlfile)
|
|
||||||
replaceFileIfChanged(outputprefix + ".h", root.declare())
|
|
||||||
replaceFileIfChanged(outputprefix + ".cpp", root.define())
|
|
||||||
|
|
||||||
if webidlfile in generatedEventsWebIDLFiles:
|
|
||||||
eventName = webidlfile[:-len(".webidl")]
|
|
||||||
generatedEvent = CGEventRoot(config, eventName)
|
|
||||||
replaceFileIfChanged(eventName + ".h", generatedEvent.declare())
|
|
||||||
replaceFileIfChanged(eventName + ".cpp", generatedEvent.define())
|
|
||||||
|
|
||||||
mk = Makefile()
|
|
||||||
# NOTE: it's VERY important that we output dependencies for the FooBinding
|
|
||||||
# file here, not for the header or generated cpp file. These dependencies
|
|
||||||
# are used later to properly determine changedDeps and prevent rebuilding
|
|
||||||
# too much. See the comment explaining $(binding_dependency_trackers) in
|
|
||||||
# Makefile.in.
|
|
||||||
rule = mk.create_rule([outputprefix])
|
|
||||||
rule.add_dependencies(os.path.join(srcprefix, x) for x in sorted(root.deps()))
|
|
||||||
rule.add_dependencies(iter_modules_in_path(topsrcdir))
|
|
||||||
with open(depsname, 'w') as f:
|
|
||||||
mk.dump(f)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Parse arguments.
|
|
||||||
from optparse import OptionParser
|
|
||||||
usagestring = "usage: %prog [header|cpp] configFile outputPrefix srcPrefix webIDLFile"
|
|
||||||
o = OptionParser(usage=usagestring)
|
|
||||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
|
||||||
help="When an error happens, display the Python traceback.")
|
|
||||||
(options, args) = o.parse_args()
|
|
||||||
|
|
||||||
configFile = os.path.normpath(args[0])
|
|
||||||
srcPrefix = os.path.normpath(args[1])
|
|
||||||
|
|
||||||
# Load the configuration
|
|
||||||
f = open('ParserResults.pkl', 'rb')
|
|
||||||
config = cPickle.load(f)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def readFile(f):
|
|
||||||
file = open(f, 'rb')
|
|
||||||
try:
|
|
||||||
contents = file.read()
|
|
||||||
finally:
|
|
||||||
file.close()
|
|
||||||
return contents
|
|
||||||
allWebIDLFiles = readFile(args[2]).split()
|
|
||||||
generatedEventsWebIDLFiles = readFile(args[3]).split()
|
|
||||||
changedDeps = readFile(args[4]).split()
|
|
||||||
|
|
||||||
if all(f.endswith("Binding") or f == "ParserResults.pkl" for f in changedDeps):
|
|
||||||
toRegenerate = filter(lambda f: f.endswith("Binding"), changedDeps)
|
|
||||||
if len(toRegenerate) == 0 and len(changedDeps) == 1:
|
|
||||||
# Work around build system bug 874923: if we get here that means
|
|
||||||
# that changedDeps contained only one entry and it was
|
|
||||||
# "ParserResults.pkl". That should never happen: if the
|
|
||||||
# ParserResults.pkl changes then either one of the globalgen files
|
|
||||||
# changed (in which case we wouldn't be in this "only
|
|
||||||
# ParserResults.pkl and *Binding changed" code) or some .webidl
|
|
||||||
# files changed (and then the corresponding *Binding files should
|
|
||||||
# show up in changedDeps). Since clearly the build system is
|
|
||||||
# confused, just regenerate everything to be safe.
|
|
||||||
toRegenerate = allWebIDLFiles
|
|
||||||
else:
|
|
||||||
toRegenerate = map(lambda f: f[:-len("Binding")] + ".webidl",
|
|
||||||
toRegenerate)
|
|
||||||
else:
|
|
||||||
toRegenerate = allWebIDLFiles
|
|
||||||
|
|
||||||
for webIDLFile in toRegenerate:
|
|
||||||
assert webIDLFile.endswith(".webidl")
|
|
||||||
outputPrefix = webIDLFile[:-len(".webidl")] + "Binding"
|
|
||||||
generate_binding_files(config, outputPrefix, srcPrefix, webIDLFile,
|
|
||||||
generatedEventsWebIDLFiles);
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -29,27 +29,6 @@ INSTANCE_RESERVED_SLOTS = 3
|
||||||
def memberReservedSlot(member):
|
def memberReservedSlot(member):
|
||||||
return "(DOM_INSTANCE_RESERVED_SLOTS + %d)" % member.slotIndex
|
return "(DOM_INSTANCE_RESERVED_SLOTS + %d)" % member.slotIndex
|
||||||
|
|
||||||
def replaceFileIfChanged(filename, newContents):
|
|
||||||
"""
|
|
||||||
Read a copy of the old file, so that we don't touch it if it hasn't changed.
|
|
||||||
Returns True if the file was updated, false otherwise.
|
|
||||||
"""
|
|
||||||
oldFileContents = ""
|
|
||||||
try:
|
|
||||||
oldFile = open(filename, 'rb')
|
|
||||||
oldFileContents = ''.join(oldFile.readlines())
|
|
||||||
oldFile.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if newContents == oldFileContents:
|
|
||||||
return False
|
|
||||||
|
|
||||||
f = open(filename, 'wb')
|
|
||||||
f.write(newContents)
|
|
||||||
f.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def toStringBool(arg):
|
def toStringBool(arg):
|
||||||
return str(not not arg).lower()
|
return str(not not arg).lower()
|
||||||
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import cPickle
|
|
||||||
from Configuration import Configuration
|
|
||||||
from Codegen import CGExampleRoot, replaceFileIfChanged
|
|
||||||
|
|
||||||
def generate_interface_example(config, interfaceName):
|
|
||||||
"""
|
|
||||||
|config| Is the configuration object.
|
|
||||||
|interfaceName| is the name of the interface we're generating an example for.
|
|
||||||
"""
|
|
||||||
|
|
||||||
root = CGExampleRoot(config, interfaceName)
|
|
||||||
exampleHeader = interfaceName + "-example.h"
|
|
||||||
exampleImpl = interfaceName + "-example.cpp"
|
|
||||||
replaceFileIfChanged(exampleHeader, root.declare())
|
|
||||||
replaceFileIfChanged(exampleImpl, root.define())
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
# Parse arguments.
|
|
||||||
from optparse import OptionParser
|
|
||||||
usagestring = "usage: %prog configFile interfaceName"
|
|
||||||
o = OptionParser(usage=usagestring)
|
|
||||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
|
||||||
help="When an error happens, display the Python traceback.")
|
|
||||||
(options, args) = o.parse_args()
|
|
||||||
|
|
||||||
if len(args) != 2:
|
|
||||||
o.error(usagestring)
|
|
||||||
configFile = os.path.normpath(args[0])
|
|
||||||
interfaceName = args[1]
|
|
||||||
|
|
||||||
# Load the configuration
|
|
||||||
f = open('ParserResults.pkl', 'rb')
|
|
||||||
config = cPickle.load(f)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# Generate the example class.
|
|
||||||
generate_interface_example(config, interfaceName)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,81 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
# We do one global pass over all the WebIDL to generate our prototype enum
|
|
||||||
# and generate information for subsequent phases.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import WebIDL
|
|
||||||
import cPickle
|
|
||||||
from Configuration import Configuration
|
|
||||||
from Codegen import GlobalGenRoots, replaceFileIfChanged
|
|
||||||
|
|
||||||
def generate_file(config, name, action):
|
|
||||||
|
|
||||||
root = getattr(GlobalGenRoots, name)(config)
|
|
||||||
if action is 'declare':
|
|
||||||
filename = name + '.h'
|
|
||||||
code = root.declare()
|
|
||||||
else:
|
|
||||||
assert action is 'define'
|
|
||||||
filename = name + '.cpp'
|
|
||||||
code = root.define()
|
|
||||||
|
|
||||||
if replaceFileIfChanged(filename, code):
|
|
||||||
print "Generating %s" % (filename)
|
|
||||||
else:
|
|
||||||
print "%s hasn't changed - not touching it" % (filename)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Parse arguments.
|
|
||||||
from optparse import OptionParser
|
|
||||||
usageString = "usage: %prog [options] webidldir [files]"
|
|
||||||
o = OptionParser(usage=usageString)
|
|
||||||
o.add_option("--cachedir", dest='cachedir', default=None,
|
|
||||||
help="Directory in which to cache lex/parse tables.")
|
|
||||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
|
||||||
help="When an error happens, display the Python traceback.")
|
|
||||||
(options, args) = o.parse_args()
|
|
||||||
|
|
||||||
if len(args) < 2:
|
|
||||||
o.error(usageString)
|
|
||||||
|
|
||||||
configFile = args[0]
|
|
||||||
baseDir = args[1]
|
|
||||||
fileList = args[2:]
|
|
||||||
|
|
||||||
# Parse the WebIDL.
|
|
||||||
parser = WebIDL.Parser(options.cachedir)
|
|
||||||
for filename in fileList:
|
|
||||||
fullPath = os.path.normpath(os.path.join(baseDir, filename))
|
|
||||||
f = open(fullPath, 'rb')
|
|
||||||
lines = f.readlines()
|
|
||||||
f.close()
|
|
||||||
parser.parse(''.join(lines), fullPath)
|
|
||||||
parserResults = parser.finish()
|
|
||||||
|
|
||||||
# Load the configuration.
|
|
||||||
config = Configuration(configFile, parserResults)
|
|
||||||
|
|
||||||
# Write the configuration out to a pickle.
|
|
||||||
resultsFile = open('ParserResults.pkl', 'wb')
|
|
||||||
cPickle.dump(config, resultsFile, -1)
|
|
||||||
resultsFile.close()
|
|
||||||
|
|
||||||
# Generate the atom list.
|
|
||||||
generate_file(config, 'GeneratedAtomList', 'declare')
|
|
||||||
|
|
||||||
# Generate the prototype list.
|
|
||||||
generate_file(config, 'PrototypeList', 'declare')
|
|
||||||
|
|
||||||
# Generate the common code.
|
|
||||||
generate_file(config, 'RegisterBindings', 'declare')
|
|
||||||
generate_file(config, 'RegisterBindings', 'define')
|
|
||||||
|
|
||||||
generate_file(config, 'UnionTypes', 'declare')
|
|
||||||
generate_file(config, 'UnionTypes', 'define')
|
|
||||||
generate_file(config, 'UnionConversions', 'declare')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,243 +1,85 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# 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,
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
abs_dist := $(abspath $(DIST))
|
||||||
|
webidl_base := $(topsrcdir)/dom/webidl
|
||||||
|
|
||||||
webidl_base = $(topsrcdir)/dom/webidl
|
|
||||||
# Generated by moz.build
|
# Generated by moz.build
|
||||||
include webidlsrcs.mk
|
include webidlsrcs.mk
|
||||||
|
|
||||||
binding_include_path := mozilla/dom
|
ifdef GNU_CC
|
||||||
webidl_files += $(generated_events_webidl_files)
|
OS_CXXFLAGS += -Wno-uninitialized
|
||||||
all_webidl_files = $(webidl_files) $(generated_webidl_files) $(preprocessed_webidl_files)
|
|
||||||
|
|
||||||
# Set exported_binding_headers before adding the test IDL to the mix
|
|
||||||
exported_binding_headers := $(subst .webidl,Binding.h,$(all_webidl_files))
|
|
||||||
exported_generated_events_headers := $(subst .webidl,.h,$(generated_events_webidl_files))
|
|
||||||
|
|
||||||
# Set linked_binding_cpp_files before adding the test IDL to the mix
|
|
||||||
linked_binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files))
|
|
||||||
linked_generated_events_cpp_files := $(subst .webidl,.cpp,$(generated_events_webidl_files))
|
|
||||||
|
|
||||||
all_webidl_files += $(test_webidl_files) $(preprocessed_test_webidl_files)
|
|
||||||
|
|
||||||
generated_header_files := $(subst .webidl,Binding.h,$(all_webidl_files)) $(exported_generated_events_headers)
|
|
||||||
generated_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files)) $(linked_generated_events_cpp_files)
|
|
||||||
|
|
||||||
# We want to be able to only regenerate the .cpp and .h files that really need
|
|
||||||
# to change when a .webidl file changes. We do this by making the
|
|
||||||
# binding_dependency_trackers targets have dependencies on the right .webidl
|
|
||||||
# files via generated .pp files, having a .BindingGen target that depends on the
|
|
||||||
# binding_dependency_trackers and which has all the generated binding .h/.cpp
|
|
||||||
# depending on it, and then in the make commands for that target being able to
|
|
||||||
# check which exact binding_dependency_trackers changed.
|
|
||||||
binding_dependency_trackers := $(subst .webidl,Binding,$(all_webidl_files))
|
|
||||||
|
|
||||||
globalgen_targets := \
|
|
||||||
GeneratedAtomList.h \
|
|
||||||
PrototypeList.h \
|
|
||||||
RegisterBindings.h \
|
|
||||||
RegisterBindings.cpp \
|
|
||||||
UnionTypes.h \
|
|
||||||
UnionTypes.cpp \
|
|
||||||
UnionConversions.h \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
# Nasty hack: when the test/Makefile.in invokes us to do codegen, it
|
|
||||||
# uses a target of
|
|
||||||
# "export TestExampleInterface-example TestExampleProxyInterface-example".
|
|
||||||
# We don't actually need to load our .o.pp files in that case, so just
|
|
||||||
# pretend like we have no CPPSRCS if that's the target. It makes the
|
|
||||||
# test cycle much faster, which is why we're doing it.
|
|
||||||
#
|
|
||||||
# XXXbz We could try to cheat even more and only include our CPPSRCS
|
|
||||||
# when $(MAKECMDGOALS) contains libs, so that we can skip loading all
|
|
||||||
# those .o.pp when trying to make a single .cpp file too, but that
|
|
||||||
# would break |make FooBinding.o(bj)|. Ah, well.
|
|
||||||
ifneq (export TestExampleInterface-example TestExampleProxyInterface-example,$(MAKECMDGOALS))
|
|
||||||
CPPSRCS = \
|
|
||||||
$(unified_binding_cpp_files) \
|
|
||||||
$(linked_generated_events_cpp_files) \
|
|
||||||
$(filter %.cpp, $(globalgen_targets)) \
|
|
||||||
$(NULL)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ABS_DIST := $(abspath $(DIST))
|
# These come from webidlsrcs.mk.
|
||||||
|
# TODO Write directly into backend.mk.
|
||||||
|
CPPSRCS += $(globalgen_sources) $(unified_binding_cpp_files)
|
||||||
|
|
||||||
EXTRA_EXPORT_MDDEPEND_FILES := $(addsuffix .pp,$(binding_dependency_trackers))
|
# Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
|
||||||
|
# since we generate exported bindings directly to $(DIST)/include, we need
|
||||||
|
# to add that path to the search list.
|
||||||
|
#
|
||||||
|
# Ideally, binding generation uses the prefixed header file names.
|
||||||
|
# Bug 932092 tracks.
|
||||||
|
LOCAL_INCLUDES += -I$(DIST)/include/mozilla/dom
|
||||||
|
|
||||||
EXPORTS_GENERATED_FILES := $(exported_binding_headers) $(exported_generated_events_headers)
|
PYTHON_UNIT_TESTS += $(srcdir)/mozwebidlcodegen/test/test_mozwebidlcodegen.py
|
||||||
EXPORTS_GENERATED_DEST := $(ABS_DIST)/include/$(binding_include_path)
|
|
||||||
EXPORTS_GENERATED_TARGET := export
|
|
||||||
INSTALL_TARGETS += EXPORTS_GENERATED
|
|
||||||
|
|
||||||
# Install auto-generated GlobalGen files. The rules for the install must
|
|
||||||
# be in the same target/subtier as GlobalGen.py, otherwise the files will not
|
|
||||||
# get installed into the appropriate location as they are generated.
|
|
||||||
globalgen_headers_FILES := \
|
|
||||||
GeneratedAtomList.h \
|
|
||||||
PrototypeList.h \
|
|
||||||
RegisterBindings.h \
|
|
||||||
UnionConversions.h \
|
|
||||||
UnionTypes.h \
|
|
||||||
$(NULL)
|
|
||||||
globalgen_headers_DEST = $(ABS_DIST)/include/mozilla/dom
|
|
||||||
globalgen_headers_TARGET := export
|
|
||||||
INSTALL_TARGETS += globalgen_headers
|
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
ifdef GNU_CC
|
# TODO This list should be emitted to a .pp file via
|
||||||
CXXFLAGS += -Wno-uninitialized
|
# GenerateCSS2PropertiesWebIDL.py.
|
||||||
endif
|
css2properties_dependencies = \
|
||||||
|
$(topsrcdir)/layout/style/nsCSSPropList.h \
|
||||||
# If you change bindinggen_dependencies here, change it in
|
|
||||||
# dom/bindings/test/Makefile.in too.
|
|
||||||
bindinggen_dependencies := \
|
|
||||||
BindingGen.py \
|
|
||||||
Bindings.conf \
|
|
||||||
Configuration.py \
|
|
||||||
Codegen.py \
|
|
||||||
ParserResults.pkl \
|
|
||||||
parser/WebIDL.py \
|
|
||||||
$(GLOBAL_DEPS) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
CSS2Properties.webidl: $(topsrcdir)/layout/style/nsCSSPropList.h \
|
|
||||||
$(topsrcdir)/layout/style/nsCSSPropAliasList.h \
|
$(topsrcdir)/layout/style/nsCSSPropAliasList.h \
|
||||||
$(webidl_base)/CSS2Properties.webidl.in \
|
$(webidl_base)/CSS2Properties.webidl.in \
|
||||||
$(webidl_base)/CSS2PropertiesProps.h \
|
$(webidl_base)/CSS2PropertiesProps.h \
|
||||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
||||||
$(GLOBAL_DEPS)
|
|
||||||
$(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style $(webidl_base)/CSS2PropertiesProps.h | \
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) \
|
|
||||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py $(webidl_base)/CSS2Properties.webidl.in > CSS2Properties.webidl
|
|
||||||
|
|
||||||
$(webidl_files): %: $(webidl_base)/%
|
|
||||||
$(INSTALL) $(IFLAGS1) $(webidl_base)/$* .
|
|
||||||
|
|
||||||
$(test_webidl_files): %: $(srcdir)/test/%
|
|
||||||
$(INSTALL) $(IFLAGS1) $(srcdir)/test/$* .
|
|
||||||
|
|
||||||
# We can't easily use PP_TARGETS here because it insists on outputting targets
|
|
||||||
# that look like "$(CURDIR)/foo" whereas we want our target to just be "foo".
|
|
||||||
# Make sure to include $(GLOBAL_DEPS) so we pick up changes to what symbols are
|
|
||||||
# defined. Also make sure to remove $@ before writing to it, because otherwise
|
|
||||||
# if a file goes from non-preprocessed to preprocessed we can end up writing to
|
|
||||||
# a symlink, which will clobber files in the srcdir, which is bad.
|
|
||||||
$(preprocessed_webidl_files): %: $(webidl_base)/% $(GLOBAL_DEPS)
|
|
||||||
$(RM) $@
|
|
||||||
$(call py_action,preprocessor, \
|
|
||||||
$(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $(webidl_base)/$* -o $@)
|
|
||||||
|
|
||||||
# See the comment about PP_TARGETS for $(preprocessed_webidl_files)
|
|
||||||
$(preprocessed_test_webidl_files): %: $(srcdir)/test/% $(GLOBAL_DEPS)
|
|
||||||
$(RM) $@
|
|
||||||
$(call py_action,preprocessor, \
|
|
||||||
$(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $(srcdir)/test/$* -o $@)
|
|
||||||
|
|
||||||
# Make is dumb and can get confused between "foo" and "$(CURDIR)/foo". Make
|
|
||||||
# sure that the latter depends on the former, since the latter gets used in .pp
|
|
||||||
# files.
|
|
||||||
all_webidl_files_absolute = $(addprefix $(CURDIR)/,$(all_webidl_files))
|
|
||||||
$(all_webidl_files_absolute): $(CURDIR)/%: %
|
|
||||||
|
|
||||||
$(generated_header_files): .BindingGen
|
|
||||||
|
|
||||||
$(generated_cpp_files): .BindingGen
|
|
||||||
|
|
||||||
# $(binding_dependency_trackers) pick up additional dependencies via .pp files
|
|
||||||
# The rule: just brings the tracker up to date, if it's out of date, so that
|
|
||||||
# we'll know that we have to redo binding generation and flag this prerequisite
|
|
||||||
# there as being newer than the bindinggen target.
|
|
||||||
$(binding_dependency_trackers):
|
|
||||||
@$(TOUCH) $@
|
|
||||||
|
|
||||||
$(globalgen_targets): ParserResults.pkl
|
|
||||||
|
|
||||||
%-example: .BindingGen
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
|
||||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
|
||||||
$(srcdir)/ExampleGen.py \
|
|
||||||
$(srcdir)/Bindings.conf $*
|
|
||||||
|
|
||||||
CACHE_DIR = _cache
|
|
||||||
|
|
||||||
globalgen_dependencies := \
|
|
||||||
GlobalGen.py \
|
|
||||||
Bindings.conf \
|
|
||||||
Configuration.py \
|
|
||||||
Codegen.py \
|
|
||||||
parser/WebIDL.py \
|
|
||||||
webidlsrcs.mk \
|
|
||||||
$(all_webidl_files) \
|
|
||||||
$(CACHE_DIR)/.done \
|
|
||||||
$(GLOBAL_DEPS) \
|
$(GLOBAL_DEPS) \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
$(CACHE_DIR)/.done:
|
CSS2Properties.webidl: $(css2properties_dependencies)
|
||||||
$(MKDIR) -p $(CACHE_DIR)
|
$(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style \
|
||||||
|
$(webidl_base)/CSS2PropertiesProps.h | \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) \
|
||||||
|
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
||||||
|
$(webidl_base)/CSS2Properties.webidl.in > $@
|
||||||
|
|
||||||
|
# Most of the logic for dependencies lives inside Python so it can be
|
||||||
|
# used by multiple build backends. We simply have rules to generate
|
||||||
|
# and include the .pp file.
|
||||||
|
#
|
||||||
|
# The generated .pp file contains all the important dependencies such as
|
||||||
|
# changes to .webidl or .py files should result in code generation being
|
||||||
|
# performed.
|
||||||
|
codegen_dependencies := \
|
||||||
|
$(nonstatic_webidl_files) \
|
||||||
|
$(GLOBAL_DEPS) \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
$(call include_deps,codegen.pp)
|
||||||
|
|
||||||
|
codegen.pp: $(codegen_dependencies)
|
||||||
|
$(call py_action,webidl,$(srcdir))
|
||||||
@$(TOUCH) $@
|
@$(TOUCH) $@
|
||||||
|
|
||||||
# Running GlobalGen.py updates ParserResults.pkl as a side-effect
|
.PHONY: compiletests
|
||||||
ParserResults.pkl: $(globalgen_dependencies)
|
compiletests:
|
||||||
$(info Generating global WebIDL files)
|
$(call SUBMAKE,libs,test)
|
||||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
|
||||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
|
||||||
$(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \
|
|
||||||
--cachedir=$(CACHE_DIR) \
|
|
||||||
$(all_webidl_files)
|
|
||||||
|
|
||||||
$(globalgen_headers_FILES): ParserResults.pkl
|
|
||||||
|
|
||||||
# Make sure .deps actually exists, since we'll try to write to it from
|
|
||||||
# BindingGen.py but we're typically running in the export phase, which is
|
|
||||||
# before anyone has bothered creating .deps.
|
|
||||||
# Then, pass our long lists through files to try to avoid blowing out the
|
|
||||||
# command line.
|
|
||||||
# Next, BindingGen.py will examine the changed dependency list to figure out
|
|
||||||
# what it really needs to regenerate.
|
|
||||||
# Finally, touch the .BindingGen file so that we don't have to keep redoing
|
|
||||||
# all that until something else actually changes.
|
|
||||||
.BindingGen: $(bindinggen_dependencies) $(binding_dependency_trackers)
|
|
||||||
$(info Generating WebIDL bindings)
|
|
||||||
$(MKDIR) -p .deps
|
|
||||||
echo $(all_webidl_files) > .all-webidl-file-list
|
|
||||||
echo $(generated_events_webidl_files) > .generated-events-webidl-files
|
|
||||||
echo $? > .changed-dependency-list
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
|
||||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
|
||||||
$(srcdir)/BindingGen.py \
|
|
||||||
$(srcdir)/Bindings.conf \
|
|
||||||
$(CURDIR) \
|
|
||||||
.all-webidl-file-list \
|
|
||||||
.generated-events-webidl-files \
|
|
||||||
.changed-dependency-list
|
|
||||||
@$(TOUCH) $@
|
|
||||||
|
|
||||||
GARBAGE += \
|
GARBAGE += \
|
||||||
webidlyacc.py \
|
codegen.pp \
|
||||||
|
codegen.json \
|
||||||
parser.out \
|
parser.out \
|
||||||
$(wildcard *-example.h) \
|
WebIDLGrammar.pkl \
|
||||||
$(wildcard *-example.cpp) \
|
$(wildcard *.h) \
|
||||||
.BindingGen \
|
$(wildcard *Binding.cpp) \
|
||||||
.all-webidl-file-list \
|
$(wildcard *Event.cpp) \
|
||||||
.generated-events-webidl-files \
|
$(wildcard *-event.cpp) \
|
||||||
.changed-dependency-list \
|
$(wildcard *.webidl) \
|
||||||
$(binding_dependency_trackers) \
|
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
# Make sure all binding header files are created during the export stage, so we
|
DIST_GARBAGE += \
|
||||||
# don't have issues with .cpp files being compiled before we've generated the
|
file-lists.json \
|
||||||
# headers they depend on. This is really only needed for the test files, since
|
|
||||||
# the non-test headers are all exported above anyway. Note that this means that
|
|
||||||
# we do all of our codegen during export.
|
|
||||||
export:: $(generated_header_files)
|
|
||||||
|
|
||||||
distclean::
|
|
||||||
-$(RM) \
|
|
||||||
$(generated_header_files) \
|
|
||||||
$(generated_cpp_files) \
|
|
||||||
$(all_webidl_files) \
|
|
||||||
$(globalgen_targets) \
|
|
||||||
ParserResults.pkl \
|
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
.. _webidl:
|
||||||
|
|
||||||
|
======
|
||||||
|
WebIDL
|
||||||
|
======
|
||||||
|
|
||||||
|
WebIDL describes interfaces web browsers are supposed to implement.
|
||||||
|
|
||||||
|
The interaction between WebIDL and the build system is somewhat complex.
|
||||||
|
This document will attempt to explain how it all works.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
``.webidl`` files throughout the tree define interfaces the browser
|
||||||
|
implements. Since Gecko/Firefox is implemented in C++, there is a
|
||||||
|
mechanism to convert these interfaces and associated metadata to
|
||||||
|
C++ code. That's where the build system comes into play.
|
||||||
|
|
||||||
|
All the code for interacting with ``.webidl`` files lives under
|
||||||
|
``dom/bindings``. There is code in the build system to deal with
|
||||||
|
WebIDLs explicitly.
|
||||||
|
|
||||||
|
WebIDL source file flavors
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Not all ``.webidl`` files are created equal! There are several flavors,
|
||||||
|
each represented by a separate symbol from :ref:`mozbuild_symbols`.
|
||||||
|
|
||||||
|
WEBIDL_FILES
|
||||||
|
Refers to regular/static ``.webidl`` files. Most WebIDL interfaces
|
||||||
|
are defined this way.
|
||||||
|
|
||||||
|
GENERATED_EVENTS_WEBIDL_FILES
|
||||||
|
In addition to generating a binding, these ``.webidl`` files also
|
||||||
|
generate a source file implementing the event object in C++
|
||||||
|
|
||||||
|
PREPROCESSED_WEBIDL_FILES
|
||||||
|
The ``.webidl`` files are generated by preprocessing an input file.
|
||||||
|
They otherwise behave like *WEBIDL_FILES*.
|
||||||
|
|
||||||
|
TEST_WEBIDL_FILES
|
||||||
|
Like *WEBIDL_FILES* but the interfaces are for testing only and
|
||||||
|
aren't shipped with the browser.
|
||||||
|
|
||||||
|
PREPROCESSED_TEST_WEBIDL_FILES
|
||||||
|
Like *TEST_WEBIDL_FILES* except the ``.webidl`` is obtained via
|
||||||
|
preprocessing, much like *PREPROCESSED_WEBIDL_FILES*.
|
||||||
|
|
||||||
|
GENERATED_WEBIDL_FILES
|
||||||
|
The ``.webidl`` for these is obtained through an *external*
|
||||||
|
mechanism. Typically there are custom build rules for producing these
|
||||||
|
files.
|
||||||
|
|
||||||
|
Producing C++ code
|
||||||
|
==================
|
||||||
|
|
||||||
|
The most complicated part about WebIDLs is the process by which
|
||||||
|
``.webidl`` files are converted into C++.
|
||||||
|
|
||||||
|
This process is handled by code in the :py:mod:`mozwebidlcodegen`
|
||||||
|
package. :py:class:`mozwebidlcodegen.WebIDLCodegenManager` is
|
||||||
|
specifically where you want to look for how code generation is
|
||||||
|
performed. This includes complex dependency management.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
============
|
||||||
|
|
||||||
|
This section aims to document the build and developer workflow requirements
|
||||||
|
for WebIDL.
|
||||||
|
|
||||||
|
Parser unit tests
|
||||||
|
There are parser tests provided by ``dom/bindings/parser/runtests.py``
|
||||||
|
that should run as part of ``make check``. There must be a mechanism
|
||||||
|
to run the tests in *human* mode so they output friendly error
|
||||||
|
messages.
|
||||||
|
|
||||||
|
The current mechanism for this is ``mach webidl-parser-test``.
|
||||||
|
|
||||||
|
Mochitests
|
||||||
|
There are various mochitests under ``dom/bindings/test``. They should
|
||||||
|
be runnable through the standard mechanisms.
|
||||||
|
|
||||||
|
Working with test interfaces
|
||||||
|
``TestExampleGenBinding.cpp`` calls into methods from the
|
||||||
|
``TestExampleInterface`` and ``TestExampleProxyInterface`` interfaces.
|
||||||
|
These interfaces need to be generated as part of the build. These
|
||||||
|
interfaces should not be exported or packaged.
|
||||||
|
|
||||||
|
There is a ``compiletests`` make target in ``dom/bindings`` that
|
||||||
|
isn't part of the build that facilitates turnkey code generation
|
||||||
|
and test file compilation.
|
||||||
|
|
||||||
|
Minimal rebuilds
|
||||||
|
Reprocessing every output for every change is expensive. So we don't
|
||||||
|
inconvenience people changing ``.webidl`` files, the build system
|
||||||
|
should only perform a minimal rebuild when sources change.
|
||||||
|
|
||||||
|
This logic is mostly all handled in
|
||||||
|
:py:class:`mozwebidlcodegen.WebIDLCodegenManager`. The unit tests for
|
||||||
|
that Python code should adequately test typical rebuild scenarios.
|
||||||
|
|
||||||
|
Bug 940469 tracks making the existing implementation better.
|
||||||
|
|
||||||
|
Explicit method for performing codegen
|
||||||
|
There needs to be an explicit method for invoking code generation.
|
||||||
|
It needs to cover regular and test files.
|
||||||
|
|
||||||
|
This is implemented via ``make export`` in ``dom/bindings``.
|
||||||
|
|
||||||
|
No-op binding generation should be fast
|
||||||
|
So developers touching ``.webidl`` files are not inconvenienced,
|
||||||
|
no-op binding generation should be fast. Watch out for the build system
|
||||||
|
processing large dependency files it doesn't need in order to perform
|
||||||
|
code generation.
|
||||||
|
|
||||||
|
Ability to generate example files
|
||||||
|
*Any* interface can have example ``.h``/``.cpp`` files generated.
|
||||||
|
There must be a mechanism to facilitate this.
|
||||||
|
|
||||||
|
This is currently facilitated through ``mach webidl-example``. e.g.
|
||||||
|
``mach webidl-example HTMLStyleElement``.
|
|
@ -18,6 +18,17 @@ from mozbuild.base import MachCommandBase
|
||||||
|
|
||||||
@CommandProvider
|
@CommandProvider
|
||||||
class WebIDLProvider(MachCommandBase):
|
class WebIDLProvider(MachCommandBase):
|
||||||
|
@Command('webidl-example', category='misc',
|
||||||
|
description='Generate example files for a WebIDL interface.')
|
||||||
|
@CommandArgument('interface', nargs='+',
|
||||||
|
help='Interface(s) whose examples to generate.')
|
||||||
|
def webidl_example(self, interface):
|
||||||
|
from mozwebidlcodegen import BuildSystemWebIDL
|
||||||
|
|
||||||
|
manager = self._spawn(BuildSystemWebIDL).manager
|
||||||
|
for i in interface:
|
||||||
|
manager.generate_example_files(i)
|
||||||
|
|
||||||
@Command('webidl-parser-test', category='testing',
|
@Command('webidl-parser-test', category='testing',
|
||||||
description='Run WebIDL tests.')
|
description='Run WebIDL tests.')
|
||||||
@CommandArgument('--verbose', '-v', action='store_true',
|
@CommandArgument('--verbose', '-v', action='store_true',
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
TEST_DIRS += ['test']
|
||||||
|
|
||||||
EXPORTS.mozilla += [
|
EXPORTS.mozilla += [
|
||||||
'ErrorResult.h',
|
'ErrorResult.h',
|
||||||
]
|
]
|
||||||
|
@ -85,3 +87,6 @@ if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:
|
||||||
]
|
]
|
||||||
|
|
||||||
FINAL_LIBRARY = 'xul'
|
FINAL_LIBRARY = 'xul'
|
||||||
|
|
||||||
|
SPHINX_TREES['webidl'] = 'docs'
|
||||||
|
SPHINX_PYTHON_PACKAGE_DIRS += ['mozwebidlcodegen']
|
||||||
|
|
|
@ -0,0 +1,565 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# This module contains code for managing WebIDL files and bindings for
|
||||||
|
# the build system.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from mach.mixin.logging import LoggingMixin
|
||||||
|
|
||||||
|
from mozbuild.base import MozbuildObject
|
||||||
|
from mozbuild.makeutil import Makefile
|
||||||
|
from mozbuild.pythonutil import iter_modules_in_path
|
||||||
|
from mozbuild.util import FileAvoidWrite
|
||||||
|
|
||||||
|
import mozpack.path as mozpath
|
||||||
|
|
||||||
|
import WebIDL
|
||||||
|
from Codegen import (
|
||||||
|
CGBindingRoot,
|
||||||
|
CGEventRoot,
|
||||||
|
CGExampleRoot,
|
||||||
|
GlobalGenRoots,
|
||||||
|
)
|
||||||
|
from Configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
class BuildResult(object):
|
||||||
|
"""Represents the result of processing WebIDL files.
|
||||||
|
|
||||||
|
This holds a summary of output file generation during code generation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# The .webidl files that had their outputs regenerated.
|
||||||
|
self.inputs = set()
|
||||||
|
|
||||||
|
# The output files that were created.
|
||||||
|
self.created = set()
|
||||||
|
|
||||||
|
# The output files that changed.
|
||||||
|
self.updated = set()
|
||||||
|
|
||||||
|
# The output files that didn't change.
|
||||||
|
self.unchanged = set()
|
||||||
|
|
||||||
|
|
||||||
|
class WebIDLCodegenManagerState(dict):
|
||||||
|
"""Holds state for the WebIDL code generation manager.
|
||||||
|
|
||||||
|
State is currently just an extended dict. The internal implementation of
|
||||||
|
state should be considered a black box to everyone except
|
||||||
|
WebIDLCodegenManager. But we'll still document it.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
version
|
||||||
|
The integer version of the format. This is to detect incompatible
|
||||||
|
changes between state. It should be bumped whenever the format
|
||||||
|
changes or semantics change.
|
||||||
|
|
||||||
|
webidls
|
||||||
|
A dictionary holding information about every known WebIDL input.
|
||||||
|
Keys are the basenames of input WebIDL files. Values are dicts of
|
||||||
|
metadata. Keys in those dicts are:
|
||||||
|
|
||||||
|
* filename - The full path to the input filename.
|
||||||
|
* inputs - A set of full paths to other webidl files this webidl
|
||||||
|
depends on.
|
||||||
|
* outputs - Set of full output paths that are created/derived from
|
||||||
|
this file.
|
||||||
|
* sha1 - The hexidecimal SHA-1 of the input filename from the last
|
||||||
|
processing time.
|
||||||
|
|
||||||
|
global_inputs
|
||||||
|
A dictionary defining files that influence all processing. Keys
|
||||||
|
are full filenames. Values are hexidecimal SHA-1 from the last
|
||||||
|
processing time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self, fh=None):
|
||||||
|
self['version'] = self.VERSION
|
||||||
|
self['webidls'] = {}
|
||||||
|
self['global_depends'] = {}
|
||||||
|
|
||||||
|
if not fh:
|
||||||
|
return
|
||||||
|
|
||||||
|
state = json.load(fh)
|
||||||
|
if state['version'] != self.VERSION:
|
||||||
|
raise Exception('Unknown state version: %s' % state['version'])
|
||||||
|
|
||||||
|
self['version'] = state['version']
|
||||||
|
self['global_depends'] = state['global_depends']
|
||||||
|
|
||||||
|
for k, v in state['webidls'].items():
|
||||||
|
self['webidls'][k] = v
|
||||||
|
|
||||||
|
# Sets are converted to lists for serialization because JSON
|
||||||
|
# doesn't support sets.
|
||||||
|
self['webidls'][k]['inputs'] = set(v['inputs'])
|
||||||
|
self['webidls'][k]['outputs'] = set(v['outputs'])
|
||||||
|
|
||||||
|
def dump(self, fh):
|
||||||
|
"""Dump serialized state to a file handle."""
|
||||||
|
normalized = deepcopy(self)
|
||||||
|
|
||||||
|
for k, v in self['webidls'].items():
|
||||||
|
# Convert sets to lists because JSON doesn't support sets.
|
||||||
|
normalized['webidls'][k]['outputs'] = sorted(v['outputs'])
|
||||||
|
normalized['webidls'][k]['inputs'] = sorted(v['inputs'])
|
||||||
|
|
||||||
|
json.dump(normalized, fh, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
class WebIDLCodegenManager(LoggingMixin):
|
||||||
|
"""Manages all code generation around WebIDL.
|
||||||
|
|
||||||
|
To facilitate testing, this object is meant to be generic and reusable.
|
||||||
|
Paths, etc should be parameters and not hardcoded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Global parser derived declaration files.
|
||||||
|
GLOBAL_DECLARE_FILES = {
|
||||||
|
'GeneratedAtomList.h',
|
||||||
|
'PrototypeList.h',
|
||||||
|
'RegisterBindings.h',
|
||||||
|
'UnionConversions.h',
|
||||||
|
'UnionTypes.h',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Global parser derived definition files.
|
||||||
|
GLOBAL_DEFINE_FILES = {
|
||||||
|
'RegisterBindings.cpp',
|
||||||
|
'UnionTypes.cpp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Example interfaces to build along with the tree. Other example
|
||||||
|
# interfaces will need to be generated manually.
|
||||||
|
BUILD_EXAMPLE_INTERFACES = {
|
||||||
|
'TestExampleInterface',
|
||||||
|
'TestExampleProxyInterface',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, config_path, inputs, exported_header_dir,
|
||||||
|
codegen_dir, state_path, cache_dir=None, make_deps_path=None,
|
||||||
|
make_deps_target=None):
|
||||||
|
"""Create an instance that manages WebIDLs in the build system.
|
||||||
|
|
||||||
|
config_path refers to a WebIDL config file (e.g. Bindings.conf).
|
||||||
|
inputs is a 3-tuple describing the input .webidl files and how to
|
||||||
|
process them. Members are:
|
||||||
|
(set(.webidl files), set(basenames of exported files),
|
||||||
|
set(basenames of generated events files))
|
||||||
|
|
||||||
|
exported_header_dir and codegen_dir are directories where generated
|
||||||
|
files will be written to.
|
||||||
|
state_path is the path to a file that will receive JSON state from our
|
||||||
|
actions.
|
||||||
|
make_deps_path is the path to a make dependency file that we can
|
||||||
|
optionally write.
|
||||||
|
make_deps_target is the target that receives the make dependencies. It
|
||||||
|
must be defined if using make_deps_path.
|
||||||
|
"""
|
||||||
|
self.populate_logger()
|
||||||
|
|
||||||
|
input_paths, exported_stems, generated_events_stems = inputs
|
||||||
|
|
||||||
|
self._config_path = config_path
|
||||||
|
self._input_paths = set(input_paths)
|
||||||
|
self._exported_stems = set(exported_stems)
|
||||||
|
self._generated_events_stems = set(generated_events_stems)
|
||||||
|
self._exported_header_dir = exported_header_dir
|
||||||
|
self._codegen_dir = codegen_dir
|
||||||
|
self._state_path = state_path
|
||||||
|
self._cache_dir = cache_dir
|
||||||
|
self._make_deps_path = make_deps_path
|
||||||
|
self._make_deps_target = make_deps_target
|
||||||
|
|
||||||
|
if (make_deps_path and not make_deps_target) or (not make_deps_path and
|
||||||
|
make_deps_target):
|
||||||
|
raise Exception('Must define both make_deps_path and make_deps_target '
|
||||||
|
'if one is defined.')
|
||||||
|
|
||||||
|
self._parser_results = None
|
||||||
|
self._config = None
|
||||||
|
self._state = WebIDLCodegenManagerState()
|
||||||
|
|
||||||
|
if os.path.exists(state_path):
|
||||||
|
with open(state_path, 'rb') as fh:
|
||||||
|
try:
|
||||||
|
self._state = WebIDLCodegenManagerState(fh=fh)
|
||||||
|
except Exception as e:
|
||||||
|
self.log(logging.WARN, 'webidl_bad_state', {'msg': str(e)},
|
||||||
|
'Bad WebIDL state: {msg}')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self):
|
||||||
|
if not self._config:
|
||||||
|
self._parse_webidl()
|
||||||
|
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def generate_build_files(self):
|
||||||
|
"""Generate files required for the build.
|
||||||
|
|
||||||
|
This function is in charge of generating all the .h/.cpp files derived
|
||||||
|
from input .webidl files. Please note that there are build actions
|
||||||
|
required to produce .webidl files and these build actions are
|
||||||
|
explicitly not captured here: this function assumes all .webidl files
|
||||||
|
are present and up to date.
|
||||||
|
|
||||||
|
This routine is called as part of the build to ensure files that need
|
||||||
|
to exist are present and up to date. This routine may not be called if
|
||||||
|
the build dependencies (generated as a result of calling this the first
|
||||||
|
time) say everything is up to date.
|
||||||
|
|
||||||
|
Because reprocessing outputs for every .webidl on every invocation
|
||||||
|
is expensive, we only regenerate the minimal set of files on every
|
||||||
|
invocation. The rules for deciding what needs done are roughly as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
1. If any .webidl changes, reparse all .webidl files and regenerate
|
||||||
|
the global derived files. Only regenerate output files (.h/.cpp)
|
||||||
|
impacted by the modified .webidl files.
|
||||||
|
2. If an non-.webidl dependency (Python files, config file) changes,
|
||||||
|
assume everything is out of date and regenerate the world. This
|
||||||
|
is because changes in those could globally impact every output
|
||||||
|
file.
|
||||||
|
3. If an output file is missing, ensure it is present by performing
|
||||||
|
necessary regeneration.
|
||||||
|
"""
|
||||||
|
# Despite #1 above, we assume the build system is smart enough to not
|
||||||
|
# invoke us if nothing has changed. Therefore, any invocation means
|
||||||
|
# something has changed. And, if anything has changed, we need to
|
||||||
|
# parse the WebIDL.
|
||||||
|
self._parse_webidl()
|
||||||
|
|
||||||
|
result = BuildResult()
|
||||||
|
|
||||||
|
# If we parse, we always update globals - they are cheap and it is
|
||||||
|
# easier that way.
|
||||||
|
created, updated, unchanged = self._write_global_derived()
|
||||||
|
result.created |= created
|
||||||
|
result.updated |= updated
|
||||||
|
result.unchanged |= unchanged
|
||||||
|
|
||||||
|
# If any of the extra dependencies changed, regenerate the world.
|
||||||
|
global_changed, global_hashes = self._global_dependencies_changed()
|
||||||
|
if global_changed:
|
||||||
|
# Make a copy because we may modify.
|
||||||
|
changed_inputs = set(self._input_paths)
|
||||||
|
else:
|
||||||
|
changed_inputs = self._compute_changed_inputs()
|
||||||
|
|
||||||
|
self._state['global_depends'] = global_hashes
|
||||||
|
|
||||||
|
# Generate bindings from .webidl files.
|
||||||
|
for filename in sorted(changed_inputs):
|
||||||
|
basename = mozpath.basename(filename)
|
||||||
|
result.inputs.add(filename)
|
||||||
|
written, deps = self._generate_build_files_for_webidl(filename)
|
||||||
|
result.created |= written[0]
|
||||||
|
result.updated |= written[1]
|
||||||
|
result.unchanged |= written[2]
|
||||||
|
|
||||||
|
self._state['webidls'][basename] = dict(
|
||||||
|
filename=filename,
|
||||||
|
outputs=written[0] | written[1] | written[2],
|
||||||
|
inputs=set(deps),
|
||||||
|
sha1=self._input_hashes[filename],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process some special interfaces required for testing.
|
||||||
|
for interface in self.BUILD_EXAMPLE_INTERFACES:
|
||||||
|
written = self.generate_example_files(interface)
|
||||||
|
result.created |= written[0]
|
||||||
|
result.updated |= written[1]
|
||||||
|
result.unchanged |= written[2]
|
||||||
|
|
||||||
|
# Generate a make dependency file.
|
||||||
|
if self._make_deps_path:
|
||||||
|
mk = Makefile()
|
||||||
|
codegen_rule = mk.create_rule([self._make_deps_target])
|
||||||
|
codegen_rule.add_dependencies(global_hashes.keys())
|
||||||
|
codegen_rule.add_dependencies(self._input_paths)
|
||||||
|
|
||||||
|
with FileAvoidWrite(self._make_deps_path) as fh:
|
||||||
|
mk.dump(fh)
|
||||||
|
|
||||||
|
self._save_state()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_example_files(self, interface):
|
||||||
|
"""Generates example files for a given interface."""
|
||||||
|
root = CGExampleRoot(self.config, interface)
|
||||||
|
|
||||||
|
return self._maybe_write_codegen(root, *self._example_paths(interface))
|
||||||
|
|
||||||
|
def _parse_webidl(self):
|
||||||
|
self.log(logging.INFO, 'webidl_parse',
|
||||||
|
{'count': len(self._input_paths)},
|
||||||
|
'Parsing {count} WebIDL files.')
|
||||||
|
|
||||||
|
hashes = {}
|
||||||
|
parser = WebIDL.Parser(self._cache_dir)
|
||||||
|
|
||||||
|
for path in sorted(self._input_paths):
|
||||||
|
with open(path, 'rb') as fh:
|
||||||
|
data = fh.read()
|
||||||
|
hashes[path] = hashlib.sha1(data).hexdigest()
|
||||||
|
parser.parse(data, path)
|
||||||
|
|
||||||
|
self._parser_results = parser.finish()
|
||||||
|
self._config = Configuration(self._config_path, self._parser_results)
|
||||||
|
self._input_hashes = hashes
|
||||||
|
|
||||||
|
def _write_global_derived(self):
|
||||||
|
things = [('declare', f) for f in self.GLOBAL_DECLARE_FILES]
|
||||||
|
things.extend(('define', f) for f in self.GLOBAL_DEFINE_FILES)
|
||||||
|
|
||||||
|
result = (set(), set(), set())
|
||||||
|
|
||||||
|
for what, filename in things:
|
||||||
|
stem = mozpath.splitext(filename)[0]
|
||||||
|
root = getattr(GlobalGenRoots, stem)(self._config)
|
||||||
|
|
||||||
|
if what == 'declare':
|
||||||
|
code = root.declare()
|
||||||
|
output_root = self._exported_header_dir
|
||||||
|
elif what == 'define':
|
||||||
|
code = root.define()
|
||||||
|
output_root = self._codegen_dir
|
||||||
|
else:
|
||||||
|
raise Exception('Unknown global gen type: %s' % what)
|
||||||
|
|
||||||
|
output_path = mozpath.join(output_root, filename)
|
||||||
|
self._maybe_write_file(output_path, code, result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _compute_changed_inputs(self):
|
||||||
|
"""Compute the set of input files that need to be regenerated."""
|
||||||
|
changed_inputs = set()
|
||||||
|
expected_outputs = self.expected_build_output_files()
|
||||||
|
|
||||||
|
# Look for missing output files.
|
||||||
|
if any(not os.path.exists(f) for f in expected_outputs):
|
||||||
|
# FUTURE Bug 940469 Only regenerate minimum set.
|
||||||
|
changed_inputs |= self._input_paths
|
||||||
|
|
||||||
|
# That's it for examining output files. We /could/ examine SHA-1's of
|
||||||
|
# output files from a previous run to detect modifications. But that's
|
||||||
|
# a lot of extra work and most build systems don't do that anyway.
|
||||||
|
|
||||||
|
# Now we move on to the input files.
|
||||||
|
old_hashes = {v['filename']: v['sha1']
|
||||||
|
for v in self._state['webidls'].values()}
|
||||||
|
|
||||||
|
old_filenames = set(old_hashes.keys())
|
||||||
|
new_filenames = self._input_paths
|
||||||
|
|
||||||
|
# If an old file has disappeared or a new file has arrived, mark
|
||||||
|
# it.
|
||||||
|
changed_inputs |= old_filenames ^ new_filenames
|
||||||
|
|
||||||
|
# For the files in common between runs, compare content. If the file
|
||||||
|
# has changed, mark it. We don't need to perform mtime comparisons
|
||||||
|
# because content is a stronger validator.
|
||||||
|
for filename in old_filenames & new_filenames:
|
||||||
|
if old_hashes[filename] != self._input_hashes[filename]:
|
||||||
|
changed_inputs.add(filename)
|
||||||
|
|
||||||
|
# We've now populated the base set of inputs that have changed.
|
||||||
|
|
||||||
|
# Inherit dependencies from previous run. The full set of dependencies
|
||||||
|
# is associated with each record, so we don't need to perform any fancy
|
||||||
|
# graph traversal.
|
||||||
|
for v in self._state['webidls'].values():
|
||||||
|
if any(dep for dep in v['inputs'] if dep in changed_inputs):
|
||||||
|
changed_inputs.add(v['filename'])
|
||||||
|
|
||||||
|
# Ensure all changed inputs actually exist (some changed inputs could
|
||||||
|
# have been from deleted files).
|
||||||
|
return set(f for f in changed_inputs if os.path.exists(f))
|
||||||
|
|
||||||
|
def _binding_info(self, p):
|
||||||
|
"""Compute binding metadata for an input path.
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
|
||||||
|
(stem, binding_stem, is_event, output_files)
|
||||||
|
|
||||||
|
output_files is itself a tuple. The first two items are the binding
|
||||||
|
header and C++ paths, respectively. The 2nd pair are the event header
|
||||||
|
and C++ paths or None if this isn't an event binding.
|
||||||
|
"""
|
||||||
|
basename = mozpath.basename(p)
|
||||||
|
stem = mozpath.splitext(basename)[0]
|
||||||
|
binding_stem = '%sBinding' % stem
|
||||||
|
|
||||||
|
if stem in self._exported_stems:
|
||||||
|
header_dir = self._exported_header_dir
|
||||||
|
else:
|
||||||
|
header_dir = self._codegen_dir
|
||||||
|
|
||||||
|
is_event = stem in self._generated_events_stems
|
||||||
|
|
||||||
|
files = (
|
||||||
|
mozpath.join(header_dir, '%s.h' % binding_stem),
|
||||||
|
mozpath.join(self._codegen_dir, '%s.cpp' % binding_stem),
|
||||||
|
mozpath.join(header_dir, '%s.h' % stem) if is_event else None,
|
||||||
|
mozpath.join(self._codegen_dir, '%s.cpp' % stem) if is_event else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
return stem, binding_stem, is_event, header_dir, files
|
||||||
|
|
||||||
|
def _example_paths(self, interface):
|
||||||
|
return (
|
||||||
|
mozpath.join(self._codegen_dir, '%s-example.h' % interface),
|
||||||
|
mozpath.join(self._codegen_dir, '%s-example.cpp' % interface))
|
||||||
|
|
||||||
|
def expected_build_output_files(self):
|
||||||
|
"""Obtain the set of files generate_build_files() should write."""
|
||||||
|
paths = set()
|
||||||
|
|
||||||
|
# Account for global generation.
|
||||||
|
for p in self.GLOBAL_DECLARE_FILES:
|
||||||
|
paths.add(mozpath.join(self._exported_header_dir, p))
|
||||||
|
for p in self.GLOBAL_DEFINE_FILES:
|
||||||
|
paths.add(mozpath.join(self._codegen_dir, p))
|
||||||
|
|
||||||
|
for p in self._input_paths:
|
||||||
|
stem, binding_stem, is_event, header_dir, files = self._binding_info(p)
|
||||||
|
paths |= {f for f in files if f}
|
||||||
|
|
||||||
|
for interface in self.BUILD_EXAMPLE_INTERFACES:
|
||||||
|
for p in self._example_paths(interface):
|
||||||
|
paths.add(p)
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def _generate_build_files_for_webidl(self, filename):
|
||||||
|
self.log(logging.INFO, 'webidl_generate_build_for_input',
|
||||||
|
{'filename': filename},
|
||||||
|
'Generating WebIDL files derived from {filename}')
|
||||||
|
|
||||||
|
stem, binding_stem, is_event, header_dir, files = self._binding_info(filename)
|
||||||
|
root = CGBindingRoot(self._config, binding_stem, filename)
|
||||||
|
|
||||||
|
result = self._maybe_write_codegen(root, files[0], files[1])
|
||||||
|
|
||||||
|
if is_event:
|
||||||
|
generated_event = CGEventRoot(self._config, stem)
|
||||||
|
result = self._maybe_write_codegen(generated_event, files[2],
|
||||||
|
files[3], result)
|
||||||
|
|
||||||
|
return result, root.deps()
|
||||||
|
|
||||||
|
def _global_dependencies_changed(self):
|
||||||
|
"""Determine whether the global dependencies have changed."""
|
||||||
|
current_files = set(iter_modules_in_path(mozpath.dirname(__file__)))
|
||||||
|
|
||||||
|
# We need to catch other .py files from /dom/bindings. We assume these
|
||||||
|
# are in the same directory as the config file.
|
||||||
|
current_files |= set(iter_modules_in_path(mozpath.dirname(self._config_path)))
|
||||||
|
|
||||||
|
current_files.add(self._config_path)
|
||||||
|
|
||||||
|
current_hashes = {}
|
||||||
|
for f in current_files:
|
||||||
|
# This will fail if the file doesn't exist. If a current global
|
||||||
|
# dependency doesn't exist, something else is wrong.
|
||||||
|
with open(f, 'rb') as fh:
|
||||||
|
current_hashes[f] = hashlib.sha1(fh.read()).hexdigest()
|
||||||
|
|
||||||
|
# The set of files has changed.
|
||||||
|
if current_files ^ set(self._state['global_depends'].keys()):
|
||||||
|
return True, current_hashes
|
||||||
|
|
||||||
|
# Compare hashes.
|
||||||
|
for f, sha1 in current_hashes.items():
|
||||||
|
if sha1 != self._state['global_depends'][f]:
|
||||||
|
return True, current_hashes
|
||||||
|
|
||||||
|
return False, current_hashes
|
||||||
|
|
||||||
|
def _save_state(self):
|
||||||
|
with open(self._state_path, 'wb') as fh:
|
||||||
|
self._state.dump(fh)
|
||||||
|
|
||||||
|
def _maybe_write_codegen(self, obj, declare_path, define_path, result=None):
|
||||||
|
assert declare_path and define_path
|
||||||
|
if not result:
|
||||||
|
result = (set(), set(), set())
|
||||||
|
|
||||||
|
self._maybe_write_file(declare_path, obj.declare(), result)
|
||||||
|
self._maybe_write_file(define_path, obj.define(), result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _maybe_write_file(self, path, content, result):
|
||||||
|
fh = FileAvoidWrite(path)
|
||||||
|
fh.write(content)
|
||||||
|
existed, updated = fh.close()
|
||||||
|
|
||||||
|
if not existed:
|
||||||
|
result[0].add(path)
|
||||||
|
elif updated:
|
||||||
|
result[1].add(path)
|
||||||
|
else:
|
||||||
|
result[2].add(path)
|
||||||
|
|
||||||
|
|
||||||
|
def create_build_system_manager(topsrcdir, topobjdir, dist_dir):
|
||||||
|
"""Create a WebIDLCodegenManager for use by the build system."""
|
||||||
|
src_dir = os.path.join(topsrcdir, 'dom', 'bindings')
|
||||||
|
obj_dir = os.path.join(topobjdir, 'dom', 'bindings')
|
||||||
|
|
||||||
|
with open(os.path.join(obj_dir, 'file-lists.json'), 'rb') as fh:
|
||||||
|
files = json.load(fh)
|
||||||
|
|
||||||
|
inputs = (files['webidls'], files['exported_stems'],
|
||||||
|
files['generated_events_stems'])
|
||||||
|
|
||||||
|
cache_dir = os.path.join(obj_dir, '_cache')
|
||||||
|
try:
|
||||||
|
os.makedirs(cache_dir)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return WebIDLCodegenManager(
|
||||||
|
os.path.join(src_dir, 'Bindings.conf'),
|
||||||
|
inputs,
|
||||||
|
os.path.join(dist_dir, 'include', 'mozilla', 'dom'),
|
||||||
|
obj_dir,
|
||||||
|
os.path.join(obj_dir, 'codegen.json'),
|
||||||
|
cache_dir=cache_dir,
|
||||||
|
# The make rules include a codegen.pp file containing dependencies.
|
||||||
|
make_deps_path=os.path.join(obj_dir, 'codegen.pp'),
|
||||||
|
make_deps_target='codegen.pp',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildSystemWebIDL(MozbuildObject):
|
||||||
|
@property
|
||||||
|
def manager(self):
|
||||||
|
if not hasattr(self, '_webidl_manager'):
|
||||||
|
self._webidl_manager = create_build_system_manager(
|
||||||
|
self.topsrcdir, self.topobjdir, self.distdir)
|
||||||
|
|
||||||
|
return self._webidl_manager
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface Child : Parent {
|
||||||
|
void ChildBaz();
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
interface DummyInterface {};
|
||||||
|
interface DummyInterfaceWorkers {};
|
|
@ -0,0 +1,3 @@
|
||||||
|
/* These interfaces are hard-coded and need to be defined. */
|
||||||
|
interface TestExampleInterface {};
|
||||||
|
interface TestExampleProxyInterface {};
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface Parent {
|
||||||
|
void MethodFoo();
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
interface EventTarget {
|
||||||
|
void addEventListener();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Event {};
|
||||||
|
|
||||||
|
callback EventHandlerNonNull = any (Event event);
|
||||||
|
typedef EventHandlerNonNull? EventHandler;
|
||||||
|
|
||||||
|
[NoInterfaceObject]
|
||||||
|
interface TestEvent : EventTarget {
|
||||||
|
attribute EventHandler onfoo;
|
||||||
|
};
|
|
@ -0,0 +1,278 @@
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mozpack.path as mozpath
|
||||||
|
|
||||||
|
from mozwebidlcodegen import (
|
||||||
|
WebIDLCodegenManager,
|
||||||
|
WebIDLCodegenManagerState,
|
||||||
|
)
|
||||||
|
|
||||||
|
from mozfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
from mozunit import (
|
||||||
|
MockedOpen,
|
||||||
|
main,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
OUR_DIR = mozpath.abspath(mozpath.dirname(__file__))
|
||||||
|
TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, '..', '..', '..', '..'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestWebIDLCodegenManager(unittest.TestCase):
|
||||||
|
TEST_STEMS = {
|
||||||
|
'Child',
|
||||||
|
'Parent',
|
||||||
|
'ExampleBinding',
|
||||||
|
'TestEvent',
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _static_input_paths(self):
|
||||||
|
s = {mozpath.join(OUR_DIR, p) for p in os.listdir(OUR_DIR)
|
||||||
|
if p.endswith('.webidl')}
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _config_path(self):
|
||||||
|
config = mozpath.join(TOPSRCDIR, 'dom', 'bindings', 'Bindings.conf')
|
||||||
|
self.assertTrue(os.path.exists(config))
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def _get_manager_args(self):
|
||||||
|
tmp = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, tmp)
|
||||||
|
|
||||||
|
cache_dir = mozpath.join(tmp, 'cache')
|
||||||
|
os.mkdir(cache_dir)
|
||||||
|
|
||||||
|
ip = self._static_input_paths
|
||||||
|
|
||||||
|
inputs = (
|
||||||
|
ip,
|
||||||
|
{mozpath.splitext(mozpath.basename(p))[0] for p in ip},
|
||||||
|
set()
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
config_path=self._config_path,
|
||||||
|
inputs=inputs,
|
||||||
|
exported_header_dir=mozpath.join(tmp, 'exports'),
|
||||||
|
codegen_dir=mozpath.join(tmp, 'codegen'),
|
||||||
|
state_path=mozpath.join(tmp, 'state.json'),
|
||||||
|
make_deps_path=mozpath.join(tmp, 'codegen.pp'),
|
||||||
|
make_deps_target='codegen.pp',
|
||||||
|
cache_dir=cache_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_manager(self):
|
||||||
|
return WebIDLCodegenManager(**self._get_manager_args())
|
||||||
|
|
||||||
|
def test_unknown_state_version(self):
|
||||||
|
"""Loading a state file with a too new version resets state."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
|
||||||
|
p = args['state_path']
|
||||||
|
|
||||||
|
with open(p, 'wb') as fh:
|
||||||
|
json.dump({
|
||||||
|
'version': WebIDLCodegenManagerState.VERSION + 1,
|
||||||
|
'foobar': '1',
|
||||||
|
}, fh)
|
||||||
|
|
||||||
|
manager = WebIDLCodegenManager(**args)
|
||||||
|
|
||||||
|
self.assertEqual(manager._state['version'],
|
||||||
|
WebIDLCodegenManagerState.VERSION)
|
||||||
|
self.assertNotIn('foobar', manager._state)
|
||||||
|
|
||||||
|
def test_generate_build_files(self):
|
||||||
|
"""generate_build_files() does the right thing from empty."""
|
||||||
|
manager = self._get_manager()
|
||||||
|
result = manager.generate_build_files()
|
||||||
|
self.assertEqual(len(result.inputs), 5)
|
||||||
|
|
||||||
|
output = manager.expected_build_output_files()
|
||||||
|
self.assertEqual(result.created, output)
|
||||||
|
self.assertEqual(len(result.updated), 0)
|
||||||
|
self.assertEqual(len(result.unchanged), 0)
|
||||||
|
|
||||||
|
for f in output:
|
||||||
|
self.assertTrue(os.path.isfile(f))
|
||||||
|
|
||||||
|
for f in manager.GLOBAL_DECLARE_FILES:
|
||||||
|
self.assertIn(mozpath.join(manager._exported_header_dir, f), output)
|
||||||
|
|
||||||
|
for f in manager.GLOBAL_DEFINE_FILES:
|
||||||
|
self.assertIn(mozpath.join(manager._codegen_dir, f), output)
|
||||||
|
|
||||||
|
for s in self.TEST_STEMS:
|
||||||
|
self.assertTrue(os.path.isfile(mozpath.join(
|
||||||
|
manager._exported_header_dir, '%sBinding.h' % s)))
|
||||||
|
self.assertTrue(os.path.isfile(mozpath.join(
|
||||||
|
manager._codegen_dir, '%sBinding.cpp' % s)))
|
||||||
|
|
||||||
|
self.assertTrue(os.path.isfile(manager._state_path))
|
||||||
|
|
||||||
|
with open(manager._state_path, 'rb') as fh:
|
||||||
|
state = json.load(fh)
|
||||||
|
self.assertEqual(state['version'], 1)
|
||||||
|
self.assertIn('webidls', state)
|
||||||
|
|
||||||
|
child = state['webidls']['Child.webidl']
|
||||||
|
self.assertEqual(len(child['inputs']), 2)
|
||||||
|
self.assertEqual(len(child['outputs']), 2)
|
||||||
|
self.assertEqual(child['sha1'], 'c41527cad3bc161fa6e7909e48fa11f9eca0468b')
|
||||||
|
|
||||||
|
def test_generate_build_files_load_state(self):
|
||||||
|
"""State should be equivalent when instantiating a new instance."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
self.assertEqual(len(m1._state['webidls']), 0)
|
||||||
|
m1.generate_build_files()
|
||||||
|
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
self.assertGreater(len(m2._state['webidls']), 2)
|
||||||
|
self.assertEqual(m1._state, m2._state)
|
||||||
|
|
||||||
|
def test_no_change_no_writes(self):
|
||||||
|
"""If nothing changes, no files should be updated."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
m1.generate_build_files()
|
||||||
|
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
|
||||||
|
self.assertEqual(len(result.inputs), 0)
|
||||||
|
self.assertEqual(len(result.created), 0)
|
||||||
|
self.assertEqual(len(result.updated), 0)
|
||||||
|
|
||||||
|
def test_output_file_regenerated(self):
|
||||||
|
"""If an output file disappears, it is regenerated."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
m1.generate_build_files()
|
||||||
|
|
||||||
|
rm_count = 0
|
||||||
|
for p in m1._state['webidls']['Child.webidl']['outputs']:
|
||||||
|
rm_count += 1
|
||||||
|
os.unlink(p)
|
||||||
|
|
||||||
|
for p in m1.GLOBAL_DECLARE_FILES:
|
||||||
|
rm_count += 1
|
||||||
|
os.unlink(mozpath.join(m1._exported_header_dir, p))
|
||||||
|
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
self.assertEqual(len(result.created), rm_count)
|
||||||
|
|
||||||
|
def test_only_rebuild_self(self):
|
||||||
|
"""If an input file changes, only rebuild that one file."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
m1.generate_build_files()
|
||||||
|
|
||||||
|
child_path = None
|
||||||
|
for p in m1._input_paths:
|
||||||
|
if p.endswith('Child.webidl'):
|
||||||
|
child_path = p
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertIsNotNone(child_path)
|
||||||
|
child_content = open(child_path, 'rb').read()
|
||||||
|
|
||||||
|
with MockedOpen({child_path: child_content + '\n/* */'}):
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
self.assertEqual(result.inputs, set([child_path]))
|
||||||
|
self.assertEqual(len(result.updated), 0)
|
||||||
|
self.assertEqual(len(result.created), 0)
|
||||||
|
|
||||||
|
def test_rebuild_dependencies(self):
|
||||||
|
"""Ensure an input file used by others results in others rebuilding."""
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
m1.generate_build_files()
|
||||||
|
|
||||||
|
parent_path = None
|
||||||
|
child_path = None
|
||||||
|
for p in m1._input_paths:
|
||||||
|
if p.endswith('Parent.webidl'):
|
||||||
|
parent_path = p
|
||||||
|
elif p.endswith('Child.webidl'):
|
||||||
|
child_path = p
|
||||||
|
|
||||||
|
self.assertIsNotNone(parent_path)
|
||||||
|
parent_content = open(parent_path, 'rb').read()
|
||||||
|
|
||||||
|
with MockedOpen({parent_path: parent_content + '\n/* */'}):
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
self.assertEqual(result.inputs, {child_path, parent_path})
|
||||||
|
self.assertEqual(len(result.updated), 0)
|
||||||
|
self.assertEqual(len(result.created), 0)
|
||||||
|
|
||||||
|
def test_python_change_regenerate_everything(self):
|
||||||
|
"""If a Python file changes, we should attempt to rebuild everything."""
|
||||||
|
|
||||||
|
# We don't want to mutate files in the source directory because we want
|
||||||
|
# to be able to build from a read-only filesystem. So, we install a
|
||||||
|
# dummy module and rewrite the metadata to say it comes from the source
|
||||||
|
# directory.
|
||||||
|
#
|
||||||
|
# Hacking imp to accept a MockedFile doesn't appear possible. So for
|
||||||
|
# the first iteration we read from a temp file. The second iteration
|
||||||
|
# doesn't need to import, so we are fine with a mocked file.
|
||||||
|
fake_path = mozpath.join(OUR_DIR, 'fakemodule.py')
|
||||||
|
with NamedTemporaryFile('wt') as fh:
|
||||||
|
fh.write('# Original content')
|
||||||
|
fh.flush()
|
||||||
|
mod = imp.load_source('mozwebidlcodegen.fakemodule', fh.name)
|
||||||
|
mod.__file__ = fake_path
|
||||||
|
|
||||||
|
args = self._get_manager_args()
|
||||||
|
m1 = WebIDLCodegenManager(**args)
|
||||||
|
with MockedOpen({fake_path: '# Original content'}):
|
||||||
|
old_exists = os.path.exists
|
||||||
|
try:
|
||||||
|
def exists(p):
|
||||||
|
if p == fake_path:
|
||||||
|
return True
|
||||||
|
return old_exists(p)
|
||||||
|
|
||||||
|
os.path.exists = exists
|
||||||
|
|
||||||
|
result = m1.generate_build_files()
|
||||||
|
l = len(result.inputs)
|
||||||
|
|
||||||
|
with open(fake_path, 'wt') as fh:
|
||||||
|
fh.write('# Modified content')
|
||||||
|
|
||||||
|
m2 = WebIDLCodegenManager(**args)
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
self.assertEqual(len(result.inputs), l)
|
||||||
|
|
||||||
|
result = m2.generate_build_files()
|
||||||
|
self.assertEqual(len(result.inputs), 0)
|
||||||
|
finally:
|
||||||
|
os.path.exists = old_exists
|
||||||
|
del sys.modules['mozwebidlcodegen.fakemodule']
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -2,89 +2,23 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
# 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/.
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
# Do NOT export this library. We don't actually want our test code
|
|
||||||
# being added to libxul or anything.
|
|
||||||
|
|
||||||
# pymake can't handle descending into dom/bindings several times simultaneously
|
|
||||||
ifdef .PYMAKE
|
|
||||||
.NOTPARALLEL:
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Need this for $(test_webidl_files)
|
|
||||||
include ../webidlsrcs.mk
|
include ../webidlsrcs.mk
|
||||||
|
|
||||||
# But the webidl actually lives in our parent dir
|
# $(test_sources) comes from webidlsrcs.mk.
|
||||||
test_webidl_files := $(addprefix ../,$(test_webidl_files))
|
# TODO Update this variable in backend.mk.
|
||||||
# Store the actual locations of our source preprocessed files, so we
|
CPPSRCS += $(addprefix ../,$(test_sources))
|
||||||
# can depend on them sanely.
|
|
||||||
source_preprocessed_test_webidl_files := $(addprefix $(srcdir)/,$(preprocessed_test_webidl_files))
|
|
||||||
preprocessed_test_webidl_files := $(addprefix ../,$(preprocessed_test_webidl_files))
|
|
||||||
|
|
||||||
CPPSRCS += \
|
|
||||||
$(subst .webidl,Binding.cpp,$(test_webidl_files)) \
|
|
||||||
$(subst .webidl,Binding.cpp,$(preprocessed_test_webidl_files)) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
# If you change bindinggen_dependencies here, change it in
|
|
||||||
# dom/bindings/Makefile.in too. But note that we include ../Makefile
|
|
||||||
# here manually, since $(GLOBAL_DEPS) won't cover it.
|
|
||||||
bindinggen_dependencies := \
|
|
||||||
../BindingGen.py \
|
|
||||||
../Bindings.conf \
|
|
||||||
../Configuration.py \
|
|
||||||
../Codegen.py \
|
|
||||||
../ParserResults.pkl \
|
|
||||||
../parser/WebIDL.py \
|
|
||||||
../Makefile \
|
|
||||||
$(GLOBAL_DEPS) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
ifdef GNU_CC
|
ifdef GNU_CC
|
||||||
CXXFLAGS += -Wno-uninitialized
|
OS_CXXFLAGS += -Wno-uninitialized
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Bug 932082 tracks having bindings use namespaced includes.
|
||||||
|
LOCAL_INCLUDES += -I$(DIST)/include/mozilla/dom -I..
|
||||||
|
|
||||||
# Include rules.mk before any of our targets so our first target is coming from
|
# Include rules.mk before any of our targets so our first target is coming from
|
||||||
# rules.mk and running make with no target in this dir does the right thing.
|
# rules.mk and running make with no target in this dir does the right thing.
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
$(CPPSRCS): .BindingGen
|
|
||||||
|
|
||||||
.BindingGen: $(bindinggen_dependencies) \
|
|
||||||
$(test_webidl_files) \
|
|
||||||
$(source_preprocessed_test_webidl_files) \
|
|
||||||
$(NULL)
|
|
||||||
# The export phase in dom/bindings is what actually looks at
|
|
||||||
# dependencies and regenerates things as needed, so just go ahead and
|
|
||||||
# make that phase here. Also make our example interface files. If the
|
|
||||||
# target used here ever changes, change the conditional around
|
|
||||||
# $(CPPSRCS) in dom/bindings/Makefile.in.
|
|
||||||
$(MAKE) -C .. export TestExampleInterface-example TestExampleProxyInterface-example
|
|
||||||
@$(TOUCH) $@
|
|
||||||
|
|
||||||
check::
|
check::
|
||||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||||
$(PLY_INCLUDE) $(srcdir)/../parser/runtests.py
|
$(PLY_INCLUDE) $(srcdir)/../parser/runtests.py
|
||||||
|
|
||||||
# Since we define MOCHITEST_FILES, config/makefiles/mochitest.mk goes ahead and
|
|
||||||
# sets up a rule with libs:: in itm which makes our .DEFAULT_TARGET be "libs".
|
|
||||||
# Then ruls.mk does |.DEFAULT_TARGET ?= default| which leaves it as "libs". So
|
|
||||||
# if we make without an explicit target in this directory, we try to make
|
|
||||||
# "libs", but with a $(MAKECMDGOALS) of empty string. And then rules.mk
|
|
||||||
# helpfully does not include our *.o.pp files, since it includes them only if
|
|
||||||
# filtering some stuff out from $(MAKECMDGOALS) leaves it nonempty. The upshot
|
|
||||||
# is that if some headers change and we run make in this dir without an explicit
|
|
||||||
# target things don't get rebuilt.
|
|
||||||
#
|
|
||||||
# On the other hand, if we set .DEFAULT_TARGET to "default" explicitly here,
|
|
||||||
# then rules.mk will reinvoke make with "export" and "libs" but this time hey
|
|
||||||
# will be passed as explicit targets, show up in $(MAKECMDGOALS), and things
|
|
||||||
# will work. Do this at the end of our Makefile so the rest of the build system
|
|
||||||
# does not get a chance to muck with it after we set it.
|
|
||||||
.DEFAULT_GOAL := default
|
|
||||||
|
|
||||||
# Make sure to add .BindingGen to GARBAGE so we'll rebuild our example
|
|
||||||
# files if someone goes through and deletes GARBAGE all over, which
|
|
||||||
# will delete example files from our parent dir.
|
|
||||||
GARBAGE += \
|
|
||||||
.BindingGen \
|
|
||||||
$(NULL)
|
|
||||||
|
|
|
@ -14,9 +14,20 @@ MOCHITEST_MANIFESTS += ['mochitest.ini']
|
||||||
|
|
||||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
||||||
|
|
||||||
|
TEST_WEBIDL_FILES += [
|
||||||
|
'TestDictionary.webidl',
|
||||||
|
'TestJSImplInheritanceGen.webidl',
|
||||||
|
'TestTypedef.webidl',
|
||||||
|
]
|
||||||
|
|
||||||
|
PREPROCESSED_TEST_WEBIDL_FILES += [
|
||||||
|
'TestCodeGen.webidl',
|
||||||
|
'TestExampleGen.webidl',
|
||||||
|
'TestJSImplGen.webidl',
|
||||||
|
]
|
||||||
|
|
||||||
LOCAL_INCLUDES += [
|
LOCAL_INCLUDES += [
|
||||||
'/dom/bindings',
|
'/dom/bindings',
|
||||||
'/js/xpconnect/src',
|
'/js/xpconnect/src',
|
||||||
'/js/xpconnect/wrappers',
|
'/js/xpconnect/wrappers',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||||
|
* vim: sw=2 ts=2 sts=2 et filetype=javascript
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
let Promise =
|
||||||
|
SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
|
||||||
|
|
||||||
|
let bluetoothManager;
|
||||||
|
|
||||||
|
/* Get mozSettings value specified by @aKey.
|
||||||
|
*
|
||||||
|
* Resolve if that mozSettings value is retrieved successfully, reject
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params:
|
||||||
|
* The corresponding mozSettings value of the key.
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @param aKey
|
||||||
|
* A string.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function getSettings(aKey) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let request = navigator.mozSettings.createLock().get(aKey);
|
||||||
|
request.addEventListener("success", function(aEvent) {
|
||||||
|
ok(true, "getSettings(" + aKey + ")");
|
||||||
|
deferred.resolve(aEvent.target.result[aKey]);
|
||||||
|
});
|
||||||
|
request.addEventListener("error", function() {
|
||||||
|
ok(false, "getSettings(" + aKey + ")");
|
||||||
|
deferred.reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set mozSettings values.
|
||||||
|
*
|
||||||
|
* Resolve if that mozSettings value is set successfully, reject otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params: (none)
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @param aSettings
|
||||||
|
* An object of format |{key1: value1, key2: value2, ...}|.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function setSettings(aSettings) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let request = navigator.mozSettings.createLock().set(aSettings);
|
||||||
|
request.addEventListener("success", function() {
|
||||||
|
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
request.addEventListener("error", function() {
|
||||||
|
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
|
||||||
|
deferred.reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get mozSettings value of 'bluetooth.enabled'.
|
||||||
|
*
|
||||||
|
* Resolve if that mozSettings value is retrieved successfully, reject
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params:
|
||||||
|
* A boolean value.
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function getBluetoothEnabled() {
|
||||||
|
return getSettings("bluetooth.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set mozSettings value of 'bluetooth.enabled'.
|
||||||
|
*
|
||||||
|
* Resolve if that mozSettings value is set successfully, reject otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params: (none)
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @param aEnabled
|
||||||
|
* A boolean value.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function setBluetoothEnabled(aEnabled) {
|
||||||
|
let obj = {};
|
||||||
|
obj["bluetooth.enabled"] = aEnabled;
|
||||||
|
return setSettings(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push required permissions and test if |navigator.mozBluetooth| exists.
|
||||||
|
* Resolve if it does, reject otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params:
|
||||||
|
* bluetoothManager -- an reference to navigator.mozBluetooth.
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @param aPermissions
|
||||||
|
* Additional permissions to push before any test cases. Could be either
|
||||||
|
* a string or an array of strings.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function ensureBluetoothManager(aPermissions) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let permissions = ["bluetooth"];
|
||||||
|
if (aPermissions) {
|
||||||
|
if (Array.isArray(aPermissions)) {
|
||||||
|
permissions = permissions.concat(aPermissions);
|
||||||
|
} else if (typeof aPermissions == "string") {
|
||||||
|
permissions.push(aPermissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = [];
|
||||||
|
for (let perm of permissions) {
|
||||||
|
obj.push({
|
||||||
|
"type": perm,
|
||||||
|
"allow": 1,
|
||||||
|
"context": document,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SpecialPowers.pushPermissions(obj, function() {
|
||||||
|
ok(true, "permissions pushed: " + JSON.stringify(permissions));
|
||||||
|
|
||||||
|
bluetoothManager = window.navigator.mozBluetooth;
|
||||||
|
log("navigator.mozBluetooth is " +
|
||||||
|
(bluetoothManager ? "available" : "unavailable"));
|
||||||
|
|
||||||
|
if (bluetoothManager instanceof BluetoothManager) {
|
||||||
|
deferred.resolve(bluetoothManager);
|
||||||
|
} else {
|
||||||
|
deferred.reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for one named BluetoothManager event.
|
||||||
|
*
|
||||||
|
* Resolve if that named event occurs. Never reject.
|
||||||
|
*
|
||||||
|
* Forfill params: the DOMEvent passed.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function waitForManagerEvent(aEventName) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
bluetoothManager.addEventListener(aEventName, function onevent(aEvent) {
|
||||||
|
bluetoothManager.removeEventListener(aEventName, onevent);
|
||||||
|
|
||||||
|
ok(true, "BluetoothManager event '" + aEventName + "' got.");
|
||||||
|
deferred.resolve(aEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convenient function for setBluetoothEnabled and waitForManagerEvent
|
||||||
|
* combined.
|
||||||
|
*
|
||||||
|
* Resolve if that named event occurs. Reject if we can't set settings.
|
||||||
|
*
|
||||||
|
* Forfill params: the DOMEvent passed.
|
||||||
|
* Reject params: (none)
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function setBluetoothEnabledAndWait(aEnabled) {
|
||||||
|
return setBluetoothEnabled(aEnabled)
|
||||||
|
.then(waitForManagerEvent.bind(null, aEnabled ? "enabled" : "disabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get default adapter.
|
||||||
|
*
|
||||||
|
* Resolve if that default adapter is got, reject otherwise.
|
||||||
|
*
|
||||||
|
* Forfill params: a BluetoothAdapter instance.
|
||||||
|
* Reject params: a DOMError, or null if if there is no adapter ready yet.
|
||||||
|
*
|
||||||
|
* @return A deferred promise.
|
||||||
|
*/
|
||||||
|
function getDefaultAdapter() {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let request = bluetoothManager.getDefaultAdapter();
|
||||||
|
request.onsuccess = function(aEvent) {
|
||||||
|
let adapter = aEvent.target.result;
|
||||||
|
if (!(adapter instanceof BluetoothAdapter)) {
|
||||||
|
ok(false, "no BluetoothAdapter ready yet.");
|
||||||
|
deferred.reject(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(true, "BluetoothAdapter got.");
|
||||||
|
// TODO: We have an adapter instance now, but some of its attributes may
|
||||||
|
// still remain unassigned/out-dated. Here we waste a few seconds to
|
||||||
|
// wait for the property changed events.
|
||||||
|
//
|
||||||
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=932914
|
||||||
|
window.setTimeout(function() {
|
||||||
|
deferred.resolve(adapter);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
request.onerror = function(aEvent) {
|
||||||
|
ok(false, "Failed to get default adapter.");
|
||||||
|
deferred.reject(aEvent.target.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flush permission settings and call |finish()|.
|
||||||
|
*/
|
||||||
|
function cleanUp() {
|
||||||
|
SpecialPowers.flushPermissions(function() {
|
||||||
|
// Use ok here so that we have at least one test run.
|
||||||
|
ok(true, "permissions flushed");
|
||||||
|
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBluetoothTestBase(aPermissions, aTestCaseMain) {
|
||||||
|
ensureBluetoothManager(aPermissions)
|
||||||
|
.then(aTestCaseMain)
|
||||||
|
.then(cleanUp, function() {
|
||||||
|
ok(false, "Unhandled rejected promise.");
|
||||||
|
cleanUp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBluetoothTest(aReenable, aTestCaseMain) {
|
||||||
|
startBluetoothTestBase(["settings-read", "settings-write"], function() {
|
||||||
|
let origEnabled, needEnable;
|
||||||
|
|
||||||
|
return getBluetoothEnabled()
|
||||||
|
.then(function(aEnabled) {
|
||||||
|
origEnabled = aEnabled;
|
||||||
|
needEnable = !aEnabled;
|
||||||
|
log("Original 'bluetooth.enabled' is " + origEnabled);
|
||||||
|
|
||||||
|
if (aEnabled && aReenable) {
|
||||||
|
log(" Disable 'bluetooth.enabled' ...");
|
||||||
|
needEnable = true;
|
||||||
|
return setBluetoothEnabledAndWait(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
if (needEnable) {
|
||||||
|
log(" Enable 'bluetooth.enabled' ...");
|
||||||
|
return setBluetoothEnabledAndWait(true)
|
||||||
|
.then(waitForManagerEvent.bind(null, "adapteradded"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(getDefaultAdapter)
|
||||||
|
.then(aTestCaseMain)
|
||||||
|
.then(function() {
|
||||||
|
if (!origEnabled) {
|
||||||
|
return setBluetoothEnabledAndWait(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
[DEFAULT]
|
||||||
|
b2g = true
|
||||||
|
browser = false
|
||||||
|
qemu = true
|
||||||
|
|
||||||
|
[test_dom_BluetoothManager_enabled.js]
|
||||||
|
[test_dom_BluetoothManager_adapteradded.js]
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||||
|
* vim: sw=2 ts=2 sts=2 et filetype=javascript
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
MARIONETTE_TIMEOUT = 60000;
|
||||||
|
MARIONETTE_HEAD_JS = 'head.js';
|
||||||
|
|
||||||
|
// https://github.com/mozilla-b2g/platform_external_qemu/blob/master/vl-android.c#L765
|
||||||
|
// static int bt_hci_parse(const char *str) {
|
||||||
|
// ...
|
||||||
|
// bdaddr.b[0] = 0x52;
|
||||||
|
// bdaddr.b[1] = 0x54;
|
||||||
|
// bdaddr.b[2] = 0x00;
|
||||||
|
// bdaddr.b[3] = 0x12;
|
||||||
|
// bdaddr.b[4] = 0x34;
|
||||||
|
// bdaddr.b[5] = 0x56 + nb_hcis;
|
||||||
|
const EMULATOR_ADDRESS = "56:34:12:00:54:52";
|
||||||
|
|
||||||
|
// $ adb shell hciconfig /dev/ttyS2 name
|
||||||
|
// hci0: Type: BR/EDR Bus: UART
|
||||||
|
// BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0
|
||||||
|
// Name: 'Full Android on Emulator'
|
||||||
|
const EMULATOR_NAME = "Full Android on Emulator";
|
||||||
|
|
||||||
|
// $ adb shell hciconfig /dev/ttyS2 class
|
||||||
|
// hci0: Type: BR/EDR Bus: UART
|
||||||
|
// BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0
|
||||||
|
// Class: 0x58020c
|
||||||
|
// Service Classes: Capturing, Object Transfer, Telephony
|
||||||
|
// Device Class: Phone, Smart phone
|
||||||
|
const EMULATOR_CLASS = 0x58020c;
|
||||||
|
|
||||||
|
startBluetoothTest(true, function testCaseMain(aAdapter) {
|
||||||
|
log("Checking adapter attributes ...");
|
||||||
|
|
||||||
|
is(aAdapter.name, EMULATOR_NAME, "adapter.name");
|
||||||
|
is(aAdapter.class, EMULATOR_CLASS, "adapter.class");
|
||||||
|
is(aAdapter.address, EMULATOR_ADDRESS, "adapter.address");
|
||||||
|
is(aAdapter.discovering, false, "adapter.discovering");
|
||||||
|
is(aAdapter.discoverable, false, "adapter.discoverable");
|
||||||
|
is(aAdapter.discoverableTimeout, 120, "adapter.discoverableTimeout");
|
||||||
|
});
|
|
@ -0,0 +1,64 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||||
|
* vim: sw=2 ts=2 sts=2 et filetype=javascript
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
MARIONETTE_TIMEOUT = 60000;
|
||||||
|
MARIONETTE_HEAD_JS = 'head.js';
|
||||||
|
|
||||||
|
let enabledEventReceived;
|
||||||
|
function onEnabled() {
|
||||||
|
enabledEventReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let disabledEventReceived;
|
||||||
|
function onDisabled() {
|
||||||
|
disabledEventReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(aEnabled) {
|
||||||
|
log("Testing 'bluetooth.enabled' => " + aEnabled);
|
||||||
|
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
enabledEventReceived = false;
|
||||||
|
disabledEventReceived = false;
|
||||||
|
|
||||||
|
setBluetoothEnabled(aEnabled).then(function() {
|
||||||
|
log(" Settings set. Waiting 3 seconds and examine results.");
|
||||||
|
|
||||||
|
window.setTimeout(function() {
|
||||||
|
is(bluetoothManager.enabled, aEnabled, "bluetoothManager.enabled");
|
||||||
|
is(enabledEventReceived, aEnabled, "enabledEventReceived");
|
||||||
|
is(disabledEventReceived, !aEnabled, "disabledEventReceived");
|
||||||
|
|
||||||
|
if (bluetoothManager.enabled === aEnabled &&
|
||||||
|
enabledEventReceived === aEnabled &&
|
||||||
|
disabledEventReceived === !aEnabled) {
|
||||||
|
deferred.resolve();
|
||||||
|
} else {
|
||||||
|
deferred.reject();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
startBluetoothTestBase(["settings-read", "settings-write"],
|
||||||
|
function testCaseMain() {
|
||||||
|
bluetoothManager.addEventListener("enabled", onEnabled);
|
||||||
|
bluetoothManager.addEventListener("disabled", onDisabled);
|
||||||
|
|
||||||
|
return getBluetoothEnabled()
|
||||||
|
.then(function(aEnabled) {
|
||||||
|
log("Original 'bluetooth.enabled' is " + aEnabled);
|
||||||
|
// Set to !aEnabled and reset back to aEnabled.
|
||||||
|
return test(!aEnabled).then(test.bind(null, aEnabled));
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
bluetoothManager.removeEventListener("enabled", onEnabled);
|
||||||
|
bluetoothManager.removeEventListener("disabled", onDisabled);
|
||||||
|
});
|
||||||
|
});
|
|
@ -65,8 +65,8 @@ BrowserElementParentFactory.prototype = {
|
||||||
// alive for as long as its frame element lives.
|
// alive for as long as its frame element lives.
|
||||||
this._bepMap = new WeakMap();
|
this._bepMap = new WeakMap();
|
||||||
|
|
||||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
|
Services.obs.addObserver(this, 'remote-browser-shown', /* ownsWeak = */ true);
|
||||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', /* ownsWeak = */ true);
|
Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
|
||||||
},
|
},
|
||||||
|
|
||||||
_browserFramesPrefEnabled: function() {
|
_browserFramesPrefEnabled: function() {
|
||||||
|
@ -79,11 +79,19 @@ BrowserElementParentFactory.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_observeInProcessBrowserFrameShown: function(frameLoader) {
|
_observeInProcessBrowserFrameShown: function(frameLoader) {
|
||||||
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
|
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
debug("In-process browser frame shown " + frameLoader);
|
debug("In-process browser frame shown " + frameLoader);
|
||||||
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ false);
|
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ false);
|
||||||
},
|
},
|
||||||
|
|
||||||
_observeRemoteBrowserFrameShown: function(frameLoader) {
|
_observeRemoteBrowserFrameShown: function(frameLoader) {
|
||||||
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
|
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
debug("Remote browser frame shown " + frameLoader);
|
debug("Remote browser frame shown " + frameLoader);
|
||||||
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ true);
|
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ true);
|
||||||
},
|
},
|
||||||
|
@ -103,10 +111,10 @@ BrowserElementParentFactory.prototype = {
|
||||||
this._init();
|
this._init();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'remote-browser-frame-shown':
|
case 'remote-browser-shown':
|
||||||
this._observeRemoteBrowserFrameShown(subject);
|
this._observeRemoteBrowserFrameShown(subject);
|
||||||
break;
|
break;
|
||||||
case 'in-process-browser-or-app-frame-shown':
|
case 'inprocess-browser-shown':
|
||||||
this._observeInProcessBrowserFrameShown(subject);
|
this._observeInProcessBrowserFrameShown(subject);
|
||||||
break;
|
break;
|
||||||
case 'content-document-global-created':
|
case 'content-document-global-created':
|
||||||
|
|
|
@ -198,6 +198,7 @@ function createDOMDownloadObject(aWindow, aDownload) {
|
||||||
|
|
||||||
function DOMDownloadImpl() {
|
function DOMDownloadImpl() {
|
||||||
debug("DOMDownloadImpl constructor ");
|
debug("DOMDownloadImpl constructor ");
|
||||||
|
|
||||||
this.wrappedJSObject = this;
|
this.wrappedJSObject = this;
|
||||||
this.totalBytes = 0;
|
this.totalBytes = 0;
|
||||||
this.currentBytes = 0;
|
this.currentBytes = 0;
|
||||||
|
@ -205,9 +206,11 @@ function DOMDownloadImpl() {
|
||||||
this.path = null;
|
this.path = null;
|
||||||
this.state = "stopped";
|
this.state = "stopped";
|
||||||
this.contentType = null;
|
this.contentType = null;
|
||||||
this.startTime = Date.now();
|
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
|
/* fields that require getters/setters */
|
||||||
|
this._startTime = new Date();
|
||||||
|
|
||||||
/* private fields */
|
/* private fields */
|
||||||
this.id = null;
|
this.id = null;
|
||||||
}
|
}
|
||||||
|
@ -244,6 +247,19 @@ DOMDownloadImpl.prototype = {
|
||||||
return this.__DOM_IMPL__.getEventHandler("onstatechange");
|
return this.__DOM_IMPL__.getEventHandler("onstatechange");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get startTime() {
|
||||||
|
return this._startTime;
|
||||||
|
},
|
||||||
|
|
||||||
|
set startTime(aStartTime) {
|
||||||
|
if (aStartTime instanceof Date) {
|
||||||
|
this._startTime = aStartTime;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._startTime = new Date(aStartTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_init: function(aWindow, aDownload) {
|
_init: function(aWindow, aDownload) {
|
||||||
this._window = aWindow;
|
this._window = aWindow;
|
||||||
this.id = aDownload.id;
|
this.id = aDownload.id;
|
||||||
|
|
|
@ -22,11 +22,6 @@ function testSteps()
|
||||||
objectStore.add(Bob);
|
objectStore.add(Bob);
|
||||||
yield undefined;
|
yield undefined;
|
||||||
|
|
||||||
// This direct eval causes locals to be aliased, and thus allocated on
|
|
||||||
// the scope chain. Comment it out (and the workarounds below) and
|
|
||||||
// the test passes. Bug 943409.
|
|
||||||
eval('');
|
|
||||||
|
|
||||||
db.transaction("foo", "readwrite").objectStore("foo")
|
db.transaction("foo", "readwrite").objectStore("foo")
|
||||||
.index("name").openCursor().onsuccess = function(event) {
|
.index("name").openCursor().onsuccess = function(event) {
|
||||||
event.target.transaction.oncomplete = continueToNextStep;
|
event.target.transaction.oncomplete = continueToNextStep;
|
||||||
|
|
|
@ -35,11 +35,6 @@ function testSteps()
|
||||||
let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
|
let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
|
||||||
objectStore.createIndex("name", "name", { unique: true });
|
objectStore.createIndex("name", "name", { unique: true });
|
||||||
|
|
||||||
// This direct eval causes locals to be aliased, and thus allocated on
|
|
||||||
// the scope chain. Comment it out (and the workarounds below) and
|
|
||||||
// the test passes. Bug 943409.
|
|
||||||
eval('');
|
|
||||||
|
|
||||||
for (let i = 0; i < objectStoreData.length - 1; i++) {
|
for (let i = 0; i < objectStoreData.length - 1; i++) {
|
||||||
objectStore.add(objectStoreData[i]);
|
objectStore.add(objectStoreData[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,6 @@ function testSteps()
|
||||||
|
|
||||||
event.target.onsuccess = continueToNextStep;
|
event.target.onsuccess = continueToNextStep;
|
||||||
|
|
||||||
// Bug 943409.
|
|
||||||
eval('');
|
|
||||||
|
|
||||||
for (let objectStoreIndex in objectStoreData) {
|
for (let objectStoreIndex in objectStoreData) {
|
||||||
const objectStoreInfo = objectStoreData[objectStoreIndex];
|
const objectStoreInfo = objectStoreData[objectStoreIndex];
|
||||||
let objectStore = db.createObjectStore(objectStoreInfo.name,
|
let objectStore = db.createObjectStore(objectStoreInfo.name,
|
||||||
|
|
|
@ -18,9 +18,6 @@ function testSteps()
|
||||||
let db = event.target.result;
|
let db = event.target.result;
|
||||||
db.onerror = errorHandler;
|
db.onerror = errorHandler;
|
||||||
|
|
||||||
// Bug 943409.
|
|
||||||
eval('');
|
|
||||||
|
|
||||||
for each (let autoIncrement in [false, true]) {
|
for each (let autoIncrement in [false, true]) {
|
||||||
let objectStore =
|
let objectStore =
|
||||||
db.createObjectStore(autoIncrement, { keyPath: "id",
|
db.createObjectStore(autoIncrement, { keyPath: "id",
|
||||||
|
|
|
@ -44,8 +44,8 @@ this.Keyboard = {
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function keyboardInit() {
|
init: function keyboardInit() {
|
||||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||||
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
|
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
|
||||||
|
|
||||||
for (let name of this._messageNames)
|
for (let name of this._messageNames)
|
||||||
|
@ -63,6 +63,10 @@ this.Keyboard = {
|
||||||
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', { 'type': 'blur' });
|
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', { 'type': 'blur' });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
|
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initFormsFrameScript(mm);
|
this.initFormsFrameScript(mm);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1502,6 +1502,33 @@ ContentChild::RecvNotifyPhoneStateChange(const nsString& aState)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ContentChild::AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aObserver, "null idle observer");
|
||||||
|
// Make sure aObserver isn't released while we wait for the parent
|
||||||
|
aObserver->AddRef();
|
||||||
|
SendAddIdleObserver(reinterpret_cast<uint64_t>(aObserver), aIdleTimeInS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ContentChild::RemoveIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aObserver, "null idle observer");
|
||||||
|
SendRemoveIdleObserver(reinterpret_cast<uint64_t>(aObserver), aIdleTimeInS);
|
||||||
|
aObserver->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ContentChild::RecvNotifyIdleObserver(const uint64_t& aObserver,
|
||||||
|
const nsCString& aTopic,
|
||||||
|
const nsString& aTimeStr)
|
||||||
|
{
|
||||||
|
nsIObserver* observer = reinterpret_cast<nsIObserver*>(aObserver);
|
||||||
|
observer->Observe(nullptr, aTopic.get(), aTimeStr.get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ContentChild::RecvLoadAndRegisterSheet(const URIParams& aURI, const uint32_t& aType)
|
ContentChild::RecvLoadAndRegisterSheet(const URIParams& aURI, const uint32_t& aType)
|
||||||
{
|
{
|
||||||
|
|
|
@ -240,6 +240,12 @@ public:
|
||||||
virtual bool RecvUnregisterSheet(const URIParams& aURI, const uint32_t& aType);
|
virtual bool RecvUnregisterSheet(const URIParams& aURI, const uint32_t& aType);
|
||||||
|
|
||||||
virtual bool RecvNotifyPhoneStateChange(const nsString& state);
|
virtual bool RecvNotifyPhoneStateChange(const nsString& state);
|
||||||
|
|
||||||
|
void AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS);
|
||||||
|
void RemoveIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS);
|
||||||
|
virtual bool RecvNotifyIdleObserver(const uint64_t& aObserver,
|
||||||
|
const nsCString& aTopic,
|
||||||
|
const nsString& aData);
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
gfxIntSize GetScreenSize() { return mScreenSize; }
|
gfxIntSize GetScreenSize() { return mScreenSize; }
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
#include "nsIDOMWindow.h"
|
#include "nsIDOMWindow.h"
|
||||||
#include "nsIExternalProtocolService.h"
|
#include "nsIExternalProtocolService.h"
|
||||||
#include "nsIFilePicker.h"
|
#include "nsIFilePicker.h"
|
||||||
|
#include "nsIIdleService.h"
|
||||||
#include "nsIMemoryReporter.h"
|
#include "nsIMemoryReporter.h"
|
||||||
#include "nsIMozBrowserFrame.h"
|
#include "nsIMozBrowserFrame.h"
|
||||||
#include "nsIMutable.h"
|
#include "nsIMutable.h"
|
||||||
|
@ -1082,6 +1083,8 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
|
||||||
obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nullptr);
|
obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mIdleListeners.Clear();
|
||||||
|
|
||||||
// If the child process was terminated due to a SIGKIL, ShutDownProcess
|
// If the child process was terminated due to a SIGKIL, ShutDownProcess
|
||||||
// might not have been called yet. We must call it to ensure that our
|
// might not have been called yet. We must call it to ensure that our
|
||||||
// channel is closed, etc.
|
// channel is closed, etc.
|
||||||
|
@ -3155,5 +3158,47 @@ ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ContentParent::RecvAddIdleObserver(const uint64_t& aObserver, const uint32_t& aIdleTimeInS)
|
||||||
|
{
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIIdleService> idleService =
|
||||||
|
do_GetService("@mozilla.org/widget/idleservice;1", &rv);
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
|
||||||
|
nsCOMPtr<ParentIdleListener> listener = new ParentIdleListener(this, aObserver);
|
||||||
|
mIdleListeners.Put(aObserver, listener);
|
||||||
|
idleService->AddIdleObserver(listener, aIdleTimeInS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ContentParent::RecvRemoveIdleObserver(const uint64_t& aObserver, const uint32_t& aIdleTimeInS)
|
||||||
|
{
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIIdleService> idleService =
|
||||||
|
do_GetService("@mozilla.org/widget/idleservice;1", &rv);
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
|
||||||
|
nsCOMPtr<ParentIdleListener> listener;
|
||||||
|
bool found = mIdleListeners.Get(aObserver, &listener);
|
||||||
|
if (found) {
|
||||||
|
mIdleListeners.Remove(aObserver);
|
||||||
|
idleService->RemoveIdleObserver(listener, aIdleTimeInS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS1(ParentIdleListener, nsIObserver)
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ParentIdleListener::Observe(nsISupports*, const char* aTopic, const PRUnichar* aData) {
|
||||||
|
mozilla::unused << mParent->SendNotifyIdleObserver(mObserver,
|
||||||
|
nsDependentCString(aTopic),
|
||||||
|
nsDependentString(aData));
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
#include "mozilla/LinkedList.h"
|
#include "mozilla/LinkedList.h"
|
||||||
#include "mozilla/StaticPtr.h"
|
#include "mozilla/StaticPtr.h"
|
||||||
|
|
||||||
|
#include "nsDataHashtable.h"
|
||||||
#include "nsFrameMessageManager.h"
|
#include "nsFrameMessageManager.h"
|
||||||
|
#include "nsHashKeys.h"
|
||||||
#include "nsIObserver.h"
|
#include "nsIObserver.h"
|
||||||
#include "nsIThreadInternal.h"
|
#include "nsIThreadInternal.h"
|
||||||
#include "nsIDOMGeoPositionCallback.h"
|
#include "nsIDOMGeoPositionCallback.h"
|
||||||
|
@ -27,7 +29,7 @@ class mozIApplication;
|
||||||
class nsConsoleService;
|
class nsConsoleService;
|
||||||
class nsIDOMBlob;
|
class nsIDOMBlob;
|
||||||
class nsIMemoryReporter;
|
class nsIMemoryReporter;
|
||||||
template<class KeyClass,class DataType> class nsDataHashtable;
|
class ParentIdleListener;
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
|
@ -510,6 +512,9 @@ private:
|
||||||
|
|
||||||
virtual void ProcessingError(Result what) MOZ_OVERRIDE;
|
virtual void ProcessingError(Result what) MOZ_OVERRIDE;
|
||||||
|
|
||||||
|
virtual bool RecvAddIdleObserver(const uint64_t& observerId, const uint32_t& aIdleTimeInS);
|
||||||
|
virtual bool RecvRemoveIdleObserver(const uint64_t& observerId, const uint32_t& aIdleTimeInS);
|
||||||
|
|
||||||
// If you add strong pointers to cycle collected objects here, be sure to
|
// If you add strong pointers to cycle collected objects here, be sure to
|
||||||
// release these objects in ShutDownProcess. See the comment there for more
|
// release these objects in ShutDownProcess. See the comment there for more
|
||||||
// details.
|
// details.
|
||||||
|
@ -559,9 +564,25 @@ private:
|
||||||
|
|
||||||
nsRefPtr<nsConsoleService> mConsoleService;
|
nsRefPtr<nsConsoleService> mConsoleService;
|
||||||
nsConsoleService* GetConsoleService();
|
nsConsoleService* GetConsoleService();
|
||||||
|
|
||||||
|
nsDataHashtable<nsUint64HashKey, nsCOMPtr<ParentIdleListener> > mIdleListeners;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
||||||
|
class ParentIdleListener : public nsIObserver {
|
||||||
|
public:
|
||||||
|
NS_DECL_ISUPPORTS
|
||||||
|
NS_DECL_NSIOBSERVER
|
||||||
|
|
||||||
|
ParentIdleListener(mozilla::dom::ContentParent* aParent, uint64_t aObserver)
|
||||||
|
: mParent(aParent), mObserver(aObserver)
|
||||||
|
{}
|
||||||
|
virtual ~ParentIdleListener() {}
|
||||||
|
private:
|
||||||
|
nsRefPtr<mozilla::dom::ContentParent> mParent;
|
||||||
|
uint64_t mObserver;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -325,6 +325,11 @@ child:
|
||||||
async UnregisterSheet(URIParams uri, uint32_t type);
|
async UnregisterSheet(URIParams uri, uint32_t type);
|
||||||
|
|
||||||
NotifyPhoneStateChange(nsString newState);
|
NotifyPhoneStateChange(nsString newState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify idle observers in the child
|
||||||
|
*/
|
||||||
|
NotifyIdleObserver(uint64_t observerId, nsCString topic, nsString str);
|
||||||
parent:
|
parent:
|
||||||
/**
|
/**
|
||||||
* Tell the content process some attributes of itself. This is
|
* Tell the content process some attributes of itself. This is
|
||||||
|
@ -502,6 +507,8 @@ parent:
|
||||||
bool isAudio,
|
bool isAudio,
|
||||||
bool isVideo);
|
bool isVideo);
|
||||||
|
|
||||||
|
AddIdleObserver(uint64_t observerId, uint32_t idleTimeInS);
|
||||||
|
RemoveIdleObserver(uint64_t observerId, uint32_t idleTimeInS);
|
||||||
both:
|
both:
|
||||||
AsyncMessage(nsString aMessage, ClonedMessageData aData,
|
AsyncMessage(nsString aMessage, ClonedMessageData aData,
|
||||||
CpowEntry[] aCpows, Principal aPrincipal);
|
CpowEntry[] aCpows, Principal aPrincipal);
|
||||||
|
|
|
@ -246,7 +246,7 @@ PreallocatedProcessManagerImpl::DelayedNuwaFork()
|
||||||
mPreallocateAppProcessTask = nullptr;
|
mPreallocateAppProcessTask = nullptr;
|
||||||
|
|
||||||
if (!mIsNuwaReady) {
|
if (!mIsNuwaReady) {
|
||||||
if (!mPreallocatedAppProcess && !mShutdown) {
|
if (!mPreallocatedAppProcess && !mShutdown && mEnabled) {
|
||||||
mPreallocatedAppProcess = ContentParent::RunNuwaProcess();
|
mPreallocatedAppProcess = ContentParent::RunNuwaProcess();
|
||||||
}
|
}
|
||||||
// else mPreallocatedAppProcess is starting. It will NuwaFork() when ready.
|
// else mPreallocatedAppProcess is starting. It will NuwaFork() when ready.
|
||||||
|
|
|
@ -602,7 +602,7 @@ ParticularProcessPriorityManager::Init()
|
||||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||||
if (os) {
|
if (os) {
|
||||||
os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true);
|
os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true);
|
||||||
os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ true);
|
os->AddObserver(this, "remote-browser-shown", /* ownsWeak */ true);
|
||||||
os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
|
os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
|
||||||
os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true);
|
os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true);
|
||||||
}
|
}
|
||||||
|
@ -674,7 +674,7 @@ ParticularProcessPriorityManager::Observe(nsISupports* aSubject,
|
||||||
|
|
||||||
if (topic.EqualsLiteral("audio-channel-process-changed")) {
|
if (topic.EqualsLiteral("audio-channel-process-changed")) {
|
||||||
OnAudioChannelProcessChanged(aSubject);
|
OnAudioChannelProcessChanged(aSubject);
|
||||||
} else if (topic.EqualsLiteral("remote-browser-frame-shown")) {
|
} else if (topic.EqualsLiteral("remote-browser-shown")) {
|
||||||
OnRemoteBrowserFrameShown(aSubject);
|
OnRemoteBrowserFrameShown(aSubject);
|
||||||
} else if (topic.EqualsLiteral("ipc:browser-destroyed")) {
|
} else if (topic.EqualsLiteral("ipc:browser-destroyed")) {
|
||||||
OnTabParentDestroyed(aSubject);
|
OnTabParentDestroyed(aSubject);
|
||||||
|
@ -747,6 +747,13 @@ ParticularProcessPriorityManager::OnRemoteBrowserFrameShown(nsISupports* aSubjec
|
||||||
nsCOMPtr<nsIFrameLoader> fl = do_QueryInterface(aSubject);
|
nsCOMPtr<nsIFrameLoader> fl = do_QueryInterface(aSubject);
|
||||||
NS_ENSURE_TRUE_VOID(fl);
|
NS_ENSURE_TRUE_VOID(fl);
|
||||||
|
|
||||||
|
// Ignore notifications that aren't from a BrowserOrApp
|
||||||
|
bool isBrowserOrApp;
|
||||||
|
fl->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
|
||||||
|
if (!isBrowserOrApp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsITabParent> tp;
|
nsCOMPtr<nsITabParent> tp;
|
||||||
fl->GetTabParent(getter_AddRefs(tp));
|
fl->GetTabParent(getter_AddRefs(tp));
|
||||||
NS_ENSURE_TRUE_VOID(tp);
|
NS_ENSURE_TRUE_VOID(tp);
|
||||||
|
|
|
@ -41,4 +41,5 @@ qemu = true
|
||||||
[test_thread_subject.js]
|
[test_thread_subject.js]
|
||||||
[test_mmdb_new.js]
|
[test_mmdb_new.js]
|
||||||
[test_mmdb_setmessagedeliverybyid_sms.js]
|
[test_mmdb_setmessagedeliverybyid_sms.js]
|
||||||
|
[test_mmdb_foreachmatchedmmsdeliveryinfo.js]
|
||||||
[test_replace_short_message_type.js]
|
[test_replace_short_message_type.js]
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
MARIONETTE_TIMEOUT = 60000;
|
||||||
|
MARIONETTE_HEAD_JS = 'head.js';
|
||||||
|
|
||||||
|
// TODO: bug 943233 - passing jsm exported objects to |Promise.resolve| gets
|
||||||
|
// empty object in return.
|
||||||
|
let mmdb;
|
||||||
|
|
||||||
|
const PHONE_0 = "+15555215500";
|
||||||
|
const PHONE_1 = "+15555215501";
|
||||||
|
const PHONE_2 = "+15555215502";
|
||||||
|
const PHONE_2_NET = "5555215502";
|
||||||
|
const PHONE_3 = "+15555215503";
|
||||||
|
const PHONE_3_NET = "5555215503";
|
||||||
|
const EMAIL_1 = "foo@bar.com";
|
||||||
|
let deliveryInfo = [
|
||||||
|
{ receiver: PHONE_1 },
|
||||||
|
{ receiver: PHONE_2 },
|
||||||
|
{ receiver: PHONE_1 },
|
||||||
|
{ receiver: PHONE_2_NET },
|
||||||
|
{ receiver: PHONE_3_NET },
|
||||||
|
{ receiver: EMAIL_1 },
|
||||||
|
{ receiver: PHONE_3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function clearTraversed(aDeliveryInfo) {
|
||||||
|
for (let element of aDeliveryInfo) {
|
||||||
|
delete element.traversed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doTest(aMmdb, aNeedle, aVerifyFunc, aCount) {
|
||||||
|
log(" '" + aNeedle + "': " + aCount);
|
||||||
|
|
||||||
|
clearTraversed(deliveryInfo);
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
aMmdb.forEachMatchedMmsDeliveryInfo(deliveryInfo, aNeedle, function(aElement) {
|
||||||
|
ok(true, "checking " + aElement.receiver);
|
||||||
|
ok(!aElement.hasOwnProperty("traversed"), "element.traversed");
|
||||||
|
aVerifyFunc(aElement);
|
||||||
|
|
||||||
|
aElement.traversed = true;
|
||||||
|
++count;
|
||||||
|
});
|
||||||
|
is(count, aCount, "matched count");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNotFound(aMmdb) {
|
||||||
|
log("Testing unavailable");
|
||||||
|
|
||||||
|
// TODO: bug 943233 - passing jsm exported objects to |Promise.resolve| gets
|
||||||
|
// empty object in return.
|
||||||
|
aMmdb = mmdb;
|
||||||
|
|
||||||
|
doTest(aMmdb, PHONE_0, function(aElement) {
|
||||||
|
ok(false, "Should never have a match");
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return aMmdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDirectMatch(aMmdb) {
|
||||||
|
log("Testing direct matching");
|
||||||
|
|
||||||
|
// TODO: bug 943233 - passing jsm exported objects to |Promise.resolve| gets
|
||||||
|
// empty object in return.
|
||||||
|
aMmdb = mmdb;
|
||||||
|
|
||||||
|
for (let needle of [PHONE_1, EMAIL_1]) {
|
||||||
|
let count = deliveryInfo.reduce(function(aCount, aElement) {
|
||||||
|
return aElement.receiver == needle ? aCount + 1 : aCount;
|
||||||
|
}, 0);
|
||||||
|
doTest(aMmdb, needle, function(aElement) {
|
||||||
|
is(aElement.receiver, needle, "element.receiver");
|
||||||
|
}, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aMmdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPhoneMatch(aMmdb) {
|
||||||
|
log("Testing phone matching");
|
||||||
|
|
||||||
|
let verifyFunc = function(aValid, aElement) {
|
||||||
|
ok(aValid.indexOf(aElement.receiver) >= 0, "element.receiver");
|
||||||
|
};
|
||||||
|
// TODO: bug 943233 - passing jsm exported objects to |Promise.resolve| gets
|
||||||
|
// empty object in return.
|
||||||
|
aMmdb = mmdb;
|
||||||
|
|
||||||
|
let matchingGroups = [
|
||||||
|
[PHONE_2, PHONE_2_NET],
|
||||||
|
[PHONE_3, PHONE_3_NET],
|
||||||
|
];
|
||||||
|
for (let group of matchingGroups) {
|
||||||
|
for (let item of group) {
|
||||||
|
doTest(aMmdb, item, verifyFunc.bind(null, group), group.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aMmdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
startTestBase(function testCaseMain() {
|
||||||
|
mmdb = newMobileMessageDB();
|
||||||
|
return initMobileMessageDB(mmdb, "test_mmdb_foreachmatchedmmsdeliveryinfo", 0)
|
||||||
|
.then(testNotFound)
|
||||||
|
.then(testDirectMatch)
|
||||||
|
.then(testPhoneMatch)
|
||||||
|
.then(closeMobileMessageDB.bind(null, mmdb));
|
||||||
|
});
|
|
@ -7,6 +7,8 @@ MARIONETTE_HEAD_JS = 'head.js';
|
||||||
startTestBase(function testCaseMain() {
|
startTestBase(function testCaseMain() {
|
||||||
log("Test init MobileMessageDB");
|
log("Test init MobileMessageDB");
|
||||||
|
|
||||||
|
// TODO: bug 943233 - passing jsm exported objects to |Promise.resolve| gets
|
||||||
|
// empty object in return.
|
||||||
let mmdb = newMobileMessageDB();
|
let mmdb = newMobileMessageDB();
|
||||||
let dbName = "test_mmdb_new";
|
let dbName = "test_mmdb_new";
|
||||||
let dbVersion = 0;
|
let dbVersion = 0;
|
||||||
|
|
|
@ -107,12 +107,9 @@ if CONFIG['MOZ_NFC']:
|
||||||
if CONFIG['MOZ_B2G']:
|
if CONFIG['MOZ_B2G']:
|
||||||
PARALLEL_DIRS += ['downloads']
|
PARALLEL_DIRS += ['downloads']
|
||||||
|
|
||||||
# bindings/test is here, because it needs to build after bindings/, and
|
|
||||||
# we build subdirectories before ourselves.
|
|
||||||
TEST_DIRS += [
|
TEST_DIRS += [
|
||||||
'tests',
|
'tests',
|
||||||
'imptests',
|
'imptests',
|
||||||
'bindings/test',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'cocoa', 'windows', 'android', 'qt', 'os2'):
|
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'cocoa', 'windows', 'android', 'qt', 'os2'):
|
||||||
|
|
|
@ -491,7 +491,7 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function () {
|
||||||
receiveMessage: function(msg) {
|
receiveMessage: function(msg) {
|
||||||
if (DEBUG) debug("setRadioEnabled: receiveMessage: " + JSON.stringify(msg));
|
if (DEBUG) debug("setRadioEnabled: receiveMessage: " + JSON.stringify(msg));
|
||||||
this.pendingMessages.push(msg);
|
this.pendingMessages.push(msg);
|
||||||
if (this.pendingMessages.length === 1) {
|
if (this.pendingMessages.length === 1 && !this.isDeactivatingDataCalls()) {
|
||||||
this._processNextMessage();
|
this._processNextMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -540,7 +540,6 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function () {
|
||||||
} else {
|
} else {
|
||||||
this.request = (function() {
|
this.request = (function() {
|
||||||
radioInterface.receiveMessage(msg);
|
radioInterface.receiveMessage(msg);
|
||||||
this._processNextMessage();
|
|
||||||
}).bind(this);
|
}).bind(this);
|
||||||
|
|
||||||
// In some DSDS architecture with only one modem, toggling one radio may
|
// In some DSDS architecture with only one modem, toggling one radio may
|
||||||
|
@ -596,6 +595,7 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function () {
|
||||||
this.request();
|
this.request();
|
||||||
this.request = null;
|
this.request = null;
|
||||||
}
|
}
|
||||||
|
this._processNextMessage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1258,6 +1258,10 @@ RadioInterface.prototype = {
|
||||||
case "iccmbdn":
|
case "iccmbdn":
|
||||||
this.handleIccMbdn(message);
|
this.handleIccMbdn(message);
|
||||||
break;
|
break;
|
||||||
|
case "iccmwis":
|
||||||
|
gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification",
|
||||||
|
this.clientId, message.mwi);
|
||||||
|
break;
|
||||||
case "USSDReceived":
|
case "USSDReceived":
|
||||||
if (DEBUG) this.debug("USSDReceived " + JSON.stringify(message));
|
if (DEBUG) this.debug("USSDReceived " + JSON.stringify(message));
|
||||||
this.handleUSSDReceived(message);
|
this.handleUSSDReceived(message);
|
||||||
|
@ -2104,11 +2108,6 @@ RadioInterface.prototype = {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Bug #768441
|
|
||||||
// For now we don't store indicators persistently. When the mwi.discard
|
|
||||||
// flag is false, we'll need to persist the indicator to EFmwis.
|
|
||||||
// See TS 23.040 9.2.3.24.2
|
|
||||||
|
|
||||||
let mwi = message.mwi;
|
let mwi = message.mwi;
|
||||||
if (mwi) {
|
if (mwi) {
|
||||||
mwi.returnNumber = message.sender;
|
mwi.returnNumber = message.sender;
|
||||||
|
|
|
@ -1206,6 +1206,7 @@ this.GECKO_ICC_SERVICES = {
|
||||||
PNN: 51,
|
PNN: 51,
|
||||||
OPL: 52,
|
OPL: 52,
|
||||||
MDN: 53,
|
MDN: 53,
|
||||||
|
MWIS: 54,
|
||||||
SPDI: 56
|
SPDI: 56
|
||||||
},
|
},
|
||||||
usim: {
|
usim: {
|
||||||
|
@ -1221,6 +1222,7 @@ this.GECKO_ICC_SERVICES = {
|
||||||
PNN: 45,
|
PNN: 45,
|
||||||
OPL: 46,
|
OPL: 46,
|
||||||
MDN: 47,
|
MDN: 47,
|
||||||
|
MWIS: 48,
|
||||||
SPDI: 51
|
SPDI: 51
|
||||||
},
|
},
|
||||||
ruim: {
|
ruim: {
|
||||||
|
|
|
@ -4206,6 +4206,11 @@ let RIL = {
|
||||||
|
|
||||||
this.sendChromeMessage(message);
|
this.sendChromeMessage(message);
|
||||||
|
|
||||||
|
// Update MWI Status into ICC if present.
|
||||||
|
if (message.mwi && ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
|
||||||
|
SimRecordHelper.updateMWIS(message.mwi);
|
||||||
|
}
|
||||||
|
|
||||||
// We will acknowledge receipt of the SMS after we try to store it
|
// We will acknowledge receipt of the SMS after we try to store it
|
||||||
// in the database.
|
// in the database.
|
||||||
return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
|
return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
|
||||||
|
@ -6163,7 +6168,7 @@ RIL[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_
|
||||||
RIL[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
|
RIL[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
|
||||||
let recordNumber = Buf.readInt32List()[0];
|
let recordNumber = Buf.readInt32List()[0];
|
||||||
|
|
||||||
ICCRecordHelper.readSMS(
|
SimRecordHelper.readSMS(
|
||||||
recordNumber,
|
recordNumber,
|
||||||
function onsuccess(message) {
|
function onsuccess(message) {
|
||||||
if (message && message.simStatus === 3) { //New Unread SMS
|
if (message && message.simStatus === 3) { //New Unread SMS
|
||||||
|
@ -10937,6 +10942,7 @@ let ICCFileHelper = {
|
||||||
return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
|
return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
|
||||||
case ICC_EF_AD:
|
case ICC_EF_AD:
|
||||||
case ICC_EF_MBDN:
|
case ICC_EF_MBDN:
|
||||||
|
case ICC_EF_MWIS:
|
||||||
case ICC_EF_PLMNsel:
|
case ICC_EF_PLMNsel:
|
||||||
case ICC_EF_SPN:
|
case ICC_EF_SPN:
|
||||||
case ICC_EF_SPDI:
|
case ICC_EF_SPDI:
|
||||||
|
@ -10961,6 +10967,7 @@ let ICCFileHelper = {
|
||||||
case ICC_EF_AD:
|
case ICC_EF_AD:
|
||||||
case ICC_EF_FDN:
|
case ICC_EF_FDN:
|
||||||
case ICC_EF_MBDN:
|
case ICC_EF_MBDN:
|
||||||
|
case ICC_EF_MWIS:
|
||||||
case ICC_EF_UST:
|
case ICC_EF_UST:
|
||||||
case ICC_EF_MSISDN:
|
case ICC_EF_MSISDN:
|
||||||
case ICC_EF_SPN:
|
case ICC_EF_SPN:
|
||||||
|
@ -11916,6 +11923,13 @@ let SimRecordHelper = {
|
||||||
if (DEBUG) debug("MDN: MDN service is not available");
|
if (DEBUG) debug("MDN: MDN service is not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
|
||||||
|
if (DEBUG) debug("MWIS: MWIS is available");
|
||||||
|
this.readMWIS();
|
||||||
|
} else {
|
||||||
|
if (DEBUG) debug("MWIS: MWIS is not available");
|
||||||
|
}
|
||||||
|
|
||||||
if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
|
if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
|
||||||
if (DEBUG) debug("SPDI: SPDI available.");
|
if (DEBUG) debug("SPDI: SPDI available.");
|
||||||
this.readSPDI();
|
this.readSPDI();
|
||||||
|
@ -11985,6 +11999,91 @@ let SimRecordHelper = {
|
||||||
callback: callback.bind(this)});
|
callback: callback.bind(this)});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read ICC MWIS. (Message Waiting Indication Status)
|
||||||
|
*
|
||||||
|
* @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
|
||||||
|
*/
|
||||||
|
readMWIS: function readMWIS() {
|
||||||
|
function callback(options) {
|
||||||
|
let strLen = Buf.readInt32();
|
||||||
|
// Each octet is encoded into two chars.
|
||||||
|
let octetLen = strLen / 2;
|
||||||
|
let mwis = GsmPDUHelper.readHexOctetArray(octetLen);
|
||||||
|
Buf.readStringDelimiter(strLen);
|
||||||
|
if (!mwis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
|
||||||
|
|
||||||
|
let mwi = {};
|
||||||
|
// b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0
|
||||||
|
// | | | | | | | |__ Voicemail
|
||||||
|
// | | | | | | |_____ Fax
|
||||||
|
// | | | | | |________ Electronic Mail
|
||||||
|
// | | | | |___________ Other
|
||||||
|
// | | | |______________ Videomail
|
||||||
|
// |__|__|_________________ RFU
|
||||||
|
mwi.active = ((mwis[0] & 0x01) != 0);
|
||||||
|
|
||||||
|
if (mwi.active) {
|
||||||
|
// In TS 23.040 msgCount is in the range from 0 to 255.
|
||||||
|
// The value 255 shall be taken to mean 255 or greater.
|
||||||
|
//
|
||||||
|
// However, There is no definition about 0 when MWI is active.
|
||||||
|
//
|
||||||
|
// Normally, when mwi is active, the msgCount must be larger than 0.
|
||||||
|
// Refer to other reference phone,
|
||||||
|
// 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
|
||||||
|
mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
|
||||||
|
: mwis[1];
|
||||||
|
} else {
|
||||||
|
mwi.msgCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RIL.sendChromeMessage({ rilMessageType: "iccmwis",
|
||||||
|
mwi: mwi });
|
||||||
|
}
|
||||||
|
|
||||||
|
ICCIOHelper.loadLinearFixedEF({ fileId: ICC_EF_MWIS,
|
||||||
|
recordNumber: 1, // Get 1st Subscriber Profile.
|
||||||
|
callback: callback });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update ICC MWIS. (Message Waiting Indication Status)
|
||||||
|
*
|
||||||
|
* @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
|
||||||
|
*/
|
||||||
|
updateMWIS: function updateMWIS(mwi) {
|
||||||
|
if (!RIL.iccInfoPrivate.mwis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataWriter(recordSize) {
|
||||||
|
let mwis = RIL.iccInfoPrivate.mwis;
|
||||||
|
|
||||||
|
let msgCount =
|
||||||
|
(mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
|
||||||
|
|
||||||
|
[mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
|
||||||
|
: [(mwis[0] & 0xFE), 0];
|
||||||
|
|
||||||
|
let strLen = recordSize * 2;
|
||||||
|
Buf.writeInt32(strLen);
|
||||||
|
|
||||||
|
for (let i = 0; i < mwis.length; i++) {
|
||||||
|
GsmPDUHelper.writeHexOctet(mwis[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buf.writeStringDelimiter(strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
ICCIOHelper.updateLinearFixedEF({ fileId: ICC_EF_MWIS,
|
||||||
|
recordNumber: 1, // Update 1st Subscriber Profile.
|
||||||
|
dataWriter: dataWriter });
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the SPDI (Service Provider Display Information) from the (U)SIM.
|
* Read the SPDI (Service Provider Display Information) from the (U)SIM.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2330,6 +2330,10 @@ add_test(function test_reading_optional_efs() {
|
||||||
testEf.splice(testEf.indexOf("MDN"), 1);
|
testEf.splice(testEf.indexOf("MDN"), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
record.readMWIS = function fakeReadMWIS() {
|
||||||
|
testEf.splice(testEf.indexOf("MWIS"), 1);
|
||||||
|
};
|
||||||
|
|
||||||
io.loadTransparentEF = function fakeLoadTransparentEF(options) {
|
io.loadTransparentEF = function fakeLoadTransparentEF(options) {
|
||||||
// Write data size
|
// Write data size
|
||||||
buf.writeInt32(sst.length * 2);
|
buf.writeInt32(sst.length * 2);
|
||||||
|
@ -2356,7 +2360,7 @@ add_test(function test_reading_optional_efs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add all necessary optional EFs eventually
|
// TODO: Add all necessary optional EFs eventually
|
||||||
let supportedEf = ["MSISDN", "MDN"];
|
let supportedEf = ["MSISDN", "MDN", "MWIS"];
|
||||||
ril.appType = CARD_APPTYPE_SIM;
|
ril.appType = CARD_APPTYPE_SIM;
|
||||||
do_test(buildSST(supportedEf), supportedEf);
|
do_test(buildSST(supportedEf), supportedEf);
|
||||||
ril.appType = CARD_APPTYPE_USIM;
|
ril.appType = CARD_APPTYPE_USIM;
|
||||||
|
@ -2435,3 +2439,174 @@ add_test(function test_fetch_icc_recodes() {
|
||||||
|
|
||||||
run_next_test();
|
run_next_test();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_test(function test_read_mwis() {
|
||||||
|
let worker = newUint8Worker();
|
||||||
|
let helper = worker.GsmPDUHelper;
|
||||||
|
let recordHelper = worker.SimRecordHelper;
|
||||||
|
let buf = worker.Buf;
|
||||||
|
let io = worker.ICCIOHelper;
|
||||||
|
let mwisData;
|
||||||
|
let postedMessage;
|
||||||
|
|
||||||
|
worker.postMessage = function fakePostMessage(message) {
|
||||||
|
postedMessage = message;
|
||||||
|
};
|
||||||
|
|
||||||
|
io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
|
||||||
|
if (mwisData) {
|
||||||
|
// Write data size
|
||||||
|
buf.writeInt32(mwisData.length * 2);
|
||||||
|
|
||||||
|
// Write MWIS
|
||||||
|
for (let i = 0; i < mwisData.length; i++) {
|
||||||
|
helper.writeHexOctet(mwisData[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write string delimiter
|
||||||
|
buf.writeStringDelimiter(mwisData.length * 2);
|
||||||
|
|
||||||
|
options.recordSize = mwisData.length;
|
||||||
|
if (options.callback) {
|
||||||
|
options.callback(options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
do_print("mwisData[] is not set.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildMwisData(isActive, msgCount) {
|
||||||
|
if (msgCount < 0 || msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) {
|
||||||
|
msgCount = 0;
|
||||||
|
} else if (msgCount > 255) {
|
||||||
|
msgCount = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwisData = [ (isActive) ? 0x01 : 0x00,
|
||||||
|
msgCount,
|
||||||
|
0xFF, 0xFF, 0xFF ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_test(isActive, msgCount) {
|
||||||
|
buildMwisData(isActive, msgCount);
|
||||||
|
recordHelper.readMWIS();
|
||||||
|
|
||||||
|
do_check_eq("iccmwis", postedMessage.rilMessageType);
|
||||||
|
do_check_eq(isActive, postedMessage.mwi.active);
|
||||||
|
do_check_eq((isActive) ? msgCount : 0, postedMessage.mwi.msgCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN);
|
||||||
|
do_test(true, 1);
|
||||||
|
do_test(true, 255);
|
||||||
|
|
||||||
|
do_test(false, 0);
|
||||||
|
do_test(false, 255); // Test the corner case when mwi is disable with incorrect msgCount.
|
||||||
|
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_test(function test_update_mwis() {
|
||||||
|
let worker = newUint8Worker();
|
||||||
|
let pduHelper = worker.GsmPDUHelper;
|
||||||
|
let ril = worker.RIL;
|
||||||
|
ril.appType = CARD_APPTYPE_USIM;
|
||||||
|
ril.iccInfoPrivate.mwis = [0x00, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
let recordHelper = worker.SimRecordHelper;
|
||||||
|
let buf = worker.Buf;
|
||||||
|
let ioHelper = worker.ICCIOHelper;
|
||||||
|
let recordSize = ril.iccInfoPrivate.mwis.length;
|
||||||
|
let recordNum = 1;
|
||||||
|
|
||||||
|
ioHelper.updateLinearFixedEF = function (options) {
|
||||||
|
options.pathId = worker.ICCFileHelper.getEFPath(options.fileId);
|
||||||
|
options.command = ICC_COMMAND_UPDATE_RECORD;
|
||||||
|
options.p1 = options.recordNumber;
|
||||||
|
options.p2 = READ_RECORD_ABSOLUTE_MODE;
|
||||||
|
options.p3 = recordSize;
|
||||||
|
ril.iccIO(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function do_test(isActive, count) {
|
||||||
|
let mwis = ril.iccInfoPrivate.mwis;
|
||||||
|
let isUpdated = false;
|
||||||
|
|
||||||
|
function buildMwisData() {
|
||||||
|
let result = mwis.slice(0);
|
||||||
|
result[0] = isActive? (mwis[0] | 0x01) : (mwis[0] & 0xFE);
|
||||||
|
result[1] = (count === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : count;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.sendParcel = function () {
|
||||||
|
isUpdated = true;
|
||||||
|
|
||||||
|
// Request Type.
|
||||||
|
do_check_eq(this.readInt32(), REQUEST_SIM_IO);
|
||||||
|
|
||||||
|
// Token : we don't care
|
||||||
|
this.readInt32();
|
||||||
|
|
||||||
|
// command.
|
||||||
|
do_check_eq(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
|
||||||
|
|
||||||
|
// fileId.
|
||||||
|
do_check_eq(this.readInt32(), ICC_EF_MWIS);
|
||||||
|
|
||||||
|
// pathId.
|
||||||
|
do_check_eq(this.readString(),
|
||||||
|
EF_PATH_MF_SIM + ((ril.appType === CARD_APPTYPE_USIM) ? EF_PATH_ADF_USIM : EF_PATH_DF_GSM));
|
||||||
|
|
||||||
|
// p1.
|
||||||
|
do_check_eq(this.readInt32(), recordNum);
|
||||||
|
|
||||||
|
// p2.
|
||||||
|
do_check_eq(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
|
||||||
|
|
||||||
|
// p3.
|
||||||
|
do_check_eq(this.readInt32(), recordSize);
|
||||||
|
|
||||||
|
// data.
|
||||||
|
let strLen = this.readInt32();
|
||||||
|
do_check_eq(recordSize * 2, strLen);
|
||||||
|
let expectedMwis = buildMwisData();
|
||||||
|
for (let i = 0; i < recordSize; i++) {
|
||||||
|
do_check_eq(expectedMwis[i], pduHelper.readHexOctet());
|
||||||
|
}
|
||||||
|
this.readStringDelimiter(strLen);
|
||||||
|
|
||||||
|
// pin2.
|
||||||
|
do_check_eq(this.readString(), null);
|
||||||
|
|
||||||
|
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||||
|
// AID. Ignore because it's from modem.
|
||||||
|
this.readInt32();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
do_check_false(isUpdated);
|
||||||
|
|
||||||
|
recordHelper.updateMWIS({ active: isActive,
|
||||||
|
msgCount: count });
|
||||||
|
|
||||||
|
do_check_true((ril.iccInfoPrivate.mwis) ? isUpdated : !isUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN);
|
||||||
|
do_test(true, 1);
|
||||||
|
do_test(true, 255);
|
||||||
|
|
||||||
|
do_test(false, 0);
|
||||||
|
|
||||||
|
// Test if Path ID is correct for SIM.
|
||||||
|
ril.appType = CARD_APPTYPE_SIM;
|
||||||
|
do_test(false, 0);
|
||||||
|
|
||||||
|
// Test if loadLinearFixedEF() is not invoked in updateMWIS() when
|
||||||
|
// EF_MWIS is not loaded/available.
|
||||||
|
delete ril.iccInfoPrivate.mwis;
|
||||||
|
do_test(false, 0);
|
||||||
|
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче