Merge m-c to b2g-inbound.

This commit is contained in:
Ryan VanderMeulen 2014-05-28 16:29:36 -04:00
commit 09f87e0587
87 changed files with 3002 additions and 529 deletions

View File

@ -14,7 +14,7 @@
"{objdir}/dist/b2g-*.crashreporter-symbols.zip",
"{objdir}/dist/b2g-*.tar.gz",
"{workdir}/sources.xml",
"{workdir}/out/target/product/hamachi/fota-update.mar"
"{workdir}/out/target/product/hamachi/*.mar"
],
"env": {
"VARIANT": "user",

View File

@ -388,7 +388,7 @@
#endif
@BINPATH@/components/SiteSpecificUserAgent.js
@BINPATH@/components/SiteSpecificUserAgent.manifest
@BINPATH@/components/storage-mozStorage.js
@BINPATH@/components/storage-json.js
@BINPATH@/components/crypto-SDR.js
@BINPATH@/components/jsconsole-clhandler.manifest
@BINPATH@/components/jsconsole-clhandler.js

View File

@ -1129,6 +1129,18 @@ var gBrowserInit = {
}
}, 10000);
// Load the Login Manager data from disk off the main thread, some time
// after startup. If the data is required before the timeout, for example
// because a restored page contains a password field, it will be loaded on
// the main thread, and this initialization request will be ignored.
setTimeout(function() {
try {
Services.logins;
} catch (ex) {
Cu.reportError(ex);
}
}, 3000);
// The object handling the downloads indicator is also initialized here in the
// delayed startup function, but the actual indicator element is not loaded
// unless there are downloads to be displayed.

View File

@ -328,6 +328,9 @@ const PanelUI = {
tempPanel.setAttribute("type", "arrow");
tempPanel.setAttribute("id", "customizationui-widget-panel");
tempPanel.setAttribute("class", "cui-widget-panel");
if (this._disableAnimations) {
tempPanel.setAttribute("animate", "false");
}
tempPanel.setAttribute("context", "");
document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
// If the view has a footer, set a convenience class on the panel.
@ -363,6 +366,19 @@ const PanelUI = {
}
},
/**
* NB: The enable- and disableSingleSubviewPanelAnimations methods only
* affect the hiding/showing animations of single-subview panels (tempPanel
* in the showSubView method).
*/
disableSingleSubviewPanelAnimations: function() {
this._disableAnimations = true;
},
enableSingleSubviewPanelAnimations: function() {
this._disableAnimations = false;
},
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
if (aContainer != this.contents) {
return;

View File

@ -41,6 +41,16 @@ function createSidebarItem() {
gSidebarMenu.insertBefore(gTestSidebarItem, gSidebarMenu.firstChild);
}
function addWidget() {
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
PanelUI.disableSingleSubviewPanelAnimations();
}
function removeWidget() {
CustomizableUI.removeWidgetFromArea("sidebar-button");
PanelUI.enableSingleSubviewPanelAnimations();
}
// Filters out the trailing menuseparators from the sidebar list
function getSidebarList() {
let sidebars = [...gSidebarMenu.children];
@ -76,7 +86,7 @@ let showSidebarPopup = Task.async(function*() {
// Check the sidebar widget shows the default items
add_task(function*() {
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
addWidget();
yield showSidebarPopup();
@ -89,13 +99,14 @@ add_task(function*() {
document.getElementById("customizationui-widget-panel").hidePopup();
yield subviewHiddenPromise;
return resetCustomization();
removeWidget();
});
function add_sidebar_task(description, setup, teardown) {
add_task(function*() {
info(description);
createSidebarItem();
addWidget();
yield setup();
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
@ -114,7 +125,7 @@ function add_sidebar_task(description, setup, teardown) {
yield teardown();
gTestSidebarItem.remove();
return resetCustomization();
removeWidget();
});
}

View File

@ -105,13 +105,13 @@ const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
// Seconds of idle before trying to create a bookmarks backup.
const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 10 * 60;
const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
// Minimum interval between backups. We try to not create more than one backup
// per interval.
const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
// Maximum interval between backups. If the last backup is older than these
// days we will try to create a new one more aggressively.
const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 5;
const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 3;
// Factory object
const BrowserGlueServiceFactory = {

View File

@ -27,7 +27,7 @@ function test() {
// Check only valid tabs are shown
let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children;
let expectedTabs = ["options", "webconsole", "jsdebugger", "scratchpad"];
let expectedTabs = ["webconsole", "jsdebugger", "scratchpad"];
is(tabs.length, expectedTabs.length, "displaying only " + expectedTabs.length + " tabs in addon debugger");
Array.forEach(tabs, (tab, i) => {

View File

@ -58,7 +58,7 @@ function testSelectTool(aToolbox) {
function testPreferenceAndUIStateIsConsistent() {
let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
let toolboxButtonNodes = [...doc.querySelectorAll("#toolbox-buttons > toolbarbutton")];
let toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
let toggleableTools = toolbox.toolboxButtons;
for (let tool of toggleableTools) {
@ -74,7 +74,7 @@ function testPreferenceAndUIStateIsConsistent() {
function testToggleToolboxButtons() {
let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
let toolboxButtonNodes = [...doc.querySelectorAll("#toolbox-buttons > toolbarbutton")];
let toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
let visibleButtons = toolboxButtonNodes.filter(button=>!button.hasAttribute("hidden"));
let toggleableTools = toolbox.toolboxButtons;

View File

@ -555,7 +555,7 @@ Toolbox.prototype = {
this._pickerButton.className = "command-button command-button-invertable";
this._pickerButton.setAttribute("tooltiptext", toolboxStrings("pickButton.tooltip"));
let container = this.doc.querySelector("#toolbox-buttons");
let container = this.doc.querySelector("#toolbox-picker-container");
container.appendChild(this._pickerButton);
this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
@ -690,9 +690,16 @@ Toolbox.prototype = {
vbox.id = "toolbox-panel-" + id;
}
if (id === "options") {
// Options panel is special. It doesn't belong in the same container as
// the other tabs.
let optionTabContainer = this.doc.getElementById("toolbox-option-container");
optionTabContainer.appendChild(radio);
deck.appendChild(vbox);
} else {
// If there is no tab yet, or the ordinal to be added is the largest one.
if (tabs.childNodes.length == 0 ||
+tabs.lastChild.getAttribute("ordinal") <= toolDefinition.ordinal) {
tabs.lastChild.getAttribute("ordinal") <= toolDefinition.ordinal) {
tabs.appendChild(radio);
deck.appendChild(vbox);
} else {
@ -706,6 +713,7 @@ Toolbox.prototype = {
return false;
});
}
}
this._addKeysToWindow();
},
@ -805,6 +813,15 @@ Toolbox.prototype = {
let tab = this.doc.getElementById("toolbox-tab-" + id);
tab.setAttribute("selected", "true");
// If options is selected, the separator between it and the
// command buttons should be hidden.
let sep = this.doc.getElementById("toolbox-controls-separator");
if (id === "options") {
sep.setAttribute("invisible", "true");
} else {
sep.removeAttribute("invisible");
}
if (this.currentToolId == id) {
// re-focus tool to get key events again
this.focusTool(id);
@ -831,20 +848,13 @@ Toolbox.prototype = {
let tabstrip = this.doc.getElementById("toolbox-tabs");
// select the right tab, making 0th index the default tab if right tab not
// found
let index = 0;
let tabs = tabstrip.childNodes;
for (let i = 0; i < tabs.length; i++) {
if (tabs[i] === tab) {
index = i;
break;
}
}
tabstrip.selectedItem = tab;
// found.
tabstrip.selectedItem = tab || tabstrip.childNodes[0];
// and select the right iframe
let deck = this.doc.getElementById("toolbox-deck");
deck.selectedIndex = index;
let panel = this.doc.getElementById("toolbox-panel-" + id);
deck.selectedPanel = panel;
this.currentToolId = id;
this._refreshConsoleDisplay();
@ -907,8 +917,10 @@ Toolbox.prototype = {
* Loads the tool next to the currently selected tool.
*/
selectNextTool: function() {
let tools = this.doc.querySelectorAll(".devtools-tab");
let selected = this.doc.querySelector(".devtools-tab[selected]");
let next = selected.nextSibling || selected.parentNode.firstChild;
let nextIndex = [...tools].indexOf(selected) + 1;
let next = tools[nextIndex] || tools[0];
let tool = next.getAttribute("toolid");
return this.selectTool(tool);
},
@ -917,9 +929,11 @@ Toolbox.prototype = {
* Loads the tool just left to the currently selected tool.
*/
selectPreviousTool: function() {
let tools = this.doc.querySelectorAll(".devtools-tab");
let selected = this.doc.querySelector(".devtools-tab[selected]");
let previous = selected.previousSibling || selected.parentNode.lastChild;
let tool = previous.getAttribute("toolid");
let prevIndex = [...tools].indexOf(selected) - 1;
let prev = tools[prevIndex] || tools[tools.length - 1];
let tool = prev.getAttribute("toolid");
return this.selectTool(tool);
},

View File

@ -51,26 +51,17 @@
<notificationbox id="toolbox-notificationbox" flex="1">
<toolbar class="devtools-tabbar">
#ifdef XP_MACOSX
<hbox id="toolbox-controls">
<toolbarbutton id="toolbox-close"
class="devtools-closebutton"
tooltiptext="&toolboxCloseButton.tooltip;"/>
<hbox id="toolbox-dock-buttons"/>
</hbox>
#endif
<hbox id="toolbox-tabs" flex="1">
</hbox>
<hbox id="toolbox-picker-container" />
<hbox id="toolbox-tabs" flex="1" />
<hbox id="toolbox-buttons" pack="end"/>
#ifndef XP_MACOSX
<vbox id="toolbox-controls-separator"/>
<hbox id="toolbox-option-container"/>
<hbox id="toolbox-controls">
<hbox id="toolbox-dock-buttons"/>
<toolbarbutton id="toolbox-close"
class="devtools-closebutton"
tooltiptext="&toolboxCloseButton.tooltip;"/>
</hbox>
#endif
</toolbar>
<vbox flex="1">
<deck id="toolbox-deck" flex="1" minheight="75" />

View File

@ -86,7 +86,7 @@ Tools.webConsole = {
key: l10n("cmd.commandkey", webConsoleStrings),
accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
ordinal: 1,
ordinal: 2,
icon: "chrome://browser/skin/devtools/tool-webconsole.svg",
invertIconForLightTheme: true,
url: "chrome://browser/content/devtools/webconsole.xul",
@ -117,7 +117,7 @@ Tools.inspector = {
id: "inspector",
accesskey: l10n("inspector.accesskey", inspectorStrings),
key: l10n("inspector.commandkey", inspectorStrings),
ordinal: 2,
ordinal: 1,
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
icon: "chrome://browser/skin/devtools/tool-inspector.svg",
invertIconForLightTheme: true,

View File

@ -159,7 +159,7 @@
</div>
<div class="csscoverage-report-unused">
<h2>&csscoverage.unused;</h2>
<p>&csscoverage.noMatch;</p>
<p>&csscoverage.noMatches;</p>
<div foreach="page in ${unused}">
<h3>${page.url}</h3>
<code foreach="rule in ${page.rules}"

View File

@ -390,7 +390,7 @@
@BINPATH@/components/nsLoginInfo.js
@BINPATH@/components/nsLoginManager.js
@BINPATH@/components/nsLoginManagerPrompter.js
@BINPATH@/components/storage-mozStorage.js
@BINPATH@/components/storage-json.js
@BINPATH@/components/crypto-SDR.js
@BINPATH@/components/jsconsole-clhandler.manifest
@BINPATH@/components/jsconsole-clhandler.js

View File

@ -278,7 +278,7 @@
margin: 0 4px;
min-width: 16px;
width: 16px;
opacity: 0.6;
opacity: 0.8;
}
.devtools-closebutton > image {
@ -302,10 +302,6 @@
}
.devtools-closebutton:hover {
opacity: 0.8;
}
.devtools-closebutton:hover:active {
opacity: 1;
}
@ -457,10 +453,6 @@
* Rules that apply to the global toolbox like command buttons,
* devtools tabs, docking buttons, etc. */
#toolbox-controls {
margin: 0 4px;
}
#toolbox-controls > toolbarbutton,
#toolbox-dock-buttons > toolbarbutton {
-moz-appearance: none;
@ -500,7 +492,7 @@
#toolbox-dock-window,
#toolbox-dock-bottom,
#toolbox-dock-side {
opacity: 0.6;
opacity: 0.8;
}
#toolbox-dock-window:hover,
@ -510,14 +502,16 @@
}
#toolbox-controls-separator {
width: 3px;
width: 2px;
background-image: linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0)),
linear-gradient(hsla(206,37%,4%,0), hsla(206,37%,4%,.6), hsla(206,37%,4%,0)),
linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0));
background-size: 1px 100%;
background-repeat: no-repeat;
background-position: 0, 1px, 2px;
-moz-margin-start: 8px;
}
#toolbox-controls-separator[invisible] {
visibility: hidden;
}
/* Command buttons */
@ -650,7 +644,9 @@
max-width: 127px;
margin: 0;
padding: 0;
-moz-border-start: 1px solid;
border-style: solid;
border-width: 0;
-moz-border-start-width: 1px;
-moz-box-align: center;
}
@ -691,18 +687,6 @@
background-color: rgba(44, 187, 15, .2);
}
.devtools-tab:first-child {
-moz-border-start-width: 0;
}
.theme-light .devtools-tab:last-child {
-moz-border-end: 1px solid #aaa;
}
.theme-dark .devtools-tab:last-child {
-moz-border-end: 1px solid #42484f;
}
.devtools-tab > image {
border: none;
-moz-margin-end: 0;
@ -712,10 +696,6 @@
width: 16px; /* Prevents collapse during theme switching */
}
#toolbox-tab-options > image {
margin: 0 8px;
}
.devtools-tab > label {
white-space: nowrap;
}
@ -729,7 +709,7 @@
opacity: 1;
}
.theme-dark #toolbox-tabs .devtools-tab[selected] {
.theme-dark .devtools-tab[selected] {
color: #f5f7fa;
background-color: #1a4666;
box-shadow: 0 2px 0 #d7f1ff inset,
@ -737,7 +717,7 @@
0 -2px 0 rgba(0,0,0,.2) inset;
}
.theme-light #toolbox-tabs .devtools-tab[selected] {
.theme-light .devtools-tab[selected] {
color: #f5f7fa;
background-color: #4c9ed9;
box-shadow: 0 2px 0 #d7f1ff inset,
@ -745,29 +725,51 @@
0 -2px 0 rgba(0,0,0,.06) inset;
}
.devtools-tab[selected]:not(:first-child),
.devtools-tab[highlighted]:not(:first-child) {
#toolbox-tabs .devtools-tab[selected]:not(:first-child),
#toolbox-tabs .devtools-tab[highlighted]:not(:first-child) {
border-width: 0;
-moz-padding-start: 1px;
}
.devtools-tab[selected]:last-child,
.devtools-tab[highlighted]:last-child {
#toolbox-tabs .devtools-tab[selected]:last-child,
#toolbox-tabs .devtools-tab[highlighted]:last-child {
-moz-padding-end: 1px;
}
.devtools-tab[selected] + .devtools-tab,
.devtools-tab[highlighted] + .devtools-tab {
#toolbox-tabs .devtools-tab[selected] + .devtools-tab,
#toolbox-tabs .devtools-tab[highlighted] + .devtools-tab {
-moz-border-start-width: 0;
-moz-padding-start: 1px;
}
#toolbox-tabs .devtools-tab:first-child[selected] {
-moz-border-start-width: 0;
}
#toolbox-tabs .devtools-tab:last-child {
-moz-border-end-width: 1px;
}
.devtools-tab:not([highlighted]) > .highlighted-icon,
.devtools-tab[selected] > .highlighted-icon,
.devtools-tab:not([selected])[highlighted] > .default-icon {
visibility: collapse;
}
/* The options tab is special - it doesn't have the same parent
as the other tabs (toolbox-option-container vs toolbox-tabs) */
#toolbox-option-container .devtools-tab:not([selected]) {
background-color: transparent;
}
#toolbox-option-container .devtools-tab {
border-color: transparent;
border-width: 0;
-moz-padding-start: 1px;
}
#toolbox-tab-options > image {
margin: 0 8px;
}
/* Invert the colors of certain dark theme images for displaying
* inside of the light theme.
*/

View File

@ -100,3 +100,12 @@
fun:_ZN22nsComponentManagerImpl17ManifestComponentERNS_25ManifestProcessingContextEiPKPc
...
}
{
Bug 1017112
Memcheck:Leak
fun:malloc
...
fun:PK11_InitPin
fun:_ZN11nsPK11Token12InitPasswordEPKDs
...
}

View File

@ -152,7 +152,10 @@ AudioStream::AudioStream()
, mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST())
, mLatencyRequest(HighLatency)
, mReadPoint(0)
, mLostFrames(0)
, mWrittenFramesPast(0)
, mLostFramesPast(0)
, mWrittenFramesLast(0)
, mLostFramesLast(0)
, mDumpFile(nullptr)
, mVolume(1.0)
, mBytesPerFrame(0)
@ -780,9 +783,28 @@ AudioStream::GetPositionInFramesUnlocked()
// Adjust the reported position by the number of silent frames written
// during stream underruns.
// Since frames sent to DataCallback is not consumed by the backend immediately,
// it will be an over adjustment if we return |position - mLostFramesPast - mLostFramesLast|.
// On the other hand, we need to keep the whole history of frames sent to DataCallback
// in order to adjust position correctly which will require more storage.
// We choose a simple way to store the history where |mWrittenFramesPast| and
// |mLostFramesPast| are the sum of frames from 1th to |N-1|th callbacks, and
// |mWrittenFramesLast| and |mLostFramesLast| represent the frames sent in last callback.
// When |position| lies in
// [mWrittenFramesPast+mLostFramesPast, mWrittenFramesPast+mLostFramesPast+mWrittenFramesLast+mLostFramesLast],
// we will be able to adjust position precisely which should be the major case.
// If |position| falls in [0, mWrittenFramesPast+mLostFramesPast), there will be an
// error in the adjustment. However that is fine as long as we can ensure the
// adjusted position is mono-increasing to avoid audio clock going backward.
uint64_t adjustedPosition = 0;
if (position >= mLostFrames) {
adjustedPosition = position - mLostFrames;
if (position <= mWrittenFramesPast) {
adjustedPosition = position;
} else if (position <= mWrittenFramesPast + mLostFramesPast) {
adjustedPosition = mWrittenFramesPast;
} else if (position <= mWrittenFramesPast + mLostFramesPast + mWrittenFramesLast) {
adjustedPosition = position - mLostFramesPast;
} else {
adjustedPosition = mWrittenFramesPast + mWrittenFramesLast;
}
return std::min<uint64_t>(adjustedPosition, INT64_MAX);
}
@ -929,6 +951,9 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
uint32_t servicedFrames = 0;
int64_t insertTime;
mWrittenFramesPast += mWrittenFramesLast;
mLostFramesPast += mLostFramesLast;
// NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
// Bug 996162
@ -992,6 +1017,8 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
}
underrunFrames = aFrames - servicedFrames;
mWrittenFramesLast = servicedFrames;
mLostFramesLast = underrunFrames;
if (mState != DRAINING) {
uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames);
@ -1000,7 +1027,6 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
("AudioStream %p lost %d frames", this, underrunFrames));
}
mLostFrames += underrunFrames;
servicedFrames += underrunFrames;
}

View File

@ -65,8 +65,6 @@ public:
// Get the current pitch preservation state.
// Called on the audio thread.
bool GetPreservesPitch();
// Get the number of frames written to the backend.
int64_t GetWritten();
private:
// This AudioStream holds a strong reference to this AudioClock. This
// pointer is garanteed to always be valid.
@ -372,9 +370,14 @@ private:
};
nsAutoTArray<Inserts, 8> mInserts;
// Sum of silent frames written when DataCallback requests more frames
// than are available in mBuffer.
uint64_t mLostFrames;
// Suppose we have received DataCallback for N times, |mWrittenFramesPast|
// and |mLostFramesPast| are the sum of frames written to the backend from
// 1st to |N-1|th DataCallbacks.
uint64_t mWrittenFramesPast; // non-silent frames
uint64_t mLostFramesPast; // silent frames
// Frames written to the backend in Nth DataCallback.
uint64_t mWrittenFramesLast; // non-silent frames
uint64_t mLostFramesLast; // silent frames
// Output file for dumping audio
FILE* mDumpFile;

View File

@ -140,7 +140,11 @@ SVGDocument::EnsureNonSVGUserAgentStyleSheetsLoaded()
}
}
EnsureOnDemandBuiltInUASheet(nsLayoutStylesheetCache::NumberControlSheet());
nsCSSStyleSheet* sheet = nsLayoutStylesheetCache::NumberControlSheet();
if (sheet) {
// number-control.css can be behind a pref
EnsureOnDemandBuiltInUASheet(sheet);
}
EnsureOnDemandBuiltInUASheet(nsLayoutStylesheetCache::FormsSheet());
EnsureOnDemandBuiltInUASheet(nsLayoutStylesheetCache::HTMLSheet());
EnsureOnDemandBuiltInUASheet(nsLayoutStylesheetCache::UASheet());

View File

@ -138,14 +138,21 @@ struct AlignedArray
T *mPtr;
};
/**
* Returns aStride increased, if necessary, so that it divides exactly into
* |alignment|.
*
* Note that currently |alignment| must be a power-of-2. If for some reason we
* want to support NPOT alignment we can revert back to this functions old
* implementation.
*/
template<int alignment>
int32_t GetAlignedStride(int32_t aStride)
{
if (aStride % alignment) {
return aStride + (alignment - (aStride % alignment));
}
return aStride;
static_assert(alignment > 0 && (alignment & (alignment-1)) == 0,
"This implementation currently require power-of-two alignment");
const int32_t mask = alignment - 1;
return (aStride + mask) & ~mask;
}
}

View File

@ -106,11 +106,25 @@ ImageHost::Composite(EffectChain& aEffectChain,
gfx::Rect pictureRect(0, 0,
mPictureRect.width,
mPictureRect.height);
//XXX: We might have multiple texture sources here (e.g. 3 YCbCr textures), and we're
// only iterating over the tiles of the first one. Are we assuming that the tiling
// will be identical? Can we ensure that somehow?
BigImageIterator* it = source->AsBigImageIterator();
if (it) {
// This iteration does not work if we have multiple texture sources here
// (e.g. 3 YCbCr textures). There's nothing preventing the different
// planes from having different resolutions or tile sizes. For example, a
// YCbCr frame could have Cb and Cr planes that are half the resolution of
// the Y plane, in such a way that the Y plane overflows the maximum
// texture size and the Cb and Cr planes do not. Then the Y plane would be
// split into multiple tiles and the Cb and Cr planes would just be one
// tile each.
// To handle the general case correctly, we'd have to create a grid of
// intersected tiles over all planes, and then draw each grid tile using
// the corresponding source tiles from all planes, with appropriate
// per-plane per-tile texture coords.
// DrawQuad currently assumes that all planes use the same texture coords.
MOZ_ASSERT(it->GetTileCount() == 1 || !source->GetNextSibling(),
"Can't handle multi-plane BigImages");
it->BeginBigImageIteration();
do {
nsIntRect tileRect = it->GetTileRect();

View File

@ -307,11 +307,24 @@ CompositorOGL::Initialize()
mGLContext->fGenBuffers(1, &mQuadVBO);
mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
// 4 quads, with the number of the quad (vertexID) encoded in w.
GLfloat vertices[] = {
/* First quad vertices */
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
/* Then quad texcoords */
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 2.0f,
1.0f, 0.0f, 0.0f, 2.0f,
0.0f, 1.0f, 0.0f, 2.0f,
1.0f, 1.0f, 0.0f, 2.0f,
0.0f, 0.0f, 0.0f, 3.0f,
1.0f, 0.0f, 0.0f, 3.0f,
0.0f, 1.0f, 0.0f, 3.0f,
1.0f, 1.0f, 0.0f, 3.0f,
};
HeapCopyOfStackArray<GLfloat> verticesOnHeap(vertices);
mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER,
@ -528,9 +541,7 @@ CompositorOGL::BindAndDrawQuadWithTextureRect(ShaderProgramOGL *aProg,
aTexCoordRect,
layerRects,
textureRects);
for (int n = 0; n < rects; ++n) {
BindAndDrawQuad(aProg, layerRects[n], textureRects[n]);
}
BindAndDrawQuads(aProg, rects, layerRects, textureRects);
}
void
@ -1499,26 +1510,27 @@ CompositorOGL::BindQuadVBO() {
void
CompositorOGL::QuadVBOVerticesAttrib(GLuint aAttribIndex) {
mGLContext->fVertexAttribPointer(aAttribIndex, 2,
mGLContext->fVertexAttribPointer(aAttribIndex, 4,
LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
(GLvoid*) QuadVBOVertexOffset());
(GLvoid*) 0);
}
void
CompositorOGL::QuadVBOTexCoordsAttrib(GLuint aAttribIndex) {
mGLContext->fVertexAttribPointer(aAttribIndex, 2,
mGLContext->fVertexAttribPointer(aAttribIndex, 4,
LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
(GLvoid*) QuadVBOTexCoordOffset());
(GLvoid*) 0);
}
void
CompositorOGL::BindAndDrawQuad(ShaderProgramOGL *aProg,
const Rect& aLayerRect,
const Rect& aTextureRect)
CompositorOGL::BindAndDrawQuads(ShaderProgramOGL *aProg,
int aQuads,
const Rect* aLayerRects,
const Rect* aTextureRects)
{
NS_ASSERTION(aProg->HasInitialized(), "Shader program not correctly initialized");
aProg->SetLayerRect(aLayerRect);
aProg->SetLayerRects(aLayerRects);
GLuint vertAttribIndex = aProg->AttribLocation(ShaderProgramOGL::VertexCoordAttrib);
GLuint texCoordAttribIndex = aProg->AttribLocation(ShaderProgramOGL::TexCoordAttrib);
@ -1530,11 +1542,11 @@ CompositorOGL::BindAndDrawQuad(ShaderProgramOGL *aProg,
QuadVBOTexCoordsAttrib(texCoordAttribIndex);
mGLContext->fEnableVertexAttribArray(texCoordAttribIndex);
aProg->SetTextureRect(aTextureRect);
aProg->SetTextureRects(aTextureRects);
}
mGLContext->fEnableVertexAttribArray(vertAttribIndex);
mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4 * aQuads);
}
GLuint

View File

@ -352,15 +352,22 @@ private:
GLuint aSourceFrameBuffer,
GLuint *aFBO, GLuint *aTexture);
GLintptr QuadVBOVertexOffset() { return 0; }
GLintptr QuadVBOTexCoordOffset() { return sizeof(float)*4*2; }
void BindQuadVBO();
void QuadVBOVerticesAttrib(GLuint aAttribIndex);
void QuadVBOTexCoordsAttrib(GLuint aAttribIndex);
void BindAndDrawQuads(ShaderProgramOGL *aProg,
int aQuads,
const gfx::Rect* aLayerRect,
const gfx::Rect* aTextureRect);
void BindAndDrawQuad(ShaderProgramOGL *aProg,
const gfx::Rect& aLayerRect,
const gfx::Rect& aTextureRect = gfx::Rect(0.0f, 0.0f, 1.0f, 1.0f));
const gfx::Rect& aTextureRect = gfx::Rect(0.0f, 0.0f, 1.0f, 1.0f)) {
gfx::Rect layerRects[4];
gfx::Rect textureRects[4];
layerRects[0] = aLayerRect;
textureRects[0] = aTextureRect;
BindAndDrawQuads(aProg, 1, layerRects, textureRects);
}
void BindAndDrawQuadWithTextureRect(ShaderProgramOGL *aProg,
const gfx::Rect& aRect,
const gfx::Rect& aTexCoordRect,

View File

@ -32,10 +32,10 @@ AddUniforms(ProgramProfileOGL& aProfile)
static const char *sKnownUniformNames[] = {
"uLayerTransform",
"uMaskTransform",
"uLayerRect",
"uLayerRects",
"uMatrixProj",
"uTextureTransform",
"uTextureRect",
"uTextureRects",
"uRenderTargetOffset",
"uLayerOpacity",
"uTexture",
@ -146,7 +146,7 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
AddUniforms(result);
vs << "uniform mat4 uMatrixProj;" << endl;
vs << "uniform vec4 uLayerRect;" << endl;
vs << "uniform vec4 uLayerRects[4];" << endl;
vs << "uniform mat4 uLayerTransform;" << endl;
vs << "uniform vec4 uRenderTargetOffset;" << endl;
@ -154,8 +154,8 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
vs << "uniform mat4 uTextureTransform;" << endl;
vs << "uniform vec4 uTextureRect;" << endl;
vs << "attribute vec2 aTexCoord;" << endl;
vs << "uniform vec4 uTextureRects[4];" << endl;
vs << "attribute vec4 aTexCoord;" << endl;
vs << "varying vec2 vTexCoord;" << endl;
}
@ -166,7 +166,9 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
}
vs << "void main() {" << endl;
vs << " vec4 finalPosition = vec4(aVertexCoord.xy * uLayerRect.zw + uLayerRect.xy, 0.0, 1.0);" << endl;
vs << " int vertexID = int(aVertexCoord.w);" << endl;
vs << " vec4 layerRect = uLayerRects[vertexID];" << endl;
vs << " vec4 finalPosition = vec4(aVertexCoord.xy * layerRect.zw + layerRect.xy, 0.0, 1.0);" << endl;
vs << " finalPosition = uLayerTransform * finalPosition;" << endl;
vs << " finalPosition.xyz /= finalPosition.w;" << endl;
@ -184,7 +186,8 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
vs << " finalPosition = uMatrixProj * finalPosition;" << endl;
if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
vs << " vec2 texCoord = aTexCoord * uTextureRect.zw + uTextureRect.xy;" << endl;
vs << " vec4 textureRect = uTextureRects[vertexID];" << endl;
vs << " vec2 texCoord = aTexCoord.xy * textureRect.zw + textureRect.xy;" << endl;
vs << " vTexCoord = (uTextureTransform * vec4(texCoord, 0.0, 1.0)).xy;" << endl;
}

View File

@ -52,10 +52,10 @@ public:
LayerTransform = 0,
MaskTransform,
LayerRect,
LayerRects,
MatrixProj,
TextureTransform,
TextureRect,
TextureRects,
RenderTargetOffset,
LayerOpacity,
Texture,
@ -322,9 +322,12 @@ public:
SetMatrixUniform(KnownUniform::MaskTransform, aMatrix);
}
void SetLayerRect(const gfx::Rect& aRect) {
float vals[4] = { float(aRect.x), float(aRect.y), float(aRect.width), float(aRect.height) };
SetUniform(KnownUniform::LayerRect, 4, vals);
void SetLayerRects(const gfx::Rect* aRects) {
float vals[16] = { aRects[0].x, aRects[0].y, aRects[0].width, aRects[0].height,
aRects[1].x, aRects[1].y, aRects[1].width, aRects[1].height,
aRects[2].x, aRects[2].y, aRects[2].width, aRects[2].height,
aRects[3].x, aRects[3].y, aRects[3].width, aRects[3].height };
SetUniform(KnownUniform::LayerRects, 16, vals);
}
void SetProjectionMatrix(const gfx::Matrix4x4& aMatrix) {
@ -336,9 +339,12 @@ public:
SetMatrixUniform(KnownUniform::TextureTransform, aMatrix);
}
void SetTextureRect(const gfx::Rect& aRect) {
float vals[4] = { float(aRect.x), float(aRect.y), float(aRect.width), float(aRect.height) };
SetUniform(KnownUniform::TextureRect, 4, vals);
void SetTextureRects(const gfx::Rect* aRects) {
float vals[16] = { aRects[0].x, aRects[0].y, aRects[0].width, aRects[0].height,
aRects[1].x, aRects[1].y, aRects[1].width, aRects[1].height,
aRects[2].x, aRects[2].y, aRects[2].width, aRects[2].height,
aRects[3].x, aRects[3].y, aRects[3].width, aRects[3].height };
SetUniform(KnownUniform::TextureRects, 16, vals);
}
void SetRenderOffset(const nsIntPoint& aOffset) {
@ -471,6 +477,7 @@ protected:
case 2: mGL->fUniform2fv(ku.mLocation, 1, ku.mValue.f16v); break;
case 3: mGL->fUniform3fv(ku.mLocation, 1, ku.mValue.f16v); break;
case 4: mGL->fUniform4fv(ku.mLocation, 1, ku.mValue.f16v); break;
case 16: mGL->fUniform4fv(ku.mLocation, 4, ku.mValue.f16v); break;
default:
NS_NOTREACHED("Bogus aLength param");
}

View File

@ -251,6 +251,15 @@ TextureImageTextureSourceOGL::Update(gfx::DataSourceSurface* aSurface,
(mTexImage->GetSize() != size && !aSrcOffset) ||
mTexImage->GetContentType() != gfx::ContentForFormat(aSurface->GetFormat())) {
if (mFlags & TextureFlags::DISALLOW_BIGIMAGE) {
GLint maxTextureSize;
mGL->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxTextureSize);
if (size.width > maxTextureSize || size.height > maxTextureSize) {
NS_WARNING("Texture exceeds maximum texture size, refusing upload");
return false;
}
// Explicitly use CreateBasicTextureImage instead of CreateTextureImage,
// because CreateTextureImage might still choose to create a tiled
// texture image.
mTexImage = CreateBasicTextureImage(mGL, size,
gfx::ContentForFormat(aSurface->GetFormat()),
WrapMode(mGL, mFlags),

View File

@ -123,6 +123,7 @@ gfxFontEntry::gfxFontEntry() :
mHasSpaceFeaturesNonKerning(false),
mSkipDefaultFeatureSpaceCheck(false),
mCheckedForGraphiteTables(false),
mCheckedForGraphiteSmallCaps(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
mCheckedForColorGlyph(false),
@ -156,6 +157,7 @@ gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
mHasSpaceFeaturesNonKerning(false),
mSkipDefaultFeatureSpaceCheck(false),
mCheckedForGraphiteTables(false),
mCheckedForGraphiteSmallCaps(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
mCheckedForColorGlyph(false),
@ -879,6 +881,89 @@ gfxFontEntry::CheckForGraphiteTables()
mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f'));
}
bool
gfxFontEntry::SupportsOpenTypeSmallCaps(int32_t aScript)
{
if (!mSmallCapsSupport) {
mSmallCapsSupport = new nsDataHashtable<nsUint32HashKey,bool>();
}
bool result;
if (mSmallCapsSupport->Get(uint32_t(aScript), &result)) {
return result;
}
result = false;
hb_face_t *face = GetHBFace();
if (hb_ot_layout_has_substitution(face)) {
// Decide what harfbuzz script code will be used for shaping
hb_script_t hbScript;
if (aScript <= MOZ_SCRIPT_INHERITED) {
// For unresolved "common" or "inherited" runs, default to Latin
// for now. (Compare gfxHarfBuzzShaper.)
hbScript = HB_SCRIPT_LATIN;
} else {
hbScript = hb_script_t(GetScriptTagForCode(aScript));
}
// Get the OpenType tag(s) that match this script code
hb_tag_t scriptTags[4] = {
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE
};
hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
// Replace the first remaining NONE with DEFAULT
hb_tag_t* scriptTag = &scriptTags[0];
while (*scriptTag != HB_TAG_NONE) {
++scriptTag;
}
*scriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
// Now check for 'smcp' under the first of those scripts that is present
const hb_tag_t kGSUB = HB_TAG('G','S','U','B');
const hb_tag_t kSMCP = HB_TAG('s','m','c','p');
scriptTag = &scriptTags[0];
while (*scriptTag != HB_TAG_NONE) {
unsigned int scriptIndex;
if (hb_ot_layout_table_find_script(face, kGSUB, *scriptTag,
&scriptIndex)) {
if (hb_ot_layout_language_find_feature(face, kGSUB,
scriptIndex,
HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
kSMCP, nullptr)) {
result = true;
}
break;
}
++scriptTag;
}
}
hb_face_destroy(face);
mSmallCapsSupport->Put(uint32_t(aScript), result);
return result;
}
bool
gfxFontEntry::SupportsGraphiteSmallCaps()
{
if (!mCheckedForGraphiteSmallCaps) {
gr_face* face = GetGrFace();
mHasGraphiteSmallCaps =
gr_face_find_fref(face, TRUETYPE_TAG('s','m','c','p')) != nullptr;
ReleaseGrFace(face);
mCheckedForGraphiteSmallCaps = true;
}
return mHasGraphiteSmallCaps;
}
bool
gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId,
nsTArray<uint16_t>& aLayerGlyphs,
@ -2011,6 +2096,7 @@ gfxFontShaper::MergeFontFeatures(
if (styleRuleFeatures.IsEmpty() &&
aFontFeatures.IsEmpty() &&
!aDisableLigatures &&
!aStyle->smallCaps &&
numAlts == 0) {
return false;
}
@ -2022,6 +2108,10 @@ gfxFontShaper::MergeFontFeatures(
aMergedFeatures.Put(HB_TAG('c','l','i','g'), 0);
}
if (aStyle->smallCaps) {
aMergedFeatures.Put(HB_TAG('s','m','c','p'), 1);
}
// add feature values from font
uint32_t i, count;
@ -2608,6 +2698,16 @@ gfxFont::SpaceMayParticipateInShaping(int32_t aRunScript)
return false;
}
bool
gfxFont::SupportsSmallCaps(int32_t aScript)
{
if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
return GetFontEntry()->SupportsGraphiteSmallCaps();
}
return GetFontEntry()->SupportsOpenTypeSmallCaps(aScript);
}
bool
gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
{
@ -5444,8 +5544,9 @@ gfxFontGroup::InitScriptRun(gfxContext *aContext,
// create the glyph run for this range
if (matchedFont) {
if (mStyle.smallCaps) {
if (!matchedFont->InitSmallCapsRun(aContext, aTextRun,
if (mStyle.smallCaps &&
!matchedFont->SupportsSmallCaps(aRunScript)) {
if (!matchedFont->InitFakeSmallCapsRun(aContext, aTextRun,
aString + runStart,
aOffset + runStart,
matchedLength,
@ -5549,7 +5650,7 @@ gfxFontGroup::InitScriptRun(gfxContext *aContext,
}
bool
gfxFont::InitSmallCapsRun(gfxContext *aContext,
gfxFont::InitFakeSmallCapsRun(gfxContext *aContext,
gfxTextRun *aTextRun,
const uint8_t *aText,
uint32_t aOffset,
@ -5559,12 +5660,12 @@ gfxFont::InitSmallCapsRun(gfxContext *aContext,
{
NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
aLength);
return InitSmallCapsRun(aContext, aTextRun, unicodeString.get(),
return InitFakeSmallCapsRun(aContext, aTextRun, unicodeString.get(),
aOffset, aLength, aMatchType, aScript);
}
bool
gfxFont::InitSmallCapsRun(gfxContext *aContext,
gfxFont::InitFakeSmallCapsRun(gfxContext *aContext,
gfxTextRun *aTextRun,
const char16_t *aText,
uint32_t aOffset,

View File

@ -273,6 +273,9 @@ public:
bool IgnoreGDEF() const { return mIgnoreGDEF; }
bool IgnoreGSUB() const { return mIgnoreGSUB; }
bool SupportsOpenTypeSmallCaps(int32_t aScript);
bool SupportsGraphiteSmallCaps();
virtual bool IsSymbolFont();
virtual bool HasFontTable(uint32_t aTableTag);
@ -541,6 +544,8 @@ public:
bool mSkipDefaultFeatureSpaceCheck : 1;
bool mHasGraphiteTables : 1;
bool mCheckedForGraphiteTables : 1;
bool mHasGraphiteSmallCaps : 1;
bool mCheckedForGraphiteSmallCaps : 1;
bool mHasCmapTable : 1;
bool mGrFaceInitialized : 1;
bool mCheckedForColorGlyph : 1;
@ -562,6 +567,7 @@ public:
nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
nsAutoPtr<gfxMathTable> mMathTable;
nsTArray<gfxFontFeature> mFeatureSettings;
nsAutoPtr<nsDataHashtable<nsUint32HashKey,bool>> mSmallCapsSupport;
uint32_t mLanguageOverride;
// Color Layer font support
@ -1608,6 +1614,9 @@ public:
return mFontEntry->HasGraphiteTables();
}
// whether the font supports "real" small caps or should fake them
bool SupportsSmallCaps(int32_t aScript);
// Subclasses may choose to look up glyph ids for characters.
// If they do not override this, gfxHarfBuzzShaper will fetch the cmap
// table and use that.
@ -1808,7 +1817,7 @@ public:
return mFontEntry->GetUVSGlyph(aCh, aVS);
}
bool InitSmallCapsRun(gfxContext *aContext,
bool InitFakeSmallCapsRun(gfxContext *aContext,
gfxTextRun *aTextRun,
const uint8_t *aText,
uint32_t aOffset,
@ -1816,7 +1825,7 @@ public:
uint8_t aMatchType,
int32_t aScript);
bool InitSmallCapsRun(gfxContext *aContext,
bool InitFakeSmallCapsRun(gfxContext *aContext,
gfxTextRun *aTextRun,
const char16_t *aText,
uint32_t aOffset,

View File

@ -412,7 +412,7 @@ VectorImage::HeapSizeOfVectorImageDocument(nsACString* aDocURL) const
}
nsWindowSizes windowSizes(WindowsMallocSizeOf);
doc->DocAddSizeOfExcludingThis(&windowSizes);
doc->DocAddSizeOfIncludingThis(&windowSizes);
return windowSizes.getTotalSize();
}

View File

@ -163,6 +163,35 @@ function radd_object(i) {
return i;
}
var uceFault_sub_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_sub_number'));
function rsub_number(i) {
var x = 1 - i;
if (uceFault_sub_number(i) || uceFault_sub_number(i))
assertEq(x, -98 /* = 1 - 99 */);
return i;
}
var uceFault_sub_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_sub_float'));
function rsub_float(i) {
var t = Math.fround(1/3);
var fi = Math.fround(i);
var x = Math.fround(Math.fround(Math.fround(Math.fround(t - fi) - t) - fi) - t);
if (uceFault_sub_float(i) || uceFault_sub_float(i))
assertEq(x, -198.3333282470703); /* != -198.33333334326744 (when computed with double subtractions) */
return i;
}
var uceFault_sub_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_sub_object'));
function rsub_object(i) {
var t = i;
var o = { valueOf: function () { return t; } };
var x = o - i; /* computed with t == i, not 1000 */
t = 1000;
if (uceFault_sub_object(i) || uceFault_sub_object(i))
assertEq(x, 0);
return i;
}
for (i = 0; i < 100; i++) {
rbitnot_number(i);
rbitnot_object(i);
@ -180,6 +209,9 @@ for (i = 0; i < 100; i++) {
radd_float(i);
radd_string(i);
radd_object(i);
rsub_number(i);
rsub_float(i);
rsub_object(i);
}
// Test that we can refer multiple time to the same recover instruction, as well

View File

@ -4300,6 +4300,11 @@ class MSub : public MBinaryArithInstruction
void computeRange(TempAllocator &alloc);
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
bool writeRecoverData(CompactBufferWriter &writer) const;
bool canRecoverOnBailout() const {
return specialization_ != MIRType_None;
}
};
class MMul : public MBinaryArithInstruction

View File

@ -330,6 +330,40 @@ RAdd::recover(JSContext *cx, SnapshotIterator &iter) const
return true;
}
bool
MSub::writeRecoverData(CompactBufferWriter &writer) const
{
MOZ_ASSERT(canRecoverOnBailout());
writer.writeUnsigned(uint32_t(RInstruction::Recover_Sub));
writer.writeByte(specialization_ == MIRType_Float32);
return true;
}
RSub::RSub(CompactBufferReader &reader)
{
isFloatOperation_ = reader.readByte();
}
bool
RSub::recover(JSContext *cx, SnapshotIterator &iter) const
{
RootedValue lhs(cx, iter.read());
RootedValue rhs(cx, iter.read());
RootedValue result(cx);
MOZ_ASSERT(!lhs.isObject() && !rhs.isObject());
if (!js::SubValues(cx, &lhs, &rhs, &result))
return false;
// MIRType_Float32 is a specialization embedding the fact that the result is
// rounded to a Float32.
if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
return false;
iter.storeInstructionResult(result);
return true;
}
bool
MNewObject::writeRecoverData(CompactBufferWriter &writer) const
{

View File

@ -25,6 +25,7 @@ namespace jit {
_(Rsh) \
_(Ursh) \
_(Add) \
_(Sub) \
_(NewObject) \
_(NewDerivedTypedObject)
@ -181,6 +182,21 @@ class RAdd MOZ_FINAL : public RInstruction
bool recover(JSContext *cx, SnapshotIterator &iter) const;
};
class RSub MOZ_FINAL : public RInstruction
{
private:
bool isFloatOperation_;
public:
RINSTRUCTION_HEADER_(Sub)
virtual uint32_t numOperands() const {
return 2;
}
bool recover(JSContext *cx, SnapshotIterator &iter) const;
};
class RNewObject MOZ_FINAL : public RInstruction
{
private:

View File

@ -64,7 +64,7 @@ function DocumentWrite(s)
}
function print() {
var s = '';
var s = 'TEST-INFO | ';
var a;
for (var i = 0; i < arguments.length; i++)
{

View File

@ -1840,6 +1840,9 @@ AddTransformedBoundsToRegion(const nsIntRegion& aRegion,
nsIntRegion* aDest)
{
nsIntRect bounds = aRegion.GetBounds();
if (bounds.IsEmpty()) {
return;
}
gfxRect transformed =
aTransform.TransformBounds(gfxRect(bounds.x, bounds.y, bounds.width, bounds.height));
transformed.RoundOut();

View File

@ -2156,6 +2156,68 @@ FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
return NSToCoordRound(multiples*aTileDim + aTilePos);
}
static gfxFloat
LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
const gfxPoint& aGradientEnd,
const gfxPoint& aPoint)
{
gfxPoint d = aGradientEnd - aGradientStart;
gfxPoint p = aPoint - aGradientStart;
/**
* Compute a parameter t such that a line perpendicular to the
* d vector, passing through aGradientStart + d*t, also
* passes through aPoint.
*
* t is given by
* (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
*
* Solving for t we get
* numerator = d.x*p.x + d.y*p.y
* denominator = d.x^2 + d.y^2
* t = numerator/denominator
*
* In nsCSSRendering::PaintGradient we know the length of d
* is not zero.
*/
double numerator = d.x * p.x + d.y * p.y;
double denominator = d.x * d.x + d.y * d.y;
return numerator / denominator;
}
static bool
RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
const gfxMatrix& aPatternMatrix,
const nsTArray<ColorStop>& aStops,
const gfxPoint& aGradientStart,
const gfxPoint& aGradientEnd,
gfxRGBA* aOutEdgeColor)
{
gfxFloat topLeft = LinearGradientStopPositionForPoint(
aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopLeft()));
gfxFloat topRight = LinearGradientStopPositionForPoint(
aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopRight()));
gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomLeft()));
gfxFloat bottomRight = LinearGradientStopPositionForPoint(
aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomRight()));
const ColorStop& firstStop = aStops[0];
if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
*aOutEdgeColor = firstStop.mColor;
return true;
}
const ColorStop& lastStop = aStops.LastElement();
if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
*aOutEdgeColor = lastStop.mColor;
return true;
}
return false;
}
void
nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
nsRenderingContext& aRenderingContext,
@ -2362,11 +2424,13 @@ nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
nsRefPtr<gfxPattern> gradientPattern;
bool forceRepeatToCoverTiles = false;
gfxMatrix matrix;
gfxPoint gradientStart;
gfxPoint gradientEnd;
if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
// Compute the actual gradient line ends we need to pass to cairo after
// stops have been normalized.
gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
@ -2488,6 +2552,9 @@ nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
gfxRect areaToFill =
nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
dirtyAreaToFill.RoundOut();
gfxMatrix ctm = ctx->CurrentMatrix();
bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
@ -2535,8 +2602,18 @@ nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
}
ctx->NewPath();
ctx->Rectangle(fillRect);
gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
gfxRGBA edgeColor;
if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
gradientStart, gradientEnd, &edgeColor)) {
ctx->SetColor(edgeColor);
} else {
ctx->Translate(tileRect.TopLeft());
ctx->SetPattern(gradientPattern);
}
ctx->Fill();
ctx->SetMatrix(ctm);
}

View File

@ -1623,12 +1623,10 @@ GetAnimatedGeometryRootForFrame(nsIFrame* aFrame,
if (!parent)
break;
nsIAtom* parentType = parent->GetType();
#ifdef ANDROID
// Treat the slider thumb as being as an active scrolled root
// on mobile so that it can move without repainting.
// so that it can move without repainting.
if (parentType == nsGkAtoms::sliderFrame)
break;
#endif
// Sticky frames are active if their nearest scrollable frame
// is also active, just keep a record of sticky frames that we
// encounter for now.

View File

@ -2183,7 +2183,7 @@ void
ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists,
bool& aCreateLayer,
bool aCreateLayer,
bool aPositioned)
{
nsITheme* theme = mOuter->PresContext()->GetTheme();
@ -2438,9 +2438,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
}
}
// We put scrollbars in their own layers when this is the root scroll
// frame and we are a toplevel content document. In this situation, the
// scrollbar(s) would normally be assigned their own layer anyway, since
// We put non-overlay scrollbars in their own layers when this is the root
// scroll frame and we are a toplevel content document. In this situation,
// the scrollbar(s) would normally be assigned their own layer anyway, since
// they're not scrolled with the rest of the document. But when both
// scrollbars are visible, the layer's visible rectangle would be the size
// of the viewport, so most layer implementations would create a layer buffer
@ -2475,16 +2475,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
aDirtyRect, aLists);
#ifdef MOZ_WIDGET_GONK
// TODO: only layerize the overlay scrollbars if this scrollframe can be
// panned asynchronously. For now just always layerize on B2G because.
// that's where we want the layerized scrollbars
createLayersForScrollbars = true;
#endif
if (addScrollBars) {
// Add overlay scrollbars.
AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
true);
AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, true, true);
}
return;
@ -2685,14 +2678,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
scrolledContent.BorderBackground()->AppendNewToBottom(layerItem);
}
// Now display overlay scrollbars and the resizer, if we have one.
#ifdef MOZ_WIDGET_GONK
// TODO: only layerize the overlay scrollbars if this scrollframe can be
// panned asynchronously. For now just always layerize on B2G because.
// that's where we want the layerized scrollbars
createLayersForScrollbars = true;
#endif
AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent,
createLayersForScrollbars, true);
// Always create layers for these, so that we don't create a giant layer
// covering the whole scrollport if both scrollbars are visible.
AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent, true, true);
scrolledContent.MoveTo(aLists);
}

View File

@ -68,7 +68,7 @@ public:
void AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists,
bool& aCreateLayer,
bool aCreateLayer,
bool aPositioned);
bool GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea,

View File

@ -592,12 +592,14 @@ hb_ot_layout_collect_lookups
hb_ot_layout_feature_get_lookups
hb_ot_layout_has_positioning
hb_ot_layout_has_substitution
hb_ot_layout_language_find_feature
hb_ot_layout_language_get_feature_indexes
hb_ot_layout_language_get_feature_tags
hb_ot_layout_language_get_required_feature_index
hb_ot_layout_lookup_collect_glyphs
hb_ot_layout_script_get_language_tags
hb_ot_layout_table_choose_script
hb_ot_layout_table_find_script
hb_ot_layout_table_get_script_tags
hb_ot_tag_to_language
hb_ot_tag_to_script

View File

@ -845,7 +845,7 @@ skip-if(B2G&&browserIsRemote) == 401946-1.xul about:blank # bug 974780
== 402567-1.html 402567-1-ref.html
== 402567-2.html 402567-2-ref.html
== 402567-3.html 402567-3-ref.html
skip-if(B2G) == 402567-4.html 402567-4-ref.html
skip-if(B2G) fuzzy-if(gtk2Widget,2,40) == 402567-4.html 402567-4-ref.html
random-if(B2G&&browserIsRemote) == 402629-1.html 402629-1-ref.html
random-if(B2G&&browserIsRemote) == 402629-2.html 402629-2-ref.html
random-if(B2G&&browserIsRemote) == 402629-3.html 402629-3-ref.html

View File

@ -9,11 +9,11 @@
<canvas id="canvas1" width="400" height="400"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('canvas1').getContext('2d');
ctx.font = "40px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "40px monospace";
ctx.fillText("lowercase", 50, 50);
ctx.font = "32px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "32px monospace";
ctx.fillText("SMALLCAPS", 50, 100);
ctx.font = "40px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "40px monospace";
ctx.fillText("CAPITALS", 50, 150);
</script>
</body>

View File

@ -9,11 +9,11 @@
<canvas id="canvas1" width="400" height="400"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('canvas1').getContext('2d');
ctx.font = "40px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "40px monospace";
ctx.fillText("lowercase", 50, 50);
ctx.font = "small-caps 40px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "small-caps 40px monospace";
ctx.fillText("smallcaps", 50, 100);
ctx.font = "small-caps 40px arial, helvetica, roboto, droid sans, sans-serif";
ctx.font = "small-caps 40px monospace";
ctx.fillText("CAPITALS", 50, 150);
</script>
</body>

View File

@ -0,0 +1,94 @@
This Font Software is Copyright (c) 1997-2013, SIL International (http://scripts.sil.org/)
with Reserved Font Names "Charis" and "SIL".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@ -1,6 +1,6 @@
skip-if(B2G) fails-if(Android) == resize.html resize-ref.html
# an offset seems to apply to the native resizer on windows so skip this test for now
skip-if(B2G) fails-if(Android) skip-if(winWidget) == resize-background.html resize-background-ref.html
skip-if(B2G) fails-if(Android) skip-if(winWidget) fuzzy-if(cocoaWidget,1,33) == resize-background.html resize-background-ref.html
skip-if(B2G) fails-if(Android) != ltr.html rtl.html
skip-if(B2G) fails-if(Android) != ltr-scrollbar.html rtl-scrollbar.html
skip-if(B2G) fails-if(Android) != in-ltr-doc-scrollbar.html in-rtl-doc-scrollbar.html

View File

@ -17,7 +17,7 @@ random-if(d2d) == element-paint-transform-02.html element-paint-transform-02-ref
== element-paint-background-size-02.html element-paint-background-size-02-ref.html
== element-paint-transform-repeated.html element-paint-transform-repeated-ref.html
fuzzy-if(d2d,255,24) == element-paint-transform-03.html element-paint-transform-03-ref.html
== element-paint-native-widget.html element-paint-native-widget-ref.html
fuzzy-if(gtk2Widget,1,32) fuzzy-if(cocoaWidget,1,106) == element-paint-native-widget.html element-paint-native-widget-ref.html
== element-paint-subimage-sampling-restriction.html about:blank
== element-paint-clippath.html element-paint-clippath-ref.html
== element-paint-sharpness-01a.html element-paint-sharpness-01b.html

View File

@ -3,8 +3,8 @@ skip-if(B2G) == ellipsis-font-fallback.html ellipsis-font-fallback-ref.html
skip-if(B2G) HTTP(..) == marker-basic.html marker-basic-ref.html
skip-if(B2G) HTTP(..) == marker-string.html marker-string-ref.html
skip-if(Android||B2G) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
skip-if(!gtk2Widget) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
skip-if(B2G) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
skip-if(!gtk2Widget) fuzzy-if(gtk2Widget,1,104) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
skip-if(B2G) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
fuzzy-if(OSX==10.8,1,1) HTTP(..) == anonymous-block.html anonymous-block-ref.html
skip-if(B2G) HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html
HTTP(..) == visibility-hidden.html visibility-hidden-ref.html

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that does NOT support the 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/dejavu-sans/DejaVuSans.ttf);
}
body {
font: 100px test;
}
span {
font-size: 80px;
}
</style>
</head>
<body>
<div>S<span>MALL</span> C<span>APS</span>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that does NOT support the 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/dejavu-sans/DejaVuSans.ttf);
}
body {
font: 100px test;
}
</style>
</head>
<body>
<div style="font-variant:small-caps">Small Caps
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the Graphite 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/sil/CharisSIL-R.ttf);
}
body {
font: 100px test;
}
span {
font-size: 80px;
}
</style>
</head>
<body>
<div>S<span>MALL</span> C<span>APS</span>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the Graphite 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/sil/CharisSIL-R.ttf);
}
body {
font: 100px test;
}
div {
-moz-font-feature-settings: 'smcp' on;
font-feature-settings: 'smcp' on;
}
</style>
</head>
<body>
<div>Small Caps
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the Graphite 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/sil/CharisSIL-R.ttf);
}
body {
font: 100px test;
}
</style>
</head>
<body>
<div style="font-variant:small-caps">Small Caps
</body>
</html>

View File

@ -4,8 +4,13 @@
<title>Test for Greek small-caps</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<style type="text/css">
/* use a known font, to avoid dependency on platform font behavior */
@font-face {
font-family: test;
src: url(../fonts/dejavu-sans/DejaVuSans.ttf);
}
div {
font: 150% serif;
font: 150% test;
margin: 1em;
}
span {

View File

@ -4,8 +4,13 @@
<title>Test for Greek small-caps</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<style type="text/css">
/* use a known font, to avoid dependency on platform font behavior */
@font-face {
font-family: test;
src: url(../fonts/dejavu-sans/DejaVuSans.ttf);
}
div {
font: 150% serif;
font: 150% test;
font-variant: small-caps;
margin: 1em;
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the OpenType 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/LinLibertine_Re-4.7.5.woff);
}
body {
font: 100px test;
}
span {
font-size: 80px;
}
</style>
</head>
<body>
<div>S<span>MALL</span> C<span>APS</span>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the OpenType 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/LinLibertine_Re-4.7.5.woff);
}
body {
font: 100px test;
}
div {
-moz-font-feature-settings: 'smcp' on;
font-feature-settings: 'smcp' on;
}
</style>
</head>
<body>
<div>Small Caps
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* load a font that supports the OpenType 'smcp' feature */
@font-face {
font-family: test;
src: url(../fonts/LinLibertine_Re-4.7.5.woff);
}
body {
font: 100px test;
}
</style>
</head>
<body>
<div style="font-variant:small-caps">Small Caps
</body>
</html>

View File

@ -8,7 +8,12 @@
== capitalize-7a.html capitalize-7-ref.html
== lowercase-1.html lowercase-ref.html
== lowercase-sigma-1.html lowercase-sigma-1-ref.html
== small-caps-1.html small-caps-1-ref.html
fails-if(B2G) random-if(winWidget) == small-caps-1.html small-caps-1-ref.html # fails if default font supports 'smcp'
HTTP(..) == fake-small-caps-1.html fake-small-caps-1-ref.html
HTTP(..) == opentype-small-caps-1.html opentype-small-caps-1-ref.html
HTTP(..) != opentype-small-caps-1.html opentype-small-caps-1-notref.html
HTTP(..) == graphite-small-caps-1.html graphite-small-caps-1-ref.html
HTTP(..) != graphite-small-caps-1.html graphite-small-caps-1-notref.html
== uppercase-1.html uppercase-ref.html
== uppercase-szlig-1.html uppercase-szlig-ref.html
# these use DejaVu Sans via @font-face for consistency of results
@ -22,7 +27,7 @@ skip-if(B2G) HTTP(..) == all-title.html all-title-ref.html # bug 773482
HTTP(..) != small-caps-turkish-1.html small-caps-turkish-1-notref.html
== greek-uppercase-1.html greek-uppercase-1-ref.html
== greek-uppercase-2.html greek-uppercase-2-ref.html
== greek-small-caps-1.html greek-small-caps-1-ref.html
HTTP(..) == greek-small-caps-1.html greek-small-caps-1-ref.html
== fullwidth-1.html fullwidth-1-ref.html
== fullwidth-2.html fullwidth-2-ref.html
== fullwidth-all.html fullwidth-all-ref.html

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="left">
<hbox style="font: 40px arial, helvetica, roboto, droid sans, sans-serif;" align="baseline">
<hbox style="font: 40px monospace;" align="baseline">
<label value="lowercase"/>
<label value="SMALLCAPS" style="font-size: 32px;"/>
<label value="CAPITALS"/>

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="left">
<hbox style="font: 40px arial, helvetica, roboto, droid sans, sans-serif;">
<hbox style="font: 40px monospace;">
<label value="lowercase" style="font-variant: normal;"/>
<label value="smallcaps" style="font-variant: small-caps;"/>
<label value="CAPITALS" style="font-variant: small-caps;"/>

View File

@ -664,11 +664,10 @@ nsSliderFrame::CurrentPositionChanged()
else
newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
#ifdef MOZ_WIDGET_GONK
// avoid putting the scroll thumb at subpixel positions which cause needless invalidations
nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
newThumbRect = newThumbRect.ToNearestPixels(appUnitsPerPixel).ToAppUnits(appUnitsPerPixel);
#endif
// set the rect
thumbFrame->SetRect(newThumbRect);

View File

@ -1054,7 +1054,7 @@ CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
mChunks.Put(aIndex, chunk);
mCachedChunks.Remove(aIndex);
chunk->mFile = this;
chunk->mRemovingChunk = false;
chunk->mActiveChunk = true;
MOZ_ASSERT(chunk->IsReady());
@ -1084,6 +1084,7 @@ CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
chunk = new CacheFileChunk(this, aIndex);
mChunks.Put(aIndex, chunk);
chunk->mActiveChunk = true;
LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
"the disk [this=%p]", chunk.get(), this));
@ -1114,6 +1115,7 @@ CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
// this listener is going to write to the chunk
chunk = new CacheFileChunk(this, aIndex);
mChunks.Put(aIndex, chunk);
chunk->mActiveChunk = true;
LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
chunk.get(), this));
@ -1298,7 +1300,7 @@ CacheFile::MustKeepCachedChunk(uint32_t aIndex)
}
nsresult
CacheFile::RemoveChunk(CacheFileChunk *aChunk)
CacheFile::DeactivateChunk(CacheFileChunk *aChunk)
{
nsresult rv;
@ -1308,7 +1310,7 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
{
CacheFileAutoLock lock(this);
LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%u]",
LOG(("CacheFile::DeactivateChunk() [this=%p, chunk=%p, idx=%u]",
this, aChunk, aChunk->Index()));
MOZ_ASSERT(mReady);
@ -1317,8 +1319,8 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
(!mHandle && !mMemoryOnly && mOpeningFile));
if (aChunk->mRefCnt != 2) {
LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, "
"refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
LOG(("CacheFile::DeactivateChunk() - Chunk is still used [this=%p, "
"chunk=%p, refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
// somebody got the reference before the lock was acquired
return NS_OK;
@ -1340,7 +1342,7 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
if (NS_FAILED(mStatus)) {
// Don't write any chunk to disk since this entry will be doomed
LOG(("CacheFile::RemoveChunk() - Releasing chunk because of status "
LOG(("CacheFile::DeactivateChunk() - Releasing chunk because of status "
"[this=%p, chunk=%p, mStatus=0x%08x]", this, chunk.get(), mStatus));
RemoveChunkInternal(chunk, false);
@ -1348,14 +1350,14 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
}
if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk "
LOG(("CacheFile::DeactivateChunk() - Writing dirty chunk to the disk "
"[this=%p]", this));
mDataIsDirty = true;
rv = chunk->Write(mHandle, this);
if (NS_FAILED(rv)) {
LOG(("CacheFile::RemoveChunk() - CacheFileChunk::Write() failed "
LOG(("CacheFile::DeactivateChunk() - CacheFileChunk::Write() failed "
"synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08x]",
this, chunk.get(), rv));
@ -1365,7 +1367,7 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
CacheFileIOManager::DoomFile(mHandle, nullptr);
return rv;
}
else {
// Chunk will be removed in OnChunkWritten if it is still unused
// chunk needs to be released under the lock to be able to rely on
@ -1373,10 +1375,9 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
chunk = nullptr;
return NS_OK;
}
}
bool keepChunk = ShouldCacheChunk(aChunk->Index());
LOG(("CacheFile::RemoveChunk() - %s unused chunk [this=%p, chunk=%p]",
LOG(("CacheFile::DeactivateChunk() - %s unused chunk [this=%p, chunk=%p]",
keepChunk ? "Caching" : "Releasing", this, chunk.get()));
RemoveChunkInternal(chunk, keepChunk);
@ -1391,7 +1392,9 @@ CacheFile::RemoveChunk(CacheFileChunk *aChunk)
void
CacheFile::RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk)
{
aChunk->mRemovingChunk = true;
AssertOwnsLock();
aChunk->mActiveChunk = false;
ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
aChunk->mFile.forget().take()));
@ -1738,7 +1741,7 @@ CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
file->mChunks.Put(aIdx, aChunk);
aChunk->mFile = file;
aChunk->mRemovingChunk = false;
aChunk->mActiveChunk = true;
MOZ_ASSERT(aChunk->IsReady());

View File

@ -140,7 +140,7 @@ private:
bool ShouldCacheChunk(uint32_t aIndex);
bool MustKeepCachedChunk(uint32_t aIndex);
nsresult RemoveChunk(CacheFileChunk *aChunk);
nsresult DeactivateChunk(CacheFileChunk *aChunk);
void RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk);
int64_t BytesFromChunk(uint32_t aIndex);

View File

@ -45,11 +45,29 @@ protected:
nsRefPtr<CacheFileChunk> mChunk;
};
bool
CacheFileChunk::DispatchRelease()
{
if (NS_IsMainThread()) {
return false;
}
nsRefPtr<nsRunnableMethod<CacheFileChunk, MozExternalRefCountType, false> > event =
NS_NewNonOwningRunnableMethod(this, &CacheFileChunk::Release);
NS_DispatchToMainThread(event);
return true;
}
NS_IMPL_ADDREF(CacheFileChunk)
NS_IMETHODIMP_(MozExternalRefCountType)
CacheFileChunk::Release()
{
if (DispatchRelease()) {
// Redispatched to the main thread.
return mRefCnt - 1;
}
NS_PRECONDITION(0 != mRefCnt, "dup release");
nsrefcnt count = --mRefCnt;
NS_LOG_RELEASE(this, count, "CacheFileChunk");
@ -60,8 +78,18 @@ CacheFileChunk::Release()
return 0;
}
if (!mRemovingChunk && count == 1) {
mFile->RemoveChunk(this);
// We can safely access this chunk after decreasing mRefCnt since we re-post
// all calls to Release() happening off the main thread to the main thread.
// I.e. no other Release() that would delete the object could be run before
// we call CacheFile::DeactivateChunk().
//
// NOTE: we don't grab the CacheFile's lock, so the chunk might be addrefed
// on another thread before CacheFile::DeactivateChunk() grabs the lock on
// this thread. To make sure we won't deactivate chunk that was just returned
// to a new consumer we check mRefCnt once again in
// CacheFile::DeactivateChunk() after we grab the lock.
if (mActiveChunk && count == 1) {
mFile->DeactivateChunk(this);
}
return count;
@ -78,7 +106,7 @@ CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex)
, mState(INITIAL)
, mStatus(NS_OK)
, mIsDirty(false)
, mRemovingChunk(false)
, mActiveChunk(false)
, mDataSize(0)
, mBuf(nullptr)
, mBufSize(0)

View File

@ -68,6 +68,7 @@ class CacheFileChunk : public CacheFileIOListener
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
bool DispatchRelease();
CacheFileChunk(CacheFile *aFile, uint32_t aIndex);
@ -128,7 +129,10 @@ private:
EState mState;
nsresult mStatus;
bool mIsDirty;
bool mRemovingChunk;
bool mActiveChunk; // Is true iff the chunk is in CacheFile::mChunks.
// Adding/removing chunk to/from mChunks as well as
// changing this member happens under the CacheFile's
// lock.
uint32_t mDataSize;
char *mBuf;

View File

@ -113,11 +113,6 @@ function PasswordStore(name, engine) {
Store.call(this, name, engine);
this._nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
XPCOMUtils.defineLazyGetter(this, "DBConnection", function() {
return Services.logins.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.mozIStorageConnection);
});
}
PasswordStore.prototype = {
__proto__: Store.prototype,
@ -162,21 +157,6 @@ PasswordStore.prototype = {
return null;
},
applyIncomingBatch: function applyIncomingBatch(records) {
if (!this.DBConnection) {
return Store.prototype.applyIncomingBatch.call(this, records);
}
return Utils.runInTransaction(this.DBConnection, function() {
return Store.prototype.applyIncomingBatch.call(this, records);
}, this);
},
applyIncoming: function applyIncoming(record) {
Store.prototype.applyIncoming.call(this, record);
this._sleep(0); // Yield back to main thread after synchronous operation.
},
getAllIDs: function PasswordStore__getAllIDs() {
let items = {};
let logins = Services.logins.getAllLogins({});

View File

@ -150,22 +150,6 @@ this.Utils = {
};
},
runInTransaction: function(db, callback, thisObj) {
let hasTransaction = false;
try {
db.beginTransaction();
hasTransaction = true;
} catch(e) { /* om nom nom exceptions */ }
try {
return callback.call(thisObj);
} finally {
if (hasTransaction) {
db.commitTransaction();
}
}
},
/**
* GUIDs are 9 random bytes encoded with base64url (RFC 4648).
* That makes them 12 characters long with 72 bits of entropy.

View File

@ -0,0 +1,233 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 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/. */
/**
* Contains functions shared by different Login Manager components.
*
* This JavaScript module exists in order to share code between the different
* XPCOM components that constitute the Login Manager, including implementations
* of nsILoginManager and nsILoginManagerStorage.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"LoginHelper",
];
////////////////////////////////////////////////////////////////////////////////
//// Globals
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
////////////////////////////////////////////////////////////////////////////////
//// LoginHelper
/**
* Contains functions shared by different Login Manager components.
*/
this.LoginHelper = {
/**
* Due to the way the signons2.txt file is formatted, we need to make
* sure certain field values or characters do not cause the file to
* be parsed incorrectly. Reject hostnames that we can't store correctly.
*
* @throws String with English message in case validation failed.
*/
checkHostnameValue: function (aHostname)
{
// Nulls are invalid, as they don't round-trip well. Newlines are also
// invalid for any field stored as plaintext, and a hostname made of a
// single dot cannot be stored in the legacy format.
if (aHostname == "." ||
aHostname.indexOf("\r") != -1 ||
aHostname.indexOf("\n") != -1 ||
aHostname.indexOf("\0") != -1) {
throw "Invalid hostname";
}
},
/**
* Due to the way the signons2.txt file is formatted, we need to make
* sure certain field values or characters do not cause the file to
* be parsed incorrectly. Reject logins that we can't store correctly.
*
* @throws String with English message in case validation failed.
*/
checkLoginValues: function (aLogin)
{
function badCharacterPresent(l, c)
{
return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
(l.httpRealm && l.httpRealm.indexOf(c) != -1) ||
l.hostname.indexOf(c) != -1 ||
l.usernameField.indexOf(c) != -1 ||
l.passwordField.indexOf(c) != -1);
}
// Nulls are invalid, as they don't round-trip well.
// Mostly not a formatting problem, although ".\0" can be quirky.
if (badCharacterPresent(aLogin, "\0")) {
throw "login values can't contain nulls";
}
// In theory these nulls should just be rolled up into the encrypted
// values, but nsISecretDecoderRing doesn't use nsStrings, so the
// nulls cause truncation. Check for them here just to avoid
// unexpected round-trip surprises.
if (aLogin.username.indexOf("\0") != -1 ||
aLogin.password.indexOf("\0") != -1) {
throw "login values can't contain nulls";
}
// Newlines are invalid for any field stored as plaintext.
if (badCharacterPresent(aLogin, "\r") ||
badCharacterPresent(aLogin, "\n")) {
throw "login values can't contain newlines";
}
// A line with just a "." can have special meaning.
if (aLogin.usernameField == "." ||
aLogin.formSubmitURL == ".") {
throw "login values can't be periods";
}
// A hostname with "\ \(" won't roundtrip.
// eg host="foo (", realm="bar" --> "foo ( (bar)"
// vs host="foo", realm=" (bar" --> "foo ( (bar)"
if (aLogin.hostname.indexOf(" (") != -1) {
throw "bad parens in hostname";
}
},
/**
* Creates a new login object that results by modifying the given object with
* the provided data.
*
* @param aOldStoredLogin
* Existing nsILoginInfo object to modify.
* @param aNewLoginData
* The new login values, either as nsILoginInfo or nsIProperyBag.
*
* @return The newly created nsILoginInfo object.
*
* @throws String with English message in case validation failed.
*/
buildModifiedLogin: function (aOldStoredLogin, aNewLoginData)
{
function bagHasProperty(aPropName)
{
try {
aNewLoginData.getProperty(aPropName);
return true;
} catch (ex) { }
return false;
}
aOldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
let newLogin;
if (aNewLoginData instanceof Ci.nsILoginInfo) {
// Clone the existing login to get its nsILoginMetaInfo, then init it
// with the replacement nsILoginInfo data from the new login.
newLogin = aOldStoredLogin.clone();
newLogin.init(aNewLoginData.hostname,
aNewLoginData.formSubmitURL, aNewLoginData.httpRealm,
aNewLoginData.username, aNewLoginData.password,
aNewLoginData.usernameField, aNewLoginData.passwordField);
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
// Automatically update metainfo when password is changed.
if (newLogin.password != aOldStoredLogin.password) {
newLogin.timePasswordChanged = Date.now();
}
} else if (aNewLoginData instanceof Ci.nsIPropertyBag) {
// Clone the existing login, along with all its properties.
newLogin = aOldStoredLogin.clone();
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
// Automatically update metainfo when password is changed.
// (Done before the main property updates, lest the caller be
// explicitly updating both .password and .timePasswordChanged)
if (bagHasProperty("password")) {
let newPassword = aNewLoginData.getProperty("password");
if (newPassword != aOldStoredLogin.password) {
newLogin.timePasswordChanged = Date.now();
}
}
let propEnum = aNewLoginData.enumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
switch (prop.name) {
// nsILoginInfo
case "hostname":
case "httpRealm":
case "formSubmitURL":
case "username":
case "password":
case "usernameField":
case "passwordField":
// nsILoginMetaInfo
case "guid":
case "timeCreated":
case "timeLastUsed":
case "timePasswordChanged":
case "timesUsed":
newLogin[prop.name] = prop.value;
break;
// Fake property, allows easy incrementing.
case "timesUsedIncrement":
newLogin.timesUsed += prop.value;
break;
// Fail if caller requests setting an unknown property.
default:
throw "Unexpected propertybag item: " + prop.name;
}
}
} else {
throw "newLoginData needs an expected interface!";
}
// Sanity check the login
if (newLogin.hostname == null || newLogin.hostname.length == 0) {
throw "Can't add a login with a null or empty hostname.";
}
// For logins w/o a username, set to "", not null.
if (newLogin.username == null) {
throw "Can't add a login with a null username.";
}
if (newLogin.password == null || newLogin.password.length == 0) {
throw "Can't add a login with a null or empty password.";
}
if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
// We have a form submit URL. Can't have a HTTP realm.
if (newLogin.httpRealm != null) {
throw "Can't add a login with both a httpRealm and formSubmitURL.";
}
} else if (newLogin.httpRealm) {
// We have a HTTP realm. Can't have a form submit URL.
if (newLogin.formSubmitURL != null) {
throw "Can't add a login with both a httpRealm and formSubmitURL.";
}
} else {
// Need one or the other!
throw "Can't add a login without a httpRealm or formSubmitURL.";
}
// Throws if there are bogus values.
this.checkLoginValues(newLogin);
return newLogin;
},
};

View File

@ -0,0 +1,180 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 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/. */
/**
* Provides an object that has a method to import login-related data from the
* previous SQLite storage format.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"LoginImport",
];
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
////////////////////////////////////////////////////////////////////////////////
//// LoginImport
/**
* Provides an object that has a method to import login-related data from the
* previous SQLite storage format.
*
* @param aStore
* LoginStore object where imported data will be added.
* @param aPath
* String containing the file path of the SQLite login database.
*/
this.LoginImport = function (aStore, aPath)
{
this.store = aStore;
this.path = aPath;
}
this.LoginImport.prototype = {
/**
* LoginStore object where imported data will be added.
*/
store: null,
/**
* String containing the file path of the SQLite login database.
*/
path: null,
/**
* Imports login-related data from the previous SQLite storage format.
*/
import: Task.async(function* () {
// We currently migrate data directly from the database to the JSON store at
// first run, then we set a preference to prevent repeating the import.
// Thus, merging with existing data is not a use case we support. This
// restriction might be removed to support re-importing passwords set by an
// old version by flipping the import preference and restarting.
if (this.store.data.logins.length > 0 ||
this.store.data.disabledHosts.length > 0) {
throw new Error("Unable to import saved passwords because some data " +
"has already been imported or saved.");
}
// When a timestamp is not specified, we will use the same reference time.
let referenceTimeMs = Date.now();
let connection = yield Sqlite.openConnection({ path: this.path });
try {
let schemaVersion = yield connection.getSchemaVersion();
// We support importing database schema versions from 3 onwards.
// Version 3 was implemented in bug 316084 (Firefox 3.6, March 2009).
// Version 4 was implemented in bug 465636 (Firefox 4, March 2010).
// Version 5 was implemented in bug 718817 (Firefox 13, February 2012).
if (schemaVersion < 3) {
throw new Error("Unable to import saved passwords because " +
"the existing profile is too old.");
}
let rows = yield connection.execute("SELECT * FROM moz_logins");
for (let row of rows) {
try {
let hostname = row.getResultByName("hostname");
let httpRealm = row.getResultByName("httpRealm");
let formSubmitURL = row.getResultByName("formSubmitURL");
let usernameField = row.getResultByName("usernameField");
let passwordField = row.getResultByName("passwordField");
let encryptedUsername = row.getResultByName("encryptedUsername");
let encryptedPassword = row.getResultByName("encryptedPassword");
// The "guid" field was introduced in schema version 2, and the
// "enctype" field was introduced in schema version 3. We don't
// support upgrading from older versions of the database.
let guid = row.getResultByName("guid");
let encType = row.getResultByName("encType");
// The time and count fields were introduced in schema version 4.
let timeCreated = null;
let timeLastUsed = null;
let timePasswordChanged = null;
let timesUsed = null;
try {
timeCreated = row.getResultByName("timeCreated");
timeLastUsed = row.getResultByName("timeLastUsed");
timePasswordChanged = row.getResultByName("timePasswordChanged");
timesUsed = row.getResultByName("timesUsed");
} catch (ex) { }
// These columns may be null either because they were not present in
// the database or because the record was created on a new schema
// version by an old application version.
if (!timeCreated) {
timeCreated = referenceTimeMs;
}
if (!timeLastUsed) {
timeLastUsed = referenceTimeMs;
}
if (!timePasswordChanged) {
timePasswordChanged = referenceTimeMs;
}
if (!timesUsed) {
timesUsed = 1;
}
this.store.data.logins.push({
id: this.store.data.nextId++,
hostname: hostname,
httpRealm: httpRealm,
formSubmitURL: formSubmitURL,
usernameField: usernameField,
passwordField: passwordField,
encryptedUsername: encryptedUsername,
encryptedPassword: encryptedPassword,
guid: guid,
encType: encType,
timeCreated: timeCreated,
timeLastUsed: timeLastUsed,
timePasswordChanged: timePasswordChanged,
timesUsed: timesUsed,
});
} catch (ex) {
Cu.reportError("Error importing login: " + ex);
}
}
rows = yield connection.execute("SELECT * FROM moz_disabledHosts");
for (let row of rows) {
try {
let id = row.getResultByName("id");
let hostname = row.getResultByName("hostname");
this.store.data.disabledHosts.push({
id: this.store.data.nextId++,
hostname: hostname,
});
} catch (ex) {
Cu.reportError("Error importing disabled host: " + ex);
}
}
} finally {
yield connection.close();
}
}),
};

View File

@ -0,0 +1,301 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 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/. */
/**
* Handles serialization of login-related data and persistence into a file.
*
* This modules handles the raw data stored in JavaScript serializable objects,
* and contains no special validation or query logic, that is handled entirely
* by "storage.js" instead.
*
* The data can be manipulated only after it has been loaded from disk. The
* load process can happen asynchronously, through the "load" method, or
* synchronously, through "ensureDataReady". After any modification, the
* "saveSoon" method must be called to flush the data to disk asynchronously.
*
* The raw data should be manipulated synchronously, without waiting for the
* event loop or for promise resolution, so that the saved file is always
* consistent. This synchronous approach also simplifies the query and update
* logic. For example, it is possible to find an object and modify it
* immediately without caring whether other code modifies it in the meantime.
*
* An asynchronous shutdown observer makes sure that data is always saved before
* the browser is closed. The data cannot be modified during shutdown.
*
* The file is stored in JSON format, without indentation, using UTF-8 encoding.
* With indentation applied, the file would look like this:
*
* {
* "logins": [
* {
* "id": 2,
* "hostname": "http://www.example.com",
* "httpRealm": null,
* "formSubmitURL": "http://www.example.com/submit-url",
* "usernameField": "username_field",
* "passwordField": "password_field",
* "encryptedUsername": "...",
* "encryptedPassword": "...",
* "guid": "...",
* "encType": 1,
* "timeCreated": 1262304000000,
* "timeLastUsed": 1262304000000,
* "timePasswordChanged": 1262476800000,
* "timesUsed": 1
* },
* {
* "id": 4,
* (...)
* }
* ],
* "disabledHosts": [
* "http://www.example.org",
* "http://www.example.net"
* ],
* "nextId": 10,
* "version": 1
* }
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"LoginStore",
];
////////////////////////////////////////////////////////////////////////////////
//// Globals
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm")
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
return new TextDecoder();
});
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
return new TextEncoder();
});
const FileInputStream =
Components.Constructor("@mozilla.org/network/file-input-stream;1",
"nsIFileInputStream", "init");
/**
* Delay between a change to the login data and the related save operation.
*/
const kSaveDelayMs = 1500;
/**
* Current data version assigned by the code that last touched the data.
*
* This number should be updated only when it is important to understand whether
* an old version of the code has touched the data, for example to execute an
* update logic. In most cases, this number should not be changed, in
* particular when no special one-time update logic is needed.
*
* For example, this number should NOT be changed when a new optional field is
* added to a login entry.
*/
const kDataVersion = 1;
////////////////////////////////////////////////////////////////////////////////
//// LoginStore
/**
* Handles serialization of login-related data and persistence into a file.
*
* @param aPath
* String containing the file path where data should be saved.
*/
function LoginStore(aPath)
{
this.path = aPath;
this._saver = new DeferredTask(() => this.save(), kSaveDelayMs);
AsyncShutdown.profileBeforeChange.addBlocker("Login store: writing data",
() => this._saver.finalize());
}
LoginStore.prototype = {
/**
* String containing the file path where data should be saved.
*/
path: "",
/**
* Serializable object containing the login-related data. This is populated
* directly with the data loaded from the file, and is saved without
* modifications.
*
* This contains one property for each list.
*/
data: null,
/**
* True when data has been loaded.
*/
dataReady: false,
/**
* Loads persistent data from the file to memory.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
load: function ()
{
return Task.spawn(function () {
try {
let bytes = yield OS.File.read(this.path);
// If synchronous loading happened in the meantime, exit now.
if (this.dataReady) {
return;
}
this.data = JSON.parse(gTextDecoder.decode(bytes));
} catch (ex) {
// If an exception occurred because the file did not exist, we should
// just start with new data. Other errors may indicate that the file is
// corrupt, thus we move it to a backup location before allowing it to
// be overwritten by an empty file.
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
Cu.reportError(ex);
// Move the original file to a backup location, ignoring errors.
try {
let openInfo = yield OS.File.openUnique(this.path + ".corrupt",
{ humanReadable: true });
yield openInfo.file.close();
yield OS.File.move(this.path, openInfo.path);
} catch (e2) {
Cu.reportError(e2);
}
}
// In any case, initialize a new object to host the data.
this.data = {
nextId: 1,
};
}
this._processLoadedData();
}.bind(this));
},
/**
* Loads persistent data from the file to memory, synchronously.
*/
ensureDataReady: function ()
{
if (this.dataReady) {
return;
}
try {
// This reads the file and automatically detects the UTF-8 encoding.
let inputStream = new FileInputStream(new FileUtils.File(this.path),
FileUtils.MODE_RDONLY,
FileUtils.PERMS_FILE, 0)
try {
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
this.data = json.decodeFromStream(inputStream,
inputStream.available());
} finally {
inputStream.close();
}
} catch (ex) {
// If an exception occurred because the file did not exist, we should just
// start with new data. Other errors may indicate that the file is
// corrupt, thus we move it to a backup location before allowing it to be
// overwritten by an empty file.
if (!(ex instanceof Components.Exception &&
ex.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
Cu.reportError(ex);
// Move the original file to a backup location, ignoring errors.
try {
let originalFile = new FileUtils.File(this.path);
let backupFile = originalFile.clone();
backupFile.leafName += ".corrupt";
backupFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE,
FileUtils.PERMS_FILE);
backupFile.remove(false);
originalFile.moveTo(backupFile.parent, backupFile.leafName);
} catch (e2) {
Cu.reportError(e2);
}
}
// In any case, initialize a new object to host the data.
this.data = {
nextId: 1,
};
}
this._processLoadedData();
},
/**
* Synchronously work on the data just loaded into memory.
*/
_processLoadedData: function ()
{
// Create any arrays that are not present in the saved file.
if (!this.data.logins) {
this.data.logins = [];
}
if (!this.data.disabledHosts) {
this.data.disabledHosts = [];
}
// Indicate that the current version of the code has touched the file.
this.data.version = kDataVersion;
this.dataReady = true;
},
/**
* Called when the data changed, this triggers asynchronous serialization.
*/
saveSoon: function () this._saver.arm(),
/**
* DeferredTask that handles the save operation.
*/
_saver: null,
/**
* Saves persistent data from memory to the file.
*
* If an error occurs, the previous file is not deleted.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
save: function ()
{
return Task.spawn(function () {
// Create or overwrite the file.
let bytes = gTextEncoder.encode(JSON.stringify(this.data));
yield OS.File.writeAtomic(this.path, bytes,
{ tmpPath: this.path + ".tmp" });
}.bind(this));
},
};

View File

@ -23,18 +23,31 @@ XPIDL_MODULE = 'loginmgr'
EXTRA_COMPONENTS += [
'crypto-SDR.js',
'nsLoginInfo.js',
'nsLoginManager.js',
'nsLoginManagerPrompter.js',
'passwordmgr.manifest',
]
EXTRA_PP_COMPONENTS += [
'storage-mozStorage.js',
'nsLoginManager.js',
'passwordmgr.manifest',
]
EXTRA_JS_MODULES += [
'InsecurePasswordUtils.jsm',
'LoginHelper.jsm',
'LoginManagerContent.jsm',
]
if CONFIG['OS_TARGET'] == 'Android':
EXTRA_COMPONENTS += [
'storage-mozStorage.js',
]
else:
EXTRA_COMPONENTS += [
'storage-json.js',
]
EXTRA_JS_MODULES += [
'LoginImport.jsm',
'LoginStore.jsm',
]
JAR_MANIFESTS += ['jar.mn']

View File

@ -125,7 +125,11 @@ LoginManager.prototype = {
_initStorage : function () {
#ifdef ANDROID
var contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
#else
var contractID = "@mozilla.org/login-manager/storage/json;1";
#endif
try {
var catMan = Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);

View File

@ -6,7 +6,12 @@ component {8aa66d77-1bbb-45a6-991e-b8f47751c291} nsLoginManagerPrompter.js
contract @mozilla.org/login-manager/prompter;1 {8aa66d77-1bbb-45a6-991e-b8f47751c291}
component {0f2f347c-1e4f-40cc-8efd-792dea70a85e} nsLoginInfo.js
contract @mozilla.org/login-manager/loginInfo;1 {0f2f347c-1e4f-40cc-8efd-792dea70a85e}
#ifdef ANDROID
component {8c2023b9-175c-477e-9761-44ae7b549756} storage-mozStorage.js
contract @mozilla.org/login-manager/storage/mozStorage;1 {8c2023b9-175c-477e-9761-44ae7b549756}
#else
component {c00c432d-a0c9-46d7-bef6-9c45b4d07341} storage-json.js
contract @mozilla.org/login-manager/storage/json;1 {c00c432d-a0c9-46d7-bef6-9c45b4d07341}
#endif
component {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309} crypto-SDR.js
contract @mozilla.org/login-manager/crypto/SDR;1 {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}

View File

@ -0,0 +1,654 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=4 et lcs=trail\:.,tab\:>~ : */
/* 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/. */
/**
* nsILoginManagerStorage implementation for the JSON back-end.
*/
////////////////////////////////////////////////////////////////////////////////
//// Globals
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
"resource://gre/modules/LoginImport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
"resource://gre/modules/LoginStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
////////////////////////////////////////////////////////////////////////////////
//// LoginManagerStorage_json
this.LoginManagerStorage_json = function () {}
this.LoginManagerStorage_json.prototype = {
classID : Components.ID("{c00c432d-a0c9-46d7-bef6-9c45b4d07341}"),
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
__crypto : null, // nsILoginManagerCrypto service
get _crypto() {
if (!this.__crypto)
this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
getService(Ci.nsILoginManagerCrypto);
return this.__crypto;
},
/*
* log
*
* Internal function for logging debug messages to the Error Console.
*/
log : function (message) {
if (!this._debug)
return;
dump("PwMgr json: " + message + "\n");
Services.console.logStringMessage("PwMgr json: " + message);
},
_debug : false,
/*
* initialize
*
*/
initialize : function () {
this._debug = Services.prefs.getBoolPref("signon.debug");
try {
// Force initialization of the crypto module.
// See bug 717490 comment 17.
this._crypto;
// Set the reference to LoginStore synchronously.
let jsonPath = OS.Path.join(OS.Constants.Path.profileDir,
"logins.json");
this._store = new LoginStore(jsonPath);
return Task.spawn(function () {
// Load the data asynchronously.
this.log("Opening database at " + this._store.path);
yield this._store.load();
// The import from previous versions operates the first time
// that this built-in storage back-end is used. This may be
// later than expected, in case add-ons have registered an
// alternate storage that disabled the default one.
try {
if (Services.prefs.getBoolPref("signon.importedFromSqlite")) {
return;
}
} catch (ex) {
// If the preference does not exist, we need to import.
}
// Import only happens asynchronously.
let sqlitePath = OS.Path.join(OS.Constants.Path.profileDir,
"signons.sqlite");
if (yield OS.File.exists(sqlitePath)) {
let loginImport = new LoginImport(this._store, sqlitePath);
// Failures during import, for example due to a corrupt
// file or a schema version that is too old, will not
// prevent us from marking the operation as completed.
// At the next startup, we will not try the import again.
yield loginImport.import().catch(Cu.reportError);
this._store.saveSoon();
}
// We won't attempt import again on next startup.
Services.prefs.setBoolPref("signon.importedFromSqlite", true);
}.bind(this)).catch(Cu.reportError);
} catch (e) {
this.log("Initialization failed: " + e);
throw "Initialization failed";
}
},
/*
* terminate
*
* Internal method used by regression tests only. It is called before
* replacing this storage module with a new instance.
*/
terminate : function () {
this._store._saver.disarm();
return this._store.save();
},
/*
* addLogin
*
*/
addLogin : function (login) {
this._store.ensureDataReady();
let encUsername, encPassword;
// Throws if there are bogus values.
LoginHelper.checkLoginValues(login);
[encUsername, encPassword, encType] = this._encryptLogin(login);
// Clone the login, so we don't modify the caller's object.
let loginClone = login.clone();
// Initialize the nsILoginMetaInfo fields, unless the caller gave us values
loginClone.QueryInterface(Ci.nsILoginMetaInfo);
if (loginClone.guid) {
if (!this._isGuidUnique(loginClone.guid))
throw "specified GUID already exists";
} else {
loginClone.guid = gUUIDGenerator.generateUUID().toString();
}
// Set timestamps
let currentTime = Date.now();
if (!loginClone.timeCreated)
loginClone.timeCreated = currentTime;
if (!loginClone.timeLastUsed)
loginClone.timeLastUsed = currentTime;
if (!loginClone.timePasswordChanged)
loginClone.timePasswordChanged = currentTime;
if (!loginClone.timesUsed)
loginClone.timesUsed = 1;
this._store.data.logins.push({
id: this._store.data.nextId++,
hostname: loginClone.hostname,
httpRealm: loginClone.httpRealm,
formSubmitURL: loginClone.formSubmitURL,
usernameField: loginClone.usernameField,
passwordField: loginClone.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: loginClone.guid,
encType: encType,
timeCreated: loginClone.timeCreated,
timeLastUsed: loginClone.timeLastUsed,
timePasswordChanged: loginClone.timePasswordChanged,
timesUsed: loginClone.timesUsed
});
this._store.saveSoon();
// Send a notification that a login was added.
this._sendNotification("addLogin", loginClone);
},
/*
* removeLogin
*
*/
removeLogin : function (login) {
this._store.ensureDataReady();
let [idToDelete, storedLogin] = this._getIdForLogin(login);
if (!idToDelete)
throw "No matching logins";
let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
if (foundIndex != -1) {
this._store.data.logins.splice(foundIndex, 1);
this._store.saveSoon();
}
this._sendNotification("removeLogin", storedLogin);
},
/*
* modifyLogin
*
*/
modifyLogin : function (oldLogin, newLoginData) {
this._store.ensureDataReady();
let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
if (!idToModify)
throw "No matching logins";
let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
// Check if the new GUID is duplicate.
if (newLogin.guid != oldStoredLogin.guid &&
!this._isGuidUnique(newLogin.guid))
{
throw "specified GUID already exists";
}
// Look for an existing entry in case key properties changed.
if (!newLogin.matches(oldLogin, true)) {
let logins = this.findLogins({}, newLogin.hostname,
newLogin.formSubmitURL,
newLogin.httpRealm);
if (logins.some(login => newLogin.matches(login, true)))
throw "This login already exists.";
}
// Get the encrypted value of the username and password.
let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);
for (let loginItem of this._store.data.logins) {
if (loginItem.id == idToModify) {
loginItem.hostname = newLogin.hostname;
loginItem.httpRealm = newLogin.httpRealm;
loginItem.formSubmitURL = newLogin.formSubmitURL;
loginItem.usernameField = newLogin.usernameField;
loginItem.passwordField = newLogin.passwordField;
loginItem.encryptedUsername = encUsername;
loginItem.encryptedPassword = encPassword;
loginItem.guid = newLogin.guid;
loginItem.encType = encType;
loginItem.timeCreated = newLogin.timeCreated;
loginItem.timeLastUsed = newLogin.timeLastUsed;
loginItem.timePasswordChanged = newLogin.timePasswordChanged;
loginItem.timesUsed = newLogin.timesUsed;
this._store.saveSoon();
break;
}
}
this._sendNotification("modifyLogin", [oldStoredLogin, newLogin]);
},
/*
* getAllLogins
*
* Returns an array of nsILoginInfo.
*/
getAllLogins : function (count) {
let [logins, ids] = this._searchLogins({});
// decrypt entries for caller.
logins = this._decryptLogins(logins);
this.log("_getAllLogins: returning " + logins.length + " logins.");
if (count)
count.value = logins.length; // needed for XPCOM
return logins;
},
/*
* searchLogins
*
* Public wrapper around _searchLogins to convert the nsIPropertyBag to a
* JavaScript object and decrypt the results.
*
* Returns an array of decrypted nsILoginInfo.
*/
searchLogins : function(count, matchData) {
let realMatchData = {};
// Convert nsIPropertyBag to normal JS object
let propEnum = matchData.enumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
realMatchData[prop.name] = prop.value;
}
let [logins, ids] = this._searchLogins(realMatchData);
// Decrypt entries found for the caller.
logins = this._decryptLogins(logins);
count.value = logins.length; // needed for XPCOM
return logins;
},
/*
* _searchLogins
*
* Private method to perform arbitrary searches on any field. Decryption is
* left to the caller.
*
* Returns [logins, ids] for logins that match the arguments, where logins
* is an array of encrypted nsLoginInfo and ids is an array of associated
* ids in the database.
*/
_searchLogins : function (matchData) {
this._store.ensureDataReady();
let conditions = [];
function match(aLogin) {
for (let field in matchData) {
let value = matchData[field];
switch (field) {
// Historical compatibility requires this special case
case "formSubmitURL":
if (value != null) {
if (aLogin.formSubmitURL != "" && aLogin.formSubmitURL != value) {
return false;
}
break;
}
// Normal cases.
case "hostname":
case "httpRealm":
case "id":
case "usernameField":
case "passwordField":
case "encryptedUsername":
case "encryptedPassword":
case "guid":
case "encType":
case "timeCreated":
case "timeLastUsed":
case "timePasswordChanged":
case "timesUsed":
if (value == null && aLogin[field]) {
return false;
} else if (aLogin[field] != value) {
return false;
}
break;
// Fail if caller requests an unknown property.
default:
throw "Unexpected field: " + field;
}
}
return true;
}
let foundLogins = [], foundIds = [];
for (let loginItem of this._store.data.logins) {
if (match(loginItem)) {
// Create the new nsLoginInfo object, push to array
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
login.init(loginItem.hostname, loginItem.formSubmitURL,
loginItem.httpRealm, loginItem.encryptedUsername,
loginItem.encryptedPassword, loginItem.usernameField,
loginItem.passwordField);
// set nsILoginMetaInfo values
login.QueryInterface(Ci.nsILoginMetaInfo);
login.guid = loginItem.guid;
login.timeCreated = loginItem.timeCreated;
login.timeLastUsed = loginItem.timeLastUsed;
login.timePasswordChanged = loginItem.timePasswordChanged;
login.timesUsed = loginItem.timesUsed;
foundLogins.push(login);
foundIds.push(loginItem.id);
}
}
this.log("_searchLogins: returning " + foundLogins.length + " logins");
return [foundLogins, foundIds];
},
/*
* removeAllLogins
*
* Removes all logins from storage.
*
* Disabled hosts are kept, as one presumably doesn't want to erase those.
*/
removeAllLogins : function () {
this._store.ensureDataReady();
this.log("Removing all logins");
this._store.data.logins = [];
this._store.saveSoon();
this._sendNotification("removeAllLogins", null);
},
/*
* getAllDisabledHosts
*
*/
getAllDisabledHosts : function (count) {
this._store.ensureDataReady();
let disabledHosts = this._store.data.disabledHosts.slice(0);
this.log("_getAllDisabledHosts: returning " + disabledHosts.length + " disabled hosts.");
if (count)
count.value = disabledHosts.length; // needed for XPCOM
return disabledHosts;
},
/*
* getLoginSavingEnabled
*
*/
getLoginSavingEnabled : function (hostname) {
this._store.ensureDataReady();
this.log("Getting login saving is enabled for " + hostname);
return this._store.data.disabledHosts.indexOf(hostname) == -1;
},
/*
* setLoginSavingEnabled
*
*/
setLoginSavingEnabled : function (hostname, enabled) {
this._store.ensureDataReady();
// Throws if there are bogus values.
LoginHelper.checkHostnameValue(hostname);
this.log("Setting login saving enabled for " + hostname + " to " + enabled);
let foundIndex = this._store.data.disabledHosts.indexOf(hostname);
if (enabled) {
if (foundIndex != -1) {
this._store.data.disabledHosts.splice(foundIndex, 1);
this._store.saveSoon();
}
} else {
if (foundIndex == -1) {
this._store.data.disabledHosts.push(hostname);
this._store.saveSoon();
}
}
this._sendNotification(enabled ? "hostSavingEnabled" : "hostSavingDisabled", hostname);
},
/*
* findLogins
*
*/
findLogins : function (count, hostname, formSubmitURL, httpRealm) {
let loginData = {
hostname: hostname,
formSubmitURL: formSubmitURL,
httpRealm: httpRealm
};
let matchData = { };
for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
if (loginData[field] != '')
matchData[field] = loginData[field];
let [logins, ids] = this._searchLogins(matchData);
// Decrypt entries found for the caller.
logins = this._decryptLogins(logins);
this.log("_findLogins: returning " + logins.length + " logins");
count.value = logins.length; // needed for XPCOM
return logins;
},
/*
* countLogins
*
*/
countLogins : function (hostname, formSubmitURL, httpRealm) {
let count = {};
let loginData = {
hostname: hostname,
formSubmitURL: formSubmitURL,
httpRealm: httpRealm
};
let matchData = { };
for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
if (loginData[field] != '')
matchData[field] = loginData[field];
let [logins, ids] = this._searchLogins(matchData);
this.log("_countLogins: counted logins: " + logins.length);
return logins.length;
},
/*
* uiBusy
*/
get uiBusy() {
return this._crypto.uiBusy;
},
/*
* isLoggedIn
*/
get isLoggedIn() {
return this._crypto.isLoggedIn;
},
/*
* _sendNotification
*
* Send a notification when stored data is changed.
*/
_sendNotification : function (changeType, data) {
let dataObject = data;
// Can't pass a raw JS string or array though notifyObservers(). :-(
if (data instanceof Array) {
dataObject = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
for (let i = 0; i < data.length; i++)
dataObject.appendElement(data[i], false);
} else if (typeof(data) == "string") {
dataObject = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
dataObject.data = data;
}
Services.obs.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
},
/*
* _getIdForLogin
*
* Returns an array with two items: [id, login]. If the login was not
* found, both items will be null. The returned login contains the actual
* stored login (useful for looking at the actual nsILoginMetaInfo values).
*/
_getIdForLogin : function (login) {
let matchData = { };
for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
if (login[field] != '')
matchData[field] = login[field];
let [logins, ids] = this._searchLogins(matchData);
let id = null;
let foundLogin = null;
// The specified login isn't encrypted, so we need to ensure
// the logins we're comparing with are decrypted. We decrypt one entry
// at a time, lest _decryptLogins return fewer entries and screw up
// indices between the two.
for (let i = 0; i < logins.length; i++) {
let [decryptedLogin] = this._decryptLogins([logins[i]]);
if (!decryptedLogin || !decryptedLogin.equals(login))
continue;
// We've found a match, set id and break
foundLogin = decryptedLogin;
id = ids[i];
break;
}
return [id, foundLogin];
},
/*
* _isGuidUnique
*
* Checks to see if the specified GUID already exists.
*/
_isGuidUnique : function (guid) {
this._store.ensureDataReady();
return this._store.data.logins.every(l => l.guid != guid);
},
/*
* _encryptLogin
*
* Returns the encrypted username, password, and encrypton type for the specified
* login. Can throw if the user cancels a master password entry.
*/
_encryptLogin : function (login) {
let encUsername = this._crypto.encrypt(login.username);
let encPassword = this._crypto.encrypt(login.password);
let encType = this._crypto.defaultEncType;
return [encUsername, encPassword, encType];
},
/*
* _decryptLogins
*
* Decrypts username and password fields in the provided array of
* logins.
*
* The entries specified by the array will be decrypted, if possible.
* An array of successfully decrypted logins will be returned. The return
* value should be given to external callers (since still-encrypted
* entries are useless), whereas internal callers generally don't want
* to lose unencrypted entries (eg, because the user clicked Cancel
* instead of entering their master password)
*/
_decryptLogins : function (logins) {
let result = [];
for each (let login in logins) {
try {
login.username = this._crypto.decrypt(login.username);
login.password = this._crypto.decrypt(login.password);
} catch (e) {
// If decryption failed (corrupt entry?), just skip it.
// Rethrow other errors (like canceling entry of a master pw)
if (e.result == Cr.NS_ERROR_FAILURE)
continue;
throw e;
}
result.push(login);
}
return result;
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManagerStorage_json]);

View File

@ -13,9 +13,10 @@ const DB_VERSION = 5; // The database schema version
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
/**
* Object that manages a database transaction properly so consumers don't have
@ -243,7 +244,7 @@ LoginManagerStorage_mozStorage.prototype = {
let encUsername, encPassword;
// Throws if there are bogus values.
this._checkLoginValues(login);
LoginHelper.checkLoginValues(login);
[encUsername, encPassword, encType] = this._encryptLogin(login);
@ -355,108 +356,15 @@ LoginManagerStorage_mozStorage.prototype = {
let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
if (!idToModify)
throw "No matching logins";
oldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
let newLogin;
if (newLoginData instanceof Ci.nsILoginInfo) {
// Clone the existing login to get its nsILoginMetaInfo, then init it
// with the replacement nsILoginInfo data from the new login.
newLogin = oldStoredLogin.clone();
newLogin.init(newLoginData.hostname,
newLoginData.formSubmitURL, newLoginData.httpRealm,
newLoginData.username, newLoginData.password,
newLoginData.usernameField, newLoginData.passwordField);
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
// Automatically update metainfo when password is changed.
if (newLogin.password != oldLogin.password)
newLogin.timePasswordChanged = Date.now();
} else if (newLoginData instanceof Ci.nsIPropertyBag) {
function _bagHasProperty(aPropName) {
try {
newLoginData.getProperty(aPropName);
return true;
} catch (e) {
return false;
}
}
// Clone the existing login, along with all its properties.
newLogin = oldStoredLogin.clone();
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
// Automatically update metainfo when password is changed.
// (Done before the main property updates, lest the caller be
// explicitly updating both .password and .timePasswordChanged)
if (_bagHasProperty("password")) {
let newPassword = newLoginData.getProperty("password");
if (newPassword != oldLogin.password)
newLogin.timePasswordChanged = Date.now();
}
let propEnum = newLoginData.enumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
switch (prop.name) {
// nsILoginInfo properties...
case "hostname":
case "httpRealm":
case "formSubmitURL":
case "username":
case "password":
case "usernameField":
case "passwordField":
// nsILoginMetaInfo properties...
case "guid":
case "timeCreated":
case "timeLastUsed":
case "timePasswordChanged":
case "timesUsed":
newLogin[prop.name] = prop.value;
if (prop.name == "guid" && !this._isGuidUnique(newLogin.guid))
// Check if the new GUID is duplicate.
if (newLogin.guid != oldStoredLogin.guid &&
!this._isGuidUnique(newLogin.guid))
{
throw "specified GUID already exists";
break;
// Fake property, allows easy incrementing.
case "timesUsedIncrement":
newLogin.timesUsed += prop.value;
break;
// Fail if caller requests setting an unknown property.
default:
throw "Unexpected propertybag item: " + prop.name;
}
}
} else {
throw "newLoginData needs an expected interface!";
}
// Sanity check the login
if (newLogin.hostname == null || newLogin.hostname.length == 0)
throw "Can't add a login with a null or empty hostname.";
// For logins w/o a username, set to "", not null.
if (newLogin.username == null)
throw "Can't add a login with a null username.";
if (newLogin.password == null || newLogin.password.length == 0)
throw "Can't add a login with a null or empty password.";
if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
// We have a form submit URL. Can't have a HTTP realm.
if (newLogin.httpRealm != null)
throw "Can't add a login with both a httpRealm and formSubmitURL.";
} else if (newLogin.httpRealm) {
// We have a HTTP realm. Can't have a form submit URL.
if (newLogin.formSubmitURL != null)
throw "Can't add a login with both a httpRealm and formSubmitURL.";
} else {
// Need one or the other!
throw "Can't add a login without a httpRealm or formSubmitURL.";
}
// Throws if there are bogus values.
this._checkLoginValues(newLogin);
// Look for an existing entry in case key properties changed.
if (!newLogin.matches(oldLogin, true)) {
@ -665,7 +573,6 @@ LoginManagerStorage_mozStorage.prototype = {
*
*/
storeDeletedLogin : function(aLogin) {
#ifdef ANDROID
let stmt = null;
try {
this.log("Storing " + aLogin.guid + " in deleted passwords\n");
@ -680,7 +587,6 @@ LoginManagerStorage_mozStorage.prototype = {
if (stmt)
stmt.reset();
}
#endif
},
@ -747,7 +653,7 @@ LoginManagerStorage_mozStorage.prototype = {
*/
setLoginSavingEnabled : function (hostname, enabled) {
// Throws if there are bogus values.
this._checkHostnameValue(hostname);
LoginHelper.checkHostnameValue(hostname);
this.log("Setting login saving enabled for " + hostname + " to " + enabled);
let query;
@ -978,70 +884,6 @@ LoginManagerStorage_mozStorage.prototype = {
},
/*
* _checkLoginValues
*
* Due to the way the signons2.txt file is formatted, we need to make
* sure certain field values or characters do not cause the file to
* be parse incorrectly. Reject logins that we can't store correctly.
*/
_checkLoginValues : function (aLogin) {
function badCharacterPresent(l, c) {
return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
(l.httpRealm && l.httpRealm.indexOf(c) != -1) ||
l.hostname.indexOf(c) != -1 ||
l.usernameField.indexOf(c) != -1 ||
l.passwordField.indexOf(c) != -1);
}
// Nulls are invalid, as they don't round-trip well.
// Mostly not a formatting problem, although ".\0" can be quirky.
if (badCharacterPresent(aLogin, "\0"))
throw "login values can't contain nulls";
// In theory these nulls should just be rolled up into the encrypted
// values, but nsISecretDecoderRing doesn't use nsStrings, so the
// nulls cause truncation. Check for them here just to avoid
// unexpected round-trip surprises.
if (aLogin.username.indexOf("\0") != -1 ||
aLogin.password.indexOf("\0") != -1)
throw "login values can't contain nulls";
// Newlines are invalid for any field stored as plaintext.
if (badCharacterPresent(aLogin, "\r") ||
badCharacterPresent(aLogin, "\n"))
throw "login values can't contain newlines";
// A line with just a "." can have special meaning.
if (aLogin.usernameField == "." ||
aLogin.formSubmitURL == ".")
throw "login values can't be periods";
// A hostname with "\ \(" won't roundtrip.
// eg host="foo (", realm="bar" --> "foo ( (bar)"
// vs host="foo", realm=" (bar" --> "foo ( (bar)"
if (aLogin.hostname.indexOf(" (") != -1)
throw "bad parens in hostname";
},
/*
* _checkHostnameValue
*
* Legacy storage prohibited newlines and nulls in hostnames, so we'll keep
* that standard here. Throws on illegal format.
*/
_checkHostnameValue : function (hostname) {
// File format prohibits certain values. Also, nulls
// won't round-trip with getAllDisabledHosts().
if (hostname == "." ||
hostname.indexOf("\r") != -1 ||
hostname.indexOf("\n") != -1 ||
hostname.indexOf("\0") != -1)
throw "Invalid hostname";
},
/*
* _isGuidUnique
*

View File

@ -17,12 +17,14 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
"resource://gre/modules/Promise.jsm");
const LoginInfo =
Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
@ -43,6 +45,46 @@ function run_test()
// Some of these functions are already implemented in other parts of the source
// tree, see bug 946708 about sharing more code.
// While the previous test file should have deleted all the temporary files it
// used, on Windows these might still be pending deletion on the physical file
// system. Thus, start from a new base number every time, to make a collision
// with a file that is still pending deletion highly unlikely.
let gFileCounter = Math.floor(Math.random() * 1000000);
/**
* Returns a reference to a temporary file, that is guaranteed not to exist, and
* to have never been created before.
*
* @param aLeafName
* Suggested leaf name for the file to be created.
*
* @return nsIFile pointing to a non-existent file in a temporary directory.
*
* @note It is not enough to delete the file if it exists, or to delete the file
* after calling nsIFile.createUnique, because on Windows the delete
* operation in the file system may still be pending, preventing a new
* file with the same name to be created.
*/
function getTempFile(aLeafName)
{
// Prepend a serial number to the extension in the suggested leaf name.
let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
let leafName = base + "-" + gFileCounter + ext;
gFileCounter++;
// Get a file reference under the temporary directory for this test file.
let file = FileUtils.getFile("TmpD", [leafName]);
do_check_false(file.exists());
do_register_cleanup(function () {
if (file.exists()) {
file.remove(false);
}
});
return file;
}
/**
* Allows waiting for an observer notification once.
*

View File

@ -0,0 +1,248 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the LoginImport object.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
"resource://gre/modules/LoginImport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
"resource://gre/modules/LoginStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gLoginManagerCrypto",
"@mozilla.org/login-manager/crypto/SDR;1",
"nsILoginManagerCrypto");
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
/**
* Creates empty login data tables in the given SQLite connection, resembling
* the most recent schema version (excluding indices).
*/
function promiseCreateDatabaseSchema(aConnection)
{
return Task.spawn(function () {
yield aConnection.setSchemaVersion(5);
yield aConnection.execute("CREATE TABLE moz_logins (" +
"id INTEGER PRIMARY KEY," +
"hostname TEXT NOT NULL," +
"httpRealm TEXT," +
"formSubmitURL TEXT," +
"usernameField TEXT NOT NULL," +
"passwordField TEXT NOT NULL," +
"encryptedUsername TEXT NOT NULL," +
"encryptedPassword TEXT NOT NULL," +
"guid TEXT," +
"encType INTEGER," +
"timeCreated INTEGER," +
"timeLastUsed INTEGER," +
"timePasswordChanged INTEGER," +
"timesUsed INTEGER)");
yield aConnection.execute("CREATE TABLE moz_disabledHosts (" +
"id INTEGER PRIMARY KEY," +
"hostname TEXT UNIQUE)");
yield aConnection.execute("CREATE TABLE moz_deleted_logins (" +
"id INTEGER PRIMARY KEY," +
"guid TEXT," +
"timeDeleted INTEGER)");
});
}
/**
* Inserts a new entry in the database resembling the given nsILoginInfo object.
*/
function promiseInsertLoginInfo(aConnection, aLoginInfo)
{
aLoginInfo.QueryInterface(Ci.nsILoginMetaInfo);
// We can't use the aLoginInfo object directly in the execute statement
// because the bind code in Sqlite.jsm doesn't allow objects with extra
// properties beyond those being binded. So we might as well use an array as
// it is simpler.
let values = [
aLoginInfo.hostname,
aLoginInfo.httpRealm,
aLoginInfo.formSubmitURL,
aLoginInfo.usernameField,
aLoginInfo.passwordField,
gLoginManagerCrypto.encrypt(aLoginInfo.username),
gLoginManagerCrypto.encrypt(aLoginInfo.password),
aLoginInfo.guid,
aLoginInfo.encType,
aLoginInfo.timeCreated,
aLoginInfo.timeLastUsed,
aLoginInfo.timePasswordChanged,
aLoginInfo.timesUsed,
];
return aConnection.execute("INSERT INTO moz_logins (hostname, " +
"httpRealm, formSubmitURL, usernameField, " +
"passwordField, encryptedUsername, " +
"encryptedPassword, guid, encType, timeCreated, " +
"timeLastUsed, timePasswordChanged, timesUsed) " +
"VALUES (?" + ",?".repeat(12) + ")", values);
}
/**
* Inserts a new disabled host entry in the database.
*/
function promiseInsertDisabledHost(aConnection, aHostname)
{
return aConnection.execute("INSERT INTO moz_disabledHosts (hostname) " +
"VALUES (?)", [aHostname]);
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
/**
* Imports login data from a SQLite file constructed using the test data.
*/
add_task(function test_import()
{
let store = new LoginStore(getTempFile("test-import.json").path);
let loginsSqlite = getTempFile("test-logins.sqlite").path;
// Prepare the logins to be imported, including the nsILoginMetaInfo data.
let loginList = TestData.loginList();
for (let loginInfo of loginList) {
loginInfo.QueryInterface(Ci.nsILoginMetaInfo);
loginInfo.guid = gUUIDGenerator.generateUUID().toString();
loginInfo.timeCreated = Date.now();
loginInfo.timeLastUsed = Date.now();
loginInfo.timePasswordChanged = Date.now();
loginInfo.timesUsed = 1;
}
// Create and populate the SQLite database first.
let connection = yield Sqlite.openConnection({ path: loginsSqlite });
try {
yield promiseCreateDatabaseSchema(connection);
for (let loginInfo of loginList) {
yield promiseInsertLoginInfo(connection, loginInfo);
}
yield promiseInsertDisabledHost(connection, "http://www.example.com");
yield promiseInsertDisabledHost(connection, "https://www.example.org");
} finally {
yield connection.close();
}
// The "load" method must be called before importing data.
yield store.load();
yield new LoginImport(store, loginsSqlite).import();
// Verify that every login in the test data has a matching imported row.
do_check_eq(loginList.length, store.data.logins.length);
do_check_true(loginList.every(function (loginInfo) {
return store.data.logins.some(function (loginDataItem) {
let username = gLoginManagerCrypto.decrypt(loginDataItem.encryptedUsername);
let password = gLoginManagerCrypto.decrypt(loginDataItem.encryptedPassword);
return loginDataItem.hostname == loginInfo.hostname &&
loginDataItem.httpRealm == loginInfo.httpRealm &&
loginDataItem.formSubmitURL == loginInfo.formSubmitURL &&
loginDataItem.usernameField == loginInfo.usernameField &&
loginDataItem.passwordField == loginInfo.passwordField &&
username == loginInfo.username &&
password == loginInfo.password &&
loginDataItem.guid == loginInfo.guid &&
loginDataItem.encType == loginInfo.encType &&
loginDataItem.timeCreated == loginInfo.timeCreated &&
loginDataItem.timeLastUsed == loginInfo.timeLastUsed &&
loginDataItem.timePasswordChanged == loginInfo.timePasswordChanged &&
loginDataItem.timesUsed == loginInfo.timesUsed;
});
}));
// Verify that disabled hosts have been imported.
do_check_eq(store.data.disabledHosts.length, 2);
do_check_true(store.data.disabledHosts.some(
dataItem => dataItem.hostname == "http://www.example.com"));
do_check_true(store.data.disabledHosts.some(
dataItem => dataItem.hostname == "https://www.example.org"));
});
/**
* Tests imports of NULL values due to a downgraded database.
*/
add_task(function test_import_downgraded()
{
let store = new LoginStore(getTempFile("test-import-downgraded.json").path);
let loginsSqlite = getTempFile("test-logins-downgraded.sqlite").path;
let loginList = TestData.loginList();
// Create and populate the SQLite database first.
let connection = yield Sqlite.openConnection({ path: loginsSqlite });
try {
yield promiseCreateDatabaseSchema(connection);
yield connection.setSchemaVersion(3);
yield promiseInsertLoginInfo(connection, TestData.formLogin({
guid: gUUIDGenerator.generateUUID().toString(),
timeCreated: null,
timeLastUsed: null,
timePasswordChanged: null,
timesUsed: 0,
}));
} finally {
yield connection.close();
}
// The "load" method must be called before importing data.
yield store.load();
yield new LoginImport(store, loginsSqlite).import();
// Verify that the missing metadata was generated correctly.
let loginItem = store.data.logins[0];
let creationTime = loginItem.timeCreated;
LoginTest.assertTimeIsAboutNow(creationTime);
do_check_eq(loginItem.timeLastUsed, creationTime);
do_check_eq(loginItem.timePasswordChanged, creationTime);
do_check_eq(loginItem.timesUsed, 1);
});
/**
* Verifies that importing from a SQLite file with database version 2 fails.
*/
add_task(function test_import_v2()
{
let store = new LoginStore(getTempFile("test-import-v2.json").path);
let loginsSqlite = do_get_file("data/signons-v2.sqlite").path;
// The "load" method must be called before importing data.
yield store.load();
try {
yield new LoginImport(store, loginsSqlite).import();
do_throw("The operation should have failed.");
} catch (ex) { }
});
/**
* Imports login data from a SQLite file, with database version 3.
*/
add_task(function test_import_v3()
{
let store = new LoginStore(getTempFile("test-import-v3.json").path);
let loginsSqlite = do_get_file("data/signons-v3.sqlite").path;
// The "load" method must be called before importing data.
yield store.load();
yield new LoginImport(store, loginsSqlite).import();
// We only execute basic integrity checks.
do_check_eq(store.data.logins[0].usernameField, "u1");
do_check_eq(store.data.disabledHosts.length, 0);
});

View File

@ -0,0 +1,208 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the LoginStore object.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
"resource://gre/modules/LoginStore.jsm");
const TEST_STORE_FILE_NAME = "test-logins.json";
////////////////////////////////////////////////////////////////////////////////
//// Tests
/**
* Saves login data to a file, then reloads it.
*/
add_task(function test_save_reload()
{
let storeForSave = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
// The "load" method must be called before preparing the data to be saved.
yield storeForSave.load();
let rawLoginData = {
id: storeForSave.data.nextId++,
hostname: "http://www.example.com",
httpRealm: null,
formSubmitURL: "http://www.example.com/submit-url",
usernameField: "field_" + String.fromCharCode(533, 537, 7570, 345),
passwordField: "field_" + String.fromCharCode(421, 259, 349, 537),
encryptedUsername: "(test)",
encryptedPassword: "(test)",
guid: "(test)",
encType: Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
timeCreated: Date.now(),
timeLastUsed: Date.now(),
timePasswordChanged: Date.now(),
timesUsed: 1,
};
storeForSave.data.logins.push(rawLoginData);
storeForSave.data.disabledHosts.push("http://www.example.org");
yield storeForSave.save();
// Test the asynchronous initialization path.
let storeForLoad = new LoginStore(storeForSave.path);
yield storeForLoad.load();
do_check_eq(storeForLoad.data.logins.length, 1);
do_check_matches(storeForLoad.data.logins[0], rawLoginData);
do_check_eq(storeForLoad.data.disabledHosts.length, 1);
do_check_eq(storeForLoad.data.disabledHosts[0], "http://www.example.org");
// Test the synchronous initialization path.
storeForLoad = new LoginStore(storeForSave.path);
storeForLoad.ensureDataReady();
do_check_eq(storeForLoad.data.logins.length, 1);
do_check_matches(storeForLoad.data.logins[0], rawLoginData);
do_check_eq(storeForLoad.data.disabledHosts.length, 1);
do_check_eq(storeForLoad.data.disabledHosts[0], "http://www.example.org");
});
/**
* Checks that loading from a missing file results in empty arrays.
*/
add_task(function test_load_empty()
{
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
do_check_false(yield OS.File.exists(store.path));
yield store.load();
do_check_false(yield OS.File.exists(store.path));
do_check_eq(store.data.logins.length, 0);
do_check_eq(store.data.disabledHosts.length, 0);
});
/**
* Checks that saving empty data still overwrites any existing file.
*/
add_task(function test_save_empty()
{
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
yield store.load();
let createdFile = yield OS.File.open(store.path, { create: true });
yield createdFile.close();
yield store.save();
do_check_true(yield OS.File.exists(store.path));
});
/**
* Loads data from a string in a predefined format. The purpose of this test is
* to verify that the JSON format used in previous versions can be loaded.
*/
add_task(function test_load_string_predefined()
{
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
let string = "{\"logins\":[{" +
"\"id\":1," +
"\"hostname\":\"http://www.example.com\"," +
"\"httpRealm\":null," +
"\"formSubmitURL\":\"http://www.example.com/submit-url\"," +
"\"usernameField\":\"usernameField\"," +
"\"passwordField\":\"passwordField\"," +
"\"encryptedUsername\":\"(test)\"," +
"\"encryptedPassword\":\"(test)\"," +
"\"guid\":\"(test)\"," +
"\"encType\":1," +
"\"timeCreated\":1262304000000," +
"\"timeLastUsed\":1262390400000," +
"\"timePasswordChanged\":1262476800000," +
"\"timesUsed\":1}],\"disabledHosts\":[" +
"\"http://www.example.org\"]}";
yield OS.File.writeAtomic(store.path,
new TextEncoder().encode(string),
{ tmpPath: store.path + ".tmp" });
yield store.load();
do_check_eq(store.data.logins.length, 1);
do_check_matches(store.data.logins[0], {
id: 1,
hostname: "http://www.example.com",
httpRealm: null,
formSubmitURL: "http://www.example.com/submit-url",
usernameField: "usernameField",
passwordField: "passwordField",
encryptedUsername: "(test)",
encryptedPassword: "(test)",
guid: "(test)",
encType: Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
timeCreated: 1262304000000,
timeLastUsed: 1262390400000,
timePasswordChanged: 1262476800000,
timesUsed: 1,
});
do_check_eq(store.data.disabledHosts.length, 1);
do_check_eq(store.data.disabledHosts[0], "http://www.example.org");
});
/**
* Loads login data from a malformed JSON string.
*/
add_task(function test_load_string_malformed()
{
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
let string = "{\"logins\":[{\"hostname\":\"http://www.example.com\"," +
"\"id\":1,";
yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string),
{ tmpPath: store.path + ".tmp" });
yield store.load();
// A backup file should have been created.
do_check_true(yield OS.File.exists(store.path + ".corrupt"));
yield OS.File.remove(store.path + ".corrupt");
// The store should be ready to accept new data.
do_check_eq(store.data.logins.length, 0);
do_check_eq(store.data.disabledHosts.length, 0);
});
/**
* Loads login data from a malformed JSON string, using the synchronous
* initialization path.
*/
add_task(function test_load_string_malformed_sync()
{
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
let string = "{\"logins\":[{\"hostname\":\"http://www.example.com\"," +
"\"id\":1,";
yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string),
{ tmpPath: store.path + ".tmp" });
store.ensureDataReady();
// A backup file should have been created.
do_check_true(yield OS.File.exists(store.path + ".corrupt"));
yield OS.File.remove(store.path + ".corrupt");
// The store should be ready to accept new data.
do_check_eq(store.data.logins.length, 0);
do_check_eq(store.data.disabledHosts.length, 0);
});

View File

@ -3,6 +3,17 @@ head = head.js
tail =
support-files = data/**
# Test JSON file access and import from SQLite, not applicable to Android.
[test_module_LoginImport.js]
skip-if = os == "android"
[test_module_LoginStore.js]
skip-if = os == "android"
# Test SQLite database backup and migration, applicable to Android only.
[test_storage_mozStorage.js]
skip-if = os != "android"
# The following tests apply to any storage back-end.
[test_disabled_hosts.js]
[test_legacy_empty_formSubmitURL.js]
[test_legacy_validation.js]
@ -12,4 +23,3 @@ support-files = data/**
[test_logins_search.js]
[test_notifications.js]
[test_storage.js]
[test_storage_mozStorage.js]

View File

@ -142,7 +142,7 @@ nsUrlClassifierPrefixSet::GetPrefixes(uint32_t* aCount,
NS_ENSURE_ARG_POINTER(aPrefixes);
*aPrefixes = nullptr;
uint32_t itemCount = mIndexStarts.Length() + mDeltas.Length();
uint64_t itemCount = mIndexStarts.Length() + mDeltas.Length();
uint32_t* prefixArray = static_cast<uint32_t*>(nsMemory::Alloc(itemCount * sizeof(uint32_t)));
NS_ENSURE_TRUE(prefixArray, NS_ERROR_OUT_OF_MEMORY);
@ -154,7 +154,7 @@ nsUrlClassifierPrefixSet::GetPrefixes(uint32_t* aCount,
uint32_t start = mIndexStarts[i];
uint32_t end = (i == (prefixIdxLength - 1)) ? mDeltas.Length()
: mIndexStarts[i + 1];
if (end > mDeltas.Length()) {
if (end > mDeltas.Length() || (start > end)) {
return NS_ERROR_FILE_CORRUPTED;
}
@ -312,6 +312,9 @@ nsUrlClassifierPrefixSet::LoadFromFd(AutoFDClose& fileFd)
NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
read = PR_Read(fileFd, mIndexStarts.Elements(), toRead);
NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
if (indexSize != 0 && mIndexStarts[0] != 0) {
return NS_ERROR_FILE_CORRUPTED;
}
if (deltaSize > 0) {
toRead = deltaSize*sizeof(uint16_t);
read = PR_Read(fileFd, mDeltas.Elements(), toRead);

View File

@ -1,8 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Timer.jsm");
const Ci = Components.interfaces;
let tab;

View File

@ -16,12 +16,12 @@
- Text on the button to go back to the main style editor -->
<!ENTITY csscoverage.backButton "Back">
<!-- LOCALIZATION NOTE (csscoverage.unused, csscoverage.noMatch):
<!-- LOCALIZATION NOTE (csscoverage.unused, csscoverage.noMatches):
- This is the heading and body text for the CSS usage part of the report -->
<!ENTITY csscoverage.unused "Unused Rules">
<!ENTITY csscoverage.noMatch "No matches found for the following rules:">
<!ENTITY csscoverage.noMatches "No matches found for the following rules:">
<!-- LOCALIZATION NOTE (csscoverage.optimize):
<!-- LOCALIZATION NOTE (csscoverage.optimize.header):
- This is the heading for the CSS optimization part of the report -->
<!ENTITY csscoverage.optimize.header "Optimizable Pages">
@ -33,7 +33,7 @@
<!ENTITY csscoverage.optimize.body2 "tags to the bottom of the page and creating a new inline">
<!ENTITY csscoverage.optimize.body3 "element with the styles needed before the 'load' event to the top. Here are the style blocks you need:">
<!-- LOCALIZATION NOTE (csscoverage.noPreload):
<!-- LOCALIZATION NOTE (csscoverage.optimize.bodyX):
- This is what we say when we have no optimization suggestions -->
<!ENTITY csscoverage.optimize.bodyX "All rules are inlined.">

View File

@ -64,6 +64,7 @@
#include "GLContextCGL.h"
#include "GLUploadHelpers.h"
#include "ScopedGLHelpers.h"
#include "HeapCopyOfStackArray.h"
#include "mozilla/layers/GLManager.h"
#include "mozilla/layers/CompositorOGL.h"
#include "mozilla/layers/BasicCompositor.h"
@ -2790,7 +2791,11 @@ GLPresenter::GLPresenter(GLContext* aContext)
/* Then quad texcoords */
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
};
mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, sizeof(vertices), vertices, LOCAL_GL_STATIC_DRAW);
HeapCopyOfStackArray<GLfloat> verticesOnHeap(vertices);
mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER,
verticesOnHeap.ByteLength(),
verticesOnHeap.Data(),
LOCAL_GL_STATIC_DRAW);
mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
}
@ -2810,18 +2815,24 @@ GLPresenter::BindAndDrawQuad(ShaderProgramOGL *aProgram,
{
mGLContext->MakeCurrent();
aProgram->SetLayerRect(aLayerRect);
aProgram->SetTextureRect(aTextureRect);
gfx::Rect layerRects[4];
gfx::Rect textureRects[4];
layerRects[0] = aLayerRect;
textureRects[0] = aTextureRect;
aProgram->SetLayerRects(layerRects);
aProgram->SetTextureRects(textureRects);
GLuint vertAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::VertexCoordAttrib);
GLuint texCoordAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::TexCoordAttrib);
mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
mGLContext->fVertexAttribPointer(vertAttribIndex, 2,
mGLContext->fVertexAttribPointer(vertAttribIndex, 4,
LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
(GLvoid*)0);
mGLContext->fEnableVertexAttribArray(vertAttribIndex);
mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2,
mGLContext->fVertexAttribPointer(texCoordAttribIndex, 4,
LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
(GLvoid*) (sizeof(float)*4*2));
mGLContext->fEnableVertexAttribArray(texCoordAttribIndex);

View File

@ -2481,18 +2481,12 @@ GetDPI(NSWindow* aWindow)
[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
size_t heightPx = ::CGDisplayPixelsHigh(displayID);
CGFloat scaleFactor = [aWindow userSpaceScaleFactor];
if (scaleFactor < 0.01 || heightMM < 1 || heightPx < 1) {
if (heightMM < 1 || heightPx < 1) {
// Something extremely bogus is going on
return 96.0f;
}
// Currently we don't do our own scaling to take account
// of userSpaceScaleFactor, so every "pixel" we draw is actually
// userSpaceScaleFactor screen pixels. So divide the screen height
// by userSpaceScaleFactor to get the number of "device pixels"
// available.
float dpi = (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT);
float dpi = heightPx / (heightMM / MM_PER_INCH_FLOAT);
// Account for HiDPI mode where Cocoa's "points" do not correspond to real
// device pixels

View File

@ -129,6 +129,7 @@ protected:
void GetScrollbarDrawInfo (HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame,
const CGSize& aSize, bool aShouldGetButtonStates);
nsIFrame* GetParentScrollbarFrame(nsIFrame *aFrame);
bool IsParentScrollbarRolledOver(nsIFrame* aFrame);
private:
NSButtonCell* mHelpButtonCell;

View File

@ -2068,6 +2068,21 @@ nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
NS_OBJC_END_TRY_ABORT_BLOCK;
}
static bool
ScrollbarTrackAndThumbDrawSeparately()
{
return nsLookAndFeel::UseOverlayScrollbars() || nsCocoaFeatures::OnLionOrLater();
}
bool
nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame)
{
nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
return nsLookAndFeel::UseOverlayScrollbars()
? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
: GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
}
static bool
IsHiDPIContext(nsDeviceContext* aContext)
{
@ -2507,17 +2522,17 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
case NS_THEME_SCROLLBAR_SMALL:
case NS_THEME_SCROLLBAR:
if (!nsLookAndFeel::UseOverlayScrollbars()) {
if (!ScrollbarTrackAndThumbDrawSeparately()) {
DrawScrollbar(cgContext, macRect, aFrame);
}
break;
case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
if (nsLookAndFeel::UseOverlayScrollbars()) {
if (ScrollbarTrackAndThumbDrawSeparately()) {
BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
BOOL isRolledOver = CheckBooleanAttr(GetParentScrollbarFrame(aFrame),
nsGkAtoms::hover);
if (!nsCocoaFeatures::OnMountainLionOrLater() || !isRolledOver) {
BOOL isRolledOver = IsParentScrollbarRolledOver(aFrame);
if (isOverlay && (!nsCocoaFeatures::OnMountainLionOrLater() || !isRolledOver)) {
if (isHorizontal) {
macRect.origin.y += 4;
macRect.size.height -= 4;
@ -2532,9 +2547,9 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
(CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
@"kCUIWidgetOverlayScrollBar", @"widget",
(isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
@"regular", @"size",
(isRolledOver ? @"rollover" : @""), @"state",
(isRolledOver ? @"rollover" : @"normal"), @"state",
(isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
(isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
[NSNumber numberWithBool:YES], @"indiconly",
@ -2560,12 +2575,13 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
break;
case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
if (nsLookAndFeel::UseOverlayScrollbars() &&
CheckBooleanAttr(GetParentScrollbarFrame(aFrame), nsGkAtoms::hover)) {
if (ScrollbarTrackAndThumbDrawSeparately()) {
BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
if (!isOverlay || IsParentScrollbarRolledOver(aFrame)) {
BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
if (!nsCocoaFeatures::OnMountainLionOrLater()) {
// On OSX 10.7, scrollbars don't grow when hovered. The adjustments
// below were obtained by trial and error.
if (isOverlay && !nsCocoaFeatures::OnMountainLionOrLater()) {
// On OSX 10.7, scrollbars don't grow when hovered.
// The adjustments below were obtained by trial and error.
if (isHorizontal) {
macRect.origin.y += 2.0;
} else {
@ -2580,7 +2596,7 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
(CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
@"kCUIWidgetOverlayScrollBar", @"widget",
(isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
@"regular", @"size",
(isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
(isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
@ -2590,6 +2606,7 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
nil],
nil);
}
}
break;
case NS_THEME_TEXTFIELD_MULTILINE: {
@ -3232,6 +3249,18 @@ nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
aAttribute == nsGkAtoms::open ||
aAttribute == nsGkAtoms::hover)
*aShouldRepaint = true;
if ((aWidgetType == NS_THEME_SCROLLBAR ||
aWidgetType == NS_THEME_SCROLLBAR_SMALL) &&
!ScrollbarTrackAndThumbDrawSeparately() &&
(aAttribute == nsGkAtoms::curpos ||
aAttribute == nsGkAtoms::minpos ||
aAttribute == nsGkAtoms::maxpos ||
aAttribute == nsGkAtoms::pageincrement)) {
// 10.6-style scrollbars paint the thumb as part of the scrollbar,
// so we need to invalidate the scrollbar when the thumb moves.
*aShouldRepaint = true;
}
}
return NS_OK;

View File

@ -18,18 +18,27 @@ namespace widget {
uint32_t
ContentHelper::GetTouchActionFromFrame(nsIFrame* aFrame)
{
if (!aFrame || !aFrame->GetContent() || !aFrame->GetContent()->GetPrimaryFrame()) {
// If frame is invalid or null then return default value.
// If aFrame is null then return default value
if (!aFrame) {
return NS_STYLE_TOUCH_ACTION_AUTO;
}
if (!aFrame->IsFrameOfType(nsIFrame::eSVG) && !aFrame->IsFrameOfType(nsIFrame::eBlockFrame)) {
// Since touch-action property can be applied to only svg and block-level
// elements we ignore frames of other types.
// The touch-action CSS property applies to: all elements except:
// non-replaced inline elements, table rows, row groups, table columns, and column groups
bool isNonReplacedInlineElement = aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
if (isNonReplacedInlineElement) {
return NS_STYLE_TOUCH_ACTION_AUTO;
}
return (aFrame->GetContent()->GetPrimaryFrame()->StyleDisplay()->mTouchAction);
const nsStyleDisplay* disp = aFrame->StyleDisplay();
bool isTableElement = disp->IsInnerTableStyle() &&
disp->mDisplay != NS_STYLE_DISPLAY_TABLE_CELL &&
disp->mDisplay != NS_STYLE_DISPLAY_TABLE_CAPTION;
if (isTableElement) {
return NS_STYLE_TOUCH_ACTION_AUTO;
}
return disp->mTouchAction;
}
void