Merge backout of 59d848818fd8.

This commit is contained in:
Matthew Gregan 2009-02-26 22:06:11 +13:00
commit 3facbf1d48
34 changed files with 1068 additions and 147 deletions

View File

@ -1991,7 +1991,7 @@ nsAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
if (role == nsIAccessibleRole::ROLE_ENTRY ||
role == nsIAccessibleRole::ROLE_COMBOBOX) {
nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
NS_ENSURE_STATE(content);
nsAutoString autocomplete;

View File

@ -27,20 +27,13 @@
testStates(frameDoc, STATE_READONLY);
testStates(frameDocArticle, STATE_READONLY);
testStates(frameDocCheckbox, 0, 0, STATE_READONLY);
var works = true;
try {
testStates(frameDocTextbox, 0, 0, STATE_READONLY);
}
catch (e) {
works = false;
}
todo(works, "Checking states of a textbox frame doc should not throw (Bug 478810)");
testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE);
frameDoc.designMode = "on";
testStates(frameDoc, 0, EXT_STATE_EDITABLE);
testStates(frameDocArticle, STATE_READONLY);
testStates(frameDocCheckbox, 0, 0, STATE_READONLY);
testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE);
frameDocArticle.designMode = "on";
testStates(frameDocArticle, 0, EXT_STATE_EDITABLE);

View File

@ -63,6 +63,13 @@ public:
// nsIDOMHTMLVideoElement
NS_DECL_NSIDOMHTMLVIDEOELEMENT
virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult);
NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
// Returns the current video frame width and height.

View File

@ -113,3 +113,48 @@ nsIntSize nsHTMLVideoElement::GetVideoSize(nsIntSize aDefaultSize)
{
return mMediaSize.width == -1 && mMediaSize.height == -1 ? aDefaultSize : mMediaSize;
}
PRBool
nsHTMLVideoElement::ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
return aResult.ParseSpecialIntValue(aValue, PR_TRUE);
}
return nsHTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aResult);
}
static void
MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
nsRuleData* aData)
{
nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
}
NS_IMETHODIMP_(PRBool)
nsHTMLVideoElement::IsAttributeMapped(const nsIAtom* aAttribute) const
{
static const MappedAttributeEntry attributes[] = {
{ &nsGkAtoms::width },
{ &nsGkAtoms::height },
{ nsnull }
};
static const MappedAttributeEntry* const map[] = {
attributes,
sCommonAttributeMap
};
return FindAttributeDependence(aAttribute, map, NS_ARRAY_LENGTH(map));
}
nsMapRuleToAttributesFunc
nsHTMLVideoElement::GetAttributeMappingFunction() const
{
return &MapAttributesIntoRule;
}

View File

@ -1139,7 +1139,7 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
aAppUnitsPerDevUnit);
}
details->mYOffset = !aBaselineDeltas ? 0.0f
: FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
: - FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
}
}
if (detailedGlyphs.Length() == 0) {

View File

@ -1323,6 +1323,19 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, newDirty, aLists);
}
// Now display the scrollbars and scrollcorner. These parts are drawn
// in the border-background layer, on top of our own background and
// borders and underneath borders and backgrounds of later elements
// in the tree.
nsIFrame* kid = mOuter->GetFirstChild(nsnull);
while (kid) {
if (kid != mScrolledFrame) {
rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
NS_ENSURE_SUCCESS(rv, rv);
}
kid = kid->GetNextSibling();
}
// Overflow clipping can never clip frames outside our subtree, so there
// is no need to worry about whether we are a moving frame that might clip
// non-moving frames.
@ -1347,18 +1360,6 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
NS_ENSURE_SUCCESS(rv, rv);
// Now display the scrollbars and scrollcorner
nsIFrame* kid = mOuter->GetFirstChild(nsnull);
// Put each child's background directly onto the content list
nsDisplayListSet scrollbarSet(aLists, aLists.Content());
while (kid) {
if (kid != mScrolledFrame) {
rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, scrollbarSet,
nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
NS_ENSURE_SUCCESS(rv, rv);
}
kid = kid->GetNextSibling();
}
return NS_OK;
}

View File

@ -330,20 +330,6 @@ nsSize nsVideoFrame::GetIntrinsicSize(nsIRenderingContext *aRenderingContext)
nsresult rv;
size = element->GetVideoSize(size);
if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::width)) {
PRInt32 width = -1;
rv = element->GetWidth(&width);
if (NS_SUCCEEDED(rv)) {
size.width = width;
}
}
if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
PRInt32 height = -1;
rv = element->GetHeight(&height);
if (NS_SUCCEEDED(rv)) {
size.height = height;
}
}
}
return nsSize(nsPresContext::CSSPixelsToAppUnits(size.width),

View File

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html class="reftest-wait">
<body style="background:white;">
<video id="v" src="black140x100.ogv"
style="width:280px; height:200px;"
onloadeddata="document.documentElement.removeAttribute('class')"></video>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html class="reftest-wait">
<body style="background:white;">
<video id="v" src="black140x100.ogv" width="280"
onloadeddata="document.documentElement.removeAttribute('class')"></video>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html class="reftest-wait">
<body style="background:white;">
<video id="v" src="black140x100.ogv" height="200"
onloadeddata="document.documentElement.removeAttribute('class')"></video>
</body>
</html>

View File

@ -2,6 +2,8 @@
== aspect-ratio-1b.html aspect-ratio-1-ref.html
== aspect-ratio-2a.html aspect-ratio-2-ref.html
== aspect-ratio-2b.html aspect-ratio-2-ref.html
== aspect-ratio-3a.html aspect-ratio-3-ref.html
== aspect-ratio-3b.html aspect-ratio-3-ref.html
== basic-1.html basic-1-ref.html
== canvas-1a.html basic-1-ref.html
== canvas-1b.html basic-1-ref.html

View File

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html>
<body>
<div style="overflow:scroll; width:300px; height:100px; background-color:lime;"></div>
<div style="background-color:yellow; width:300px; height:100px; margin-top:-50px; position:relative; z-index:1;"></div>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html>
<body>
<div style="overflow:scroll; width:300px; height:100px; background-color:lime;"></div>
<div style="background-color:yellow; width:300px; height:100px; margin-top:-50px"></div>
</body>
</html>

View File

@ -1 +1,2 @@
== 480053-1.html 480053-1-ref.html
== z-index-1.html z-index-1-ref.html

View File

@ -46,6 +46,7 @@ XPIDL_MODULE = loginmgr
XPIDLSRCS = \
nsILoginInfo.idl \
nsILoginMetaInfo.idl \
nsILoginManager.idl \
nsILoginManagerStorage.idl \
nsILoginManagerPrompter.idl \

View File

@ -37,7 +37,7 @@
#include "nsISupports.idl"
[scriptable, uuid(9c87a9bd-bf8b-4fae-bdb8-70513b2877df)]
[scriptable, uuid(c41b7dff-6b9b-42fe-b78d-113051facb05)]
/**
* An object containing information for a login stored by the
@ -134,6 +134,15 @@ interface nsILoginInfo : nsISupports {
* If true, ignore the password when checking for match.
*/
boolean matches(in nsILoginInfo aLoginInfo, in boolean ignorePassword);
/**
* Create an identical copy of the login, duplicating all of the login's
* nsILoginInfo and nsILoginMetaInfo properties.
*
* This allows code to be forwards-compatible, when additional properties
* are added to nsILoginMetaInfo (or nsILoginInfo) in the future.
*/
nsILoginInfo clone();
};
%{C++

View File

@ -43,7 +43,7 @@ interface nsIAutoCompleteResult;
interface nsIDOMHTMLInputElement;
interface nsIDOMHTMLFormElement;
[scriptable, uuid(04dbfa30-4238-11dd-ae16-0800200c9a66)]
[scriptable, uuid(9c78bfc1-422b-4f4f-ba09-f7eb3c4e72b2)]
interface nsILoginManager : nsISupports {
@ -52,6 +52,10 @@ interface nsILoginManager : nsISupports {
*
* @param aLogin
* The login to be added.
*
* Default values for the login's nsILoginMetaInfo properties will be
* created. However, if the caller specifies non-default values, they will
* be used instead.
*/
void addLogin(in nsILoginInfo aLogin);
@ -61,6 +65,9 @@ interface nsILoginManager : nsISupports {
*
* @param aLogin
* The login to be removed.
*
* The specified login must exactly match a stored login. However, the
* values of any nsILoginMetaInfo properties are ignored.
*/
void removeLogin(in nsILoginInfo aLogin);
@ -68,10 +75,20 @@ interface nsILoginManager : nsISupports {
/**
* Modify an existing login in the login manager.
*
* @param aLogin
* @param oldLogin
* The login to be modified.
* @param newLoginData
* The new login values (either a nsILoginInfo or nsIProperyBag)
*
* If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo
* properties are changed to the values from newLoginData (but the old
* login's nsILoginMetaInfo properties are unmodified).
*
* If newLoginData is a nsIPropertyBag, only the specified properties
* will be changed. The nsILoginMetaInfo properties of oldLogin can be
* changed in this manner.
*/
void modifyLogin(in nsILoginInfo oldLogin, in nsILoginInfo newLogin);
void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
/**

View File

@ -40,7 +40,7 @@
interface nsIFile;
interface nsILoginInfo;
[scriptable, uuid(dec624d1-18ea-40ea-8ca5-69002153c8b8)]
[scriptable, uuid(199ebbff-4656-4a18-8da9-9401c64619f9)]
/*
* NOTE: This interface is intended to be implemented by modules
@ -71,30 +71,47 @@ interface nsILoginManagerStorage : nsISupports {
/**
* Store a new login.
* Store a new login in the storage module.
*
* @param aLogin
* The login to be added.
*
* Default values for the login's nsILoginMetaInfo properties will be
* created. However, if the caller specifies non-default values, they will
* be used instead.
*/
void addLogin(in nsILoginInfo aLogin);
/**
* Remove a login from the login manager.
* Remove a login from the storage module.
*
* @param aLogin
* The login to be removed.
*
* The specified login must exactly match a stored login. However, the
* values of any nsILoginMetaInfo properties are ignored.
*/
void removeLogin(in nsILoginInfo aLogin);
/**
* Modify an existing login in the login manager.
* Modify an existing login in the storage module.
*
* @param aLogin
* @param oldLogin
* The login to be modified.
* @param newLoginData
* The new login values (either a nsILoginInfo or nsIProperyBag)
*
* If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo
* properties are changed to the values from newLoginData (but the old
* login's nsILoginMetaInfo properties are unmodified).
*
* If newLoginData is a nsIPropertyBag, only the specified properties
* will be changed. The nsILoginMetaInfo properties of oldLogin can be
* changed in this manner.
*/
void modifyLogin(in nsILoginInfo oldLogin, in nsILoginInfo newLogin);
void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
/**

View File

@ -0,0 +1,62 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Dolske <dolske@mozilla.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
[scriptable, uuid(867407d5-10e0-43a0-bc81-a324740534ca)]
/**
* An object containing metainfo for a login stored by the login manager.
*
* Code using login manager can generally ignore this interface. When adding
* logins, default value will be created. When modifying logins, these
* properties will be unchanged unless a change is explicitly requested [by
* using modifyLogin() with a nsIPropertyBag]. When deleting a login or
* comparing logins, these properties are ignored.
*/
interface nsILoginMetaInfo : nsISupports {
/**
* The GUID to uniquely identify the login. This can be any arbitrary
* string, but a format as created by nsIUUIDGenerator is recommended.
* For example, "{d4e1a1f6-5ea0-40ee-bff5-da57982f21cf}"
*
* addLogin will generate a random value unless a value is provided.
*
* addLogin and modifyLogin will throw if the GUID already exists.
*/
attribute AString guid;
};

View File

@ -47,7 +47,7 @@ nsLoginInfo.prototype = {
classDescription : "LoginInfo",
contractID : "@mozilla.org/login-manager/loginInfo;1",
classID : Components.ID("{0f2f347c-1e4f-40cc-8efd-792dea70a85e}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo, Ci.nsILoginMetaInfo]),
// Allow storage-Legacy.js to get at the JS object so it can
// slap on a few extra properties for internal use.
@ -55,6 +55,10 @@ nsLoginInfo.prototype = {
return this;
},
//
// nsILoginInfo interfaces...
//
hostname : null,
formSubmitURL : null,
httpRealm : null,
@ -105,7 +109,27 @@ nsLoginInfo.prototype = {
return false;
return true;
}
},
clone : function() {
let clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
clone.init(this.hostname, this.formSubmitURL, this.httpRealm,
this.username, this.password,
this.usernameField, this.passwordField);
// Copy nsILoginMetaInfo props
clone.QueryInterface(Ci.nsILoginMetaInfo);
clone.guid = this.guid;
return clone;
},
//
// nsILoginMetaInfo interfaces...
//
guid : null
}; // end of nsLoginInfo implementation

View File

@ -311,6 +311,9 @@ LoginManagerStorage_legacy.prototype = {
*
*/
modifyLogin : function (oldLogin, newLogin) {
if (newLogin instanceof Ci.nsIPropertyBag)
throw "legacy modifyLogin with propertybag not implemented.";
newLogin.QueryInterface(Ci.nsILoginInfo);
// Throws if there are bogus values.
this._checkLoginValues(newLogin);

View File

@ -109,6 +109,15 @@ LoginManagerStorage_mozStorage.prototype = {
return this.__uuidService;
},
__observerService : null,
get _observerService() {
if (!this.__observerService)
this.__observerService = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
return this.__observerService;
},
// The current database schema
_dbSchema: {
tables: {
@ -271,7 +280,17 @@ LoginManagerStorage_mozStorage.prototype = {
throw "User canceled master password entry, login not added.";
}
let guid = this._uuidService.generateUUID().toString();
// 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 = this._uuidService.generateUUID().toString();
}
let query =
"INSERT INTO moz_logins " +
@ -283,14 +302,14 @@ LoginManagerStorage_mozStorage.prototype = {
":guid)";
let params = {
hostname: login.hostname,
httpRealm: login.httpRealm,
formSubmitURL: login.formSubmitURL,
usernameField: login.usernameField,
passwordField: login.passwordField,
hostname: loginClone.hostname,
httpRealm: loginClone.httpRealm,
formSubmitURL: loginClone.formSubmitURL,
usernameField: loginClone.usernameField,
passwordField: loginClone.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: guid
guid: loginClone.guid
};
let stmt;
@ -303,6 +322,10 @@ LoginManagerStorage_mozStorage.prototype = {
} finally {
stmt.reset();
}
// Send a notification that a login was added.
if (!isEncrypted)
this._sendNotification("addLogin", loginClone);
},
@ -311,7 +334,7 @@ LoginManagerStorage_mozStorage.prototype = {
*
*/
removeLogin : function (login) {
let idToDelete = this._getIdForLogin(login);
let [idToDelete, storedLogin] = this._getIdForLogin(login);
if (!idToDelete)
throw "No matching logins";
@ -328,6 +351,8 @@ LoginManagerStorage_mozStorage.prototype = {
} finally {
stmt.reset();
}
this._sendNotification("removeLogin", storedLogin);
},
@ -335,13 +360,60 @@ LoginManagerStorage_mozStorage.prototype = {
* modifyLogin
*
*/
modifyLogin : function (oldLogin, newLogin) {
// Throws if there are bogus values.
this._checkLoginValues(newLogin);
let idToModify = this._getIdForLogin(oldLogin);
modifyLogin : function (oldLogin, newLoginData) {
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);
} else if (newLoginData instanceof Ci.nsIPropertyBag) {
// Clone the existing login, along with all its properties.
newLogin = oldStoredLogin.clone();
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
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":
newLogin[prop.name] = prop.value;
break;
// nsILoginMetaInfo properties...
case "guid":
newLogin.guid = prop.value;
if (!this._isGuidUnique(newLogin.guid))
throw "specified GUID already exists";
break;
// Fail if caller requests setting an unknown property.
default:
throw "Unexpected propertybag item: " + prop.name;
}
}
} else {
throw "newLoginData needs an expected interface!";
}
// Throws if there are bogus values.
this._checkLoginValues(newLogin);
// Get the encrypted value of the username and password.
let [encUsername, encPassword, userCanceled] = this._encryptLogin(newLogin);
@ -356,10 +428,12 @@ LoginManagerStorage_mozStorage.prototype = {
"usernameField = :usernameField, " +
"passwordField = :passwordField, " +
"encryptedUsername = :encryptedUsername, " +
"encryptedPassword = :encryptedPassword " +
"encryptedPassword = :encryptedPassword, " +
"guid = :guid " +
"WHERE id = :id";
let params = {
id: idToModify,
hostname: newLogin.hostname,
httpRealm: newLogin.httpRealm,
formSubmitURL: newLogin.formSubmitURL,
@ -367,8 +441,7 @@ LoginManagerStorage_mozStorage.prototype = {
passwordField: newLogin.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
id: idToModify
// guid not changed
guid: newLogin.guid
};
let stmt;
@ -381,6 +454,8 @@ LoginManagerStorage_mozStorage.prototype = {
} finally {
stmt.reset();
}
this._sendNotification("modifyLogin", [oldStoredLogin, newLogin]);
},
@ -427,6 +502,8 @@ LoginManagerStorage_mozStorage.prototype = {
} finally {
stmt.reset();
}
this._sendNotification("removeAllLogins", null);
},
@ -458,16 +535,6 @@ LoginManagerStorage_mozStorage.prototype = {
*
*/
setLoginSavingEnabled : function (hostname, enabled) {
this._setLoginSavingEnabled(hostname, enabled);
},
/*
* _setLoginSavingEnabled
*
* Private function wrapping core setLoginSavingEnabled functionality.
*/
_setLoginSavingEnabled : function (hostname, enabled) {
// Throws if there are bogus values.
this._checkHostnameValue(hostname);
@ -486,11 +553,13 @@ LoginManagerStorage_mozStorage.prototype = {
stmt = this._dbCreateStatement(query, params);
stmt.execute();
} catch (e) {
this.log("_setLoginSavingEnabled failed: " + e.name + " : " + e.message);
this.log("setLoginSavingEnabled failed: " + e.name + " : " + e.message);
throw "Couldn't write to database"
} finally {
stmt.reset();
}
this._sendNotification(enabled ? "hostSavingEnabled" : "hostSavingDisabled", hostname);
},
@ -549,16 +618,40 @@ LoginManagerStorage_mozStorage.prototype = {
},
/*
* _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;
}
this._observerService.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
},
/*
* _getIdForLogin
*
* Returns the |id| for the specified login, or null if the login was not
* found.
* 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 [logins, ids] =
this._queryLogins(login.hostname, login.formSubmitURL, login.httpRealm);
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
@ -575,11 +668,12 @@ LoginManagerStorage_mozStorage.prototype = {
continue;
// We've found a match, set id and break
foundLogin = decryptedLogin;
id = ids[i];
break;
}
return id;
return [id, foundLogin];
},
@ -614,6 +708,9 @@ LoginManagerStorage_mozStorage.prototype = {
stmt.row.httpRealm, stmt.row.encryptedUsername,
stmt.row.encryptedPassword, stmt.row.usernameField,
stmt.row.passwordField);
// set nsILoginMetaInfo values
login.QueryInterface(Ci.nsILoginMetaInfo);
login.guid = stmt.row.guid;
logins.push(login);
ids.push(stmt.row.id);
}
@ -758,6 +855,30 @@ LoginManagerStorage_mozStorage.prototype = {
},
/*
* _isGuidUnique
*
* Checks to see if the specified GUID already exists.
*/
_isGuidUnique : function (guid) {
let query = "SELECT COUNT(1) AS numLogins FROM moz_logins WHERE guid = :guid";
let params = { guid: guid };
let stmt, numLogins;
try {
stmt = this._dbCreateStatement(query, params);
stmt.step();
numLogins = stmt.row.numLogins;
} catch (e) {
this.log("_isGuidUnique failed: " + e.name + " : " + e.message);
} finally {
stmt.reset();
}
return (numLogins == 0);
},
/*
* _importLegacySignons
*
@ -784,7 +905,7 @@ LoginManagerStorage_mozStorage.prototype = {
this._addLogin(login, true);
let disabledHosts = legacy.getAllDisabledHosts({});
for each (let hostname in disabledHosts)
this._setLoginSavingEnabled(hostname, false);
this.setLoginSavingEnabled(hostname, false);
} catch (e) {
this.log("_importLegacySignons failed: " + e.name + " : " + e.message);
throw "Import failed";

View File

@ -0,0 +1,262 @@
/*
* Test suite for storage-mozStorage.js
*
* This test interfaces directly with the mozStorage password storage module,
* bypassing the normal password manager usage.
*
*/
const STORAGE_TYPE = "mozStorage";
function run_test() {
try {
var testnum = 0;
var testdesc = "Setup of nsLoginInfo test-users";
var nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Components.interfaces.nsILoginInfo);
do_check_true(nsLoginInfo != null);
var testuser1 = new nsLoginInfo;
testuser1.QueryInterface(Ci.nsILoginMetaInfo);
testuser1.init("http://testhost1", "", null,
"dummydude", "itsasecret", "put_user_here", "put_pw_here");
var guid1;
var testuser2 = new nsLoginInfo;
testuser2.init("http://testhost2", "", null,
"dummydude2", "itsasecret2", "put_user2_here", "put_pw2_here");
testuser2.QueryInterface(Ci.nsILoginMetaInfo);
var guid2 = "{12345678-abcd-1234-abcd-987654321000}";
testuser2.guid = guid2;
var testuser3 = new nsLoginInfo;
testuser3.QueryInterface(Ci.nsILoginMetaInfo);
testuser3.init("http://testhost3", "", null,
"dummydude3", "itsasecret3", "put_user3_here", "put_pw3_here");
var guid3 = "{99999999-abcd-9999-abcd-999999999999}";
// This login is different than testuser2, except it has the same guid.
var testuser2dupeguid = new nsLoginInfo;
testuser2dupeguid.QueryInterface(Ci.nsILoginMetaInfo);
testuser2dupeguid.init("http://dupe-testhost2", "", null,
"dupe-dummydude2", "dupe-itsasecret2", "put_user2_here", "put_pw2_here");
testuser2dupeguid.QueryInterface(Ci.nsILoginMetaInfo);
testuser2dupeguid.guid = guid2;
var isGUID = /^\{[0-9a-f\d]{8}-[0-9a-f\d]{4}-[0-9a-f\d]{4}-[0-9a-f\d]{4}-[0-9a-f\d]{12}\}$/;
/* ========== 1 ========== */
var testnum = 1;
var testdesc = "Initial connection to storage module"
LoginTest.deleteFile(OUTDIR, "signons-unittest6.sqlite");
var storage;
storage = LoginTest.initStorage(INDIR, "signons-empty.txt", OUTDIR, "signons-unittest6.sqlite");
var logins = storage.getAllLogins({});
do_check_eq(logins.length, 0, "Checking for no initial logins");
var disabledHosts = storage.getAllDisabledHosts({});
do_check_eq(disabledHosts.length, 0, "Checking for no initial disabled hosts");
/* ========== 2 ========== */
testnum++;
testdesc = "add user1 w/o guid";
storage.addLogin(testuser1);
LoginTest.checkStorageData(storage, [], [testuser1]);
// Check guid
do_check_eq(testuser1.guid, null, "caller's login shouldn't be modified");
logins = storage.findLogins({}, "http://testhost1", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_true(isGUID.test(logins[0].guid), "testuser1 guid is set");
guid1 = logins[0].guid;
/* ========== 3 ========== */
testnum++;
testdesc = "add user2 WITH guid";
storage.addLogin(testuser2);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2]);
// Check guid
do_check_eq(testuser2.guid, guid2, "caller's login shouldn't be modified");
logins = storage.findLogins({}, "http://testhost2", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_true(isGUID.test(logins[0].guid), "testuser2 guid is set");
do_check_eq(logins[0].guid, guid2, "checking guid2");
/* ========== 4 ========== */
testnum++;
testdesc = "add user3 w/o guid";
storage.addLogin(testuser3);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
logins = storage.findLogins({}, "http://testhost3", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_true(isGUID.test(logins[0].guid), "testuser3 guid is set");
do_check_neq(logins[0].guid, guid3, "testuser3 guid is different");
/* ========== 5 ========== */
testnum++;
testdesc = "(don't) modify user1";
// When newlogin.guid is blank, the GUID shouldn't be changed.
testuser1.guid = "";
storage.modifyLogin(testuser1, testuser1);
// Check it
do_check_eq(testuser1.guid, "", "caller's login shouldn't be modified");
logins = storage.findLogins({}, "http://testhost1", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_eq(logins[0].guid, guid1, "checking guid1");
/* ========== 6 ========== */
testnum++;
testdesc = "modify user3";
// change the GUID to our known value
var propbag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
propbag.setProperty("guid", guid3);
storage.modifyLogin(testuser3, propbag);
// Check it
logins = storage.findLogins({}, "http://testhost3", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_eq(logins[0].guid, guid3, "checking guid3");
/* ========== 7 ========== */
testnum++;
testdesc = "try adding a duplicate guid";
var ex = null;
try {
storage.addLogin(testuser2dupeguid);
} catch (e) {
ex = e;
}
do_check_true(/specified GUID already exists/.test(ex), "ensuring exception thrown when adding duplicate GUID");
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
/* ========== 8 ========== */
testnum++;
testdesc = "try modifing to a duplicate guid";
propbag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
propbag.setProperty("guid", testuser2dupeguid.guid);
ex = null;
try {
storage.modifyLogin(testuser1, propbag);
} catch (e) {
ex = e;
}
do_check_true(/specified GUID already exists/.test(ex), "ensuring exception thrown when modifying to duplicate GUID");
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
/* ========== 9 ========== */
testnum++;
testdesc = "check propertybag nulls/empty strings";
// Set formSubmitURL to a null, and usernameField to a empty-string.
do_check_eq(testuser3.formSubmitURL, "");
do_check_eq(testuser3.httpRealm, null);
do_check_eq(testuser3.usernameField, "put_user3_here");
propbag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
propbag.setProperty("formSubmitURL", null);
propbag.setProperty("httpRealm", "newRealm");
propbag.setProperty("usernameField", "");
storage.modifyLogin(testuser3, propbag);
// Fixup testuser3 to match the new values.
testuser3.formSubmitURL = null;
testuser3.httpRealm = "newRealm";
testuser3.usernameField = "";
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
/* ========== 10 ========== */
testnum++;
testdesc = "[reinit storage, look for expected guids]";
storage = LoginTest.reloadStorage(OUTDIR, "signons-unittest6.sqlite");
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
logins = storage.findLogins({}, "http://testhost1", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_eq(logins[0].guid, guid1, "checking guid1");
logins = storage.findLogins({}, "http://testhost2", "", null);
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_eq(logins[0].guid, guid2, "checking guid2");
logins = storage.findLogins({}, "http://testhost3", null, "newRealm");
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_eq(logins[0].guid, guid3, "checking guid3");
/* ========== 11 ========== */
testnum++;
testdesc = "login w/o nsILoginMetaInfo impl";
var wonkyDelegate = new nsLoginInfo;
wonkyDelegate.init("http://wonky", null, "wonkyness",
"wonkyuser", "wonkypass", "u", "p");
var wonkyLogin = {
QueryInterface : function (iid) {
var interfaces = [Ci.nsILoginInfo, Ci.nsISupports];
if (!interfaces.some( function(v) { return iid.equals(v) }))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
hostname: wonkyDelegate.hostname,
formSubmitURL: wonkyDelegate.formSubmitURL,
httpRealm: wonkyDelegate.httpRealm,
username: wonkyDelegate.username,
password: wonkyDelegate.password,
usernameField: wonkyDelegate.usernameField,
passwordField: wonkyDelegate.passwordField,
equals: wonkyDelegate.equals,
matches: wonkyDelegate.matches,
clone: wonkyDelegate.clone
};
storage.addLogin(wonkyLogin);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, wonkyLogin]);
logins = storage.findLogins({}, "http://wonky", null, "");
do_check_eq(logins.length, 1, "expecting 1 login");
logins[0].QueryInterface(Ci.nsILoginMetaInfo);
do_check_true(isGUID.test(logins[0].guid), "wonky guid is set");
storage.modifyLogin(wonkyLogin, wonkyLogin);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, wonkyLogin]);
storage.removeLogin(wonkyLogin);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
LoginTest.deleteFile(OUTDIR, "signons-unittest6.sqlite");
} catch (e) {
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
}
};

View File

@ -0,0 +1,164 @@
/*
* Test suite for storage-mozStorage.js
*
* Tests notifications dispatched when modifying stored logins.
*
*/
const STORAGE_TYPE = "mozStorage";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
var expectedNotification;
var TestObserver = {
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
observe : function (subject, topic, data) {
do_check_eq(expectedNotification, data);
expectedNotification = null; // ensure a duplicate is flagged as unexpected.
switch (data) {
case "addLogin":
case "modifyLogin":
case "removeLogin":
case "removeAllLogins":
case "hostSavingEnabled":
case "hostSavingDisabled":
break;
default:
do_throw("Unexpected observer topic: " + topic);
}
}
};
function run_test() {
try {
var testnum = 0;
var testdesc = "Setup of nsLoginInfo test-users";
var nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Components.interfaces.nsILoginInfo);
do_check_true(nsLoginInfo != null);
var testuser1 = new nsLoginInfo;
testuser1.QueryInterface(Ci.nsILoginMetaInfo);
testuser1.init("http://testhost1", "", null,
"dummydude", "itsasecret", "put_user_here", "put_pw_here");
var testuser2 = new nsLoginInfo;
testuser2.init("http://testhost2", "", null,
"dummydude2", "itsasecret2", "put_user2_here", "put_pw2_here");
// Add the observer
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(TestObserver, "passwordmgr-storage-changed", false);
/* ========== 1 ========== */
var testnum = 1;
var testdesc = "Initial connection to storage module"
LoginTest.deleteFile(OUTDIR, "signons-unittest-notify.sqlite");
var storage;
storage = LoginTest.initStorage(INDIR, "signons-empty.txt", OUTDIR, "signons-unittest-notify.sqlite");
var logins = storage.getAllLogins({});
do_check_eq(logins.length, 0);
var disabledHosts = storage.getAllDisabledHosts({});
do_check_eq(disabledHosts.length, 0, "Checking for no initial disabled hosts");
/* ========== 2 ========== */
testnum++;
testdesc = "addLogin";
expectedNotification = "addLogin";
storage.addLogin(testuser1);
LoginTest.checkStorageData(storage, [], [testuser1]);
do_check_eq(expectedNotification, null); // check that observer got a notification
/* ========== 3 ========== */
testnum++;
testdesc = "modifyLogin";
expectedNotification = "modifyLogin";
storage.modifyLogin(testuser1, testuser2);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], [testuser2]);
/* ========== 4 ========== */
testnum++;
testdesc = "removeLogin";
expectedNotification = "removeLogin";
storage.removeLogin(testuser2);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], []);
/* ========== 5 ========== */
testnum++;
testdesc = "removeAllLogins";
expectedNotification = "removeAllLogins";
storage.removeAllLogins();
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], []);
/* ========== 6 ========== */
testnum++;
testdesc = "removeAllLogins (again)";
expectedNotification = "removeAllLogins";
storage.removeAllLogins();
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], []);
/* ========== 7 ========== */
testnum++;
testdesc = "setLoginSavingEnabled / false";
expectedNotification = "hostSavingDisabled";
storage.setLoginSavingEnabled("http://site.com", false);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, ["http://site.com"], []);
/* ========== 8 ========== */
testnum++;
testdesc = "setLoginSavingEnabled / false (again)";
expectedNotification = "hostSavingDisabled";
storage.setLoginSavingEnabled("http://site.com", false);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, ["http://site.com"], []);
/* ========== 9 ========== */
testnum++;
testdesc = "setLoginSavingEnabled / true";
expectedNotification = "hostSavingEnabled";
storage.setLoginSavingEnabled("http://site.com", true);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], []);
/* ========== 10 ========== */
testnum++;
testdesc = "setLoginSavingEnabled / true (again)";
expectedNotification = "hostSavingEnabled";
storage.setLoginSavingEnabled("http://site.com", true);
do_check_eq(expectedNotification, null);
LoginTest.checkStorageData(storage, [], []);
LoginTest.deleteFile(OUTDIR, "signons-unittest-notify.sqlite");
} catch (e) {
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
}
};

View File

@ -9,3 +9,8 @@
.scrubber {
-moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
}
.throbberOverlay {
visibility: hidden;
opacity: 0.0;
}

View File

@ -26,7 +26,7 @@
if (!userChanged && !this._userChanged)
return;
this.setAttribute("value", newValue);
this.parentNode.parentNode.parentNode.Utils.seekToPosition();
document.getBindingParent(this.parentNode).Utils.seekToPosition();
break;
case "minpos":
@ -51,17 +51,25 @@
</resources>
<xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<spacer flex="1"/>
<hbox class="controlBar">
<button class="playButton" oncommand="this.parentNode.parentNode.Utils.togglePause();"/>
<stack class="scrubberStack" flex="1">
<box class="backgroundBar" flex="1"/>
<progressmeter class="bufferBar" flex="1"/>
<progressmeter class="progressBar" flex="1" max="10000"/>
<scale class="scrubber" flex="1"/>
</stack>
<button class="muteButton" oncommand="this.parentNode.parentNode.Utils.toggleMute();"/>
</hbox>
<stack flex="1">
<vbox class="throbberOverlay">
<box class="throbber" flex="1"/>
</vbox>
<vbox>
<spacer flex="1"/>
<hbox class="controlBar">
<button class="playButton" oncommand="document.getBindingParent(this).Utils.togglePause();"/>
<stack class="scrubberStack" flex="1">
<box class="backgroundBar"/>
<progressmeter class="bufferBar"/>
<progressmeter class="progressBar" max="10000"/>
<scale class="scrubber"/>
</stack>
<button class="muteButton" oncommand="document.getBindingParent(this).Utils.toggleMute();"/>
</hbox>
</vbox>
</stack>
</xbl:content>
<implementation implements="nsISecurityCheckedComponent">
@ -114,12 +122,13 @@
]]>
</constructor>
<field name="randomID">0</field>
<field name="Utils">
<![CDATA[ ({
debug : false,
video : null,
videocontrols : null,
controlBar : null,
playButton : null,
muteButton : null,
@ -128,18 +137,80 @@
bufferBar : null,
thumbWidth : 0,
FADE_TIME_MAX : 200, // ms
FADE_TIME_STEP : 30, // ms
randomID : 0,
videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
"loadstart", "durationchange", "timeupdate", "progress",
"playing", "waiting", "canplaythrough", "seeking",
"seeked", "emptied"],
fadeTime : 0, // duration of active fade animation
fadingIn: false, // are we fading in, or fading out?
fadeTimer : null,
controlsVisible : false,
controlFader : {
name : "controls", // shorthand for debugging
element : null, // the element to fade in/out
runtime : 0, // duration of active animation
fadingIn : false, // are we fading in, or fading out?
isVisible : false, // is it at all visible?
timer : null, // handle from setInterval()
delayTimer : null, // handle from setTimeout()
START_DELAY : 0, // ms, delay before fading in
RUNTIME_MAX : 200, // ms
RUNTIME_STEP : 30 // ms
},
throbberFader : {
name : "throbber",
element : null,
runtime : 0,
fadingIn : false,
isVisible : false,
timer : null,
delayTimer : null,
START_DELAY : 750,
RUNTIME_MAX : 300,
RUNTIME_STEP : 20
},
firstFrameShown : false,
lastTimeUpdate : 0,
maxCurrentTimeSeen : 0,
/*
* Set the initial state of the controls. The binding is normally created along
* with video element, but could be attached at any point (eg, if the video is
* removed from the document and then reinserted). Thus, some one-time events may
* have already fired, and so we'll need to explicitly check the initial state.
*/
setupInitialState : function() {
this.randomID = Math.random();
this.videocontrols.randomID = this.randomID;
this.playButton.setAttribute("paused", this.video.paused);
this.muteButton.setAttribute("muted", this.video.muted);
var duration = Math.round(this.video.duration * 1000); // in ms
var currentTime = Math.round(this.video.currentTime * 1000); // in ms
this.log("Initial playback position is at " + currentTime + " of " + duration);
// It would be nice to retain maxCurrentTimeSeen, but it would be difficult
// to determine if the media source changed while we were detached.
this.maxCurrentTimeSeen = currentTime;
this.durationChange(duration);
this.showPosition(currentTime, duration);
// If the first frame hasn't loaded, kick off a throbber fade-in.
if (this.video.readyState >= this.video.HAVE_CURRENT_DATA)
this.firstFrameShown = true;
else
this.startFadeIn(this.throbberFader);
// We can't determine the exact buffering status, but do know if it's
// fully loaded. (If it's still loading, it will fire a progress event
// and we'll figure out the exact state then.)
this.bufferBar.setAttribute("max", 100);
if (this.video.networkState == this.video.NETWORK_LOADED)
this.bufferBar.setAttribute("value", 100);
else
this.bufferBar.setAttribute("value", 0);
},
get dynamicControls() {
// Don't fade controls for <audio> elements.
var enabled = this.video instanceof HTMLVideoElement;
@ -152,7 +223,13 @@
},
handleEvent : function (aEvent) {
this.log("Got " + aEvent.type + " media event");
this.log("Got media event ----> " + aEvent.type);
// If the binding is detached (or has been replaced by a
// newer instance of the binding), nuke our event-listeners.
if (this.videocontrols.randomID != this.randomID)
this.terminateEventListeners();
switch (aEvent.type) {
case "play":
this.playButton.setAttribute("paused", false);
@ -200,11 +277,40 @@
case "emptied":
this.bufferBar.value = 0;
break;
case "seeking":
case "waiting":
this.startFadeIn(this.throbberFader);
break;
case "seeked":
// Normally we'd expect canplaythough to fire, but if we already
// have the data cached it shouldn't fire again.
if (this.video.readyState == this.video.HAVE_ENOUGH_DATA)
this.startFadeOut(this.throbberFader);
break;
case "playing":
case "canplaythrough":
this.startFadeOut(this.throbberFader);
break;
default:
this.log("!!! event " + aEvent.type + " not handled!");
}
},
terminateEventListeners : function () {
for each (var event in this.videoEvents)
this.video.removeEventListener(event, this, false);
if (this.controlFader.timer)
clearInterval(this.controlFader.timer);
if (this.controlFader.delayTimer)
clearInterval(this.controlFader.delayTimer);
if (this.throbberFader.timer)
clearInterval(this.throbberFader.timer);
if (this.throbberFader.delayTimer)
clearTimeout(this.throbberFader.delayTimer);
this.log("--- videocontrols terminated ---");
},
durationChange : function (duration) {
if (isNaN(duration))
duration = this.maxCurrentTimeSeen;
@ -276,64 +382,102 @@
!(this.video.autoplay && this.video.mozAutoplayEnabled))
return;
this.startFade(this.controlFader, isMouseOver);
},
startFadeIn : function (fader) {
this.startFade(fader, true);
},
startFadeOut : function (fader) {
this.startFade(fader, false);
},
startFade : function (fader, fadeIn, immediate) {
// If the fader specifies a start delay, don't immediately fade in...
// Unless there's already a fade underway, in which case we want to be
// able to immediately reverse it (eg, a seeking event right after seeked).
if (fadeIn && fader.START_DELAY && !immediate && !fader.timer) {
function delayedFadeStart(self, fader) {
self.log("Delated start timer fired.");
fader.delayTimer = null;
self.startFade(fader, true, true);
}
// If there's already a timer running, let it handle things.
if (fader.delayTimer)
return;
this.log("Delaying " + fader.name + " fade-in...");
fader.delayTimer = setTimeout(delayedFadeStart, fader.START_DELAY, this, fader);
return;
}
// Cancel any delay timer (eg, if we start fading-out before it fires)
if (fader.delayTimer) {
this.log("Canceling " + fader.name + " fade-in delay...");
clearTimeout(fader.delayTimer);
fader.delayTimer = null;
}
// If we're already fading towards the desired state (or are
// already there), then we don't need to do anything more.
var directionChange = (this.fadingIn != isMouseOver);
var directionChange = (fader.fadingIn != fadeIn);
if (!directionChange)
return;
this.fadingIn = isMouseOver;
this.log("Fading controls " + (this.fadingIn ? "in" : "out"));
fader.fadingIn = fadeIn;
this.log("Fading " + fader.name + (fader.fadingIn ? " in" : " out"));
// When switching direction mid-fade, we want the reversed fade
// to complete in the same amount of time as the current fade has
// been running. So we invert fadeTime.
// been running. So we invert the runtime.
//
// For example, if we're 20ms into a 100ms fade-in, then we want to
// fade-out over 20ms. This is done by setting fadeTime to 80ms
// (100-20), so that fadeControls will only animate for 20ms more.
if (this.fadeTime)
this.fadeTime = this.FADE_TIME_MAX - this.fadeTime;
// fade-out over 20ms. This is done by setting the .runtime to 80ms
// (100-20), so that doFade will only animate for 20ms more.
if (fader.runtime)
fader.runtime = fader.RUNTIME_MAX - fader.runtime;
if (!this.fadeTimer)
this.fadeTimer = setInterval(this.fadeControls, this.FADE_TIME_STEP, this);
// If we're fading in, immediately make the controls clickable.
// Otherwise they might not activate until the first fadeTimer
// fires, which is hard to test reliably.
if (this.fadingIn)
this.controlBar.style.visibility = "visible";
if (!fader.timer) {
fader.timer = setInterval(this.doFade, fader.RUNTIME_STEP, this, fader);
// Perform the first fade step now, notably to make a fade-in
// immediately activate the controls.
this.doFade(this, fader, -(fader.RUNTIME_STEP - 1));
}
},
fadeControls : function (self, lateness) {
doFade : function (self, fader, lateness) {
// Update elapsed time, and compute position as a percent
// of total. Last frame could run over, so clamp to 1.
self.fadeTime += self.FADE_TIME_STEP + lateness;
var pos = self.fadeTime / self.FADE_TIME_MAX;
fader.runtime += fader.RUNTIME_STEP + lateness;
var pos = fader.runtime / fader.RUNTIME_MAX;
if (pos > 1)
pos = 1;
// Calculate the opacity for our position in the animation.
var opacity;
if (self.fadingIn)
if (fader.fadingIn)
opacity = Math.pow(pos, 0.5);
else
opacity = Math.pow(1 - pos, 0.5);
self.controlsVisible = (opacity ? true : false);
fader.isVisible = (opacity ? true : false);
//self.log("Fading " + fader.name + " to opacity " + opacity);
self.controlBar.style.opacity = opacity;
fader.element.style.opacity = opacity;
// Use .visibility to ignore mouse clicks when hidden.
if (self.controlsVisible)
self.controlBar.style.visibility = "visible";
if (fader.isVisible)
fader.element.style.visibility = "visible";
else
self.controlBar.style.visibility = "hidden";
fader.element.style.visibility = "hidden";
// Is the animation done?
if (pos == 1) {
clearInterval(self.fadeTimer);
self.fadeTimer = null;
self.fadeTime = 0;
clearInterval(fader.timer);
fader.timer = null;
fader.runtime = 0;
}
},
@ -379,7 +523,9 @@
this.Utils.video = video;
this.Utils.videocontrols = this;
this.Utils.controlBar = document.getAnonymousElementByAttribute(this, "class", "controlBar");
this.Utils.controlFader.element = document.getAnonymousElementByAttribute(this, "class", "controlBar");
this.Utils.throbberFader.element = document.getAnonymousElementByAttribute(this, "class", "throbberOverlay");
this.Utils.playButton = document.getAnonymousElementByAttribute(this, "class", "playButton");
this.Utils.muteButton = document.getAnonymousElementByAttribute(this, "class", "muteButton");
this.Utils.progressBar = document.getAnonymousElementByAttribute(this, "class", "progressBar");
@ -391,8 +537,7 @@
if (thumb)
this.Utils.thumbWidth = thumb.clientWidth;
// Set initial state of play/pause button.
this.Utils.playButton.setAttribute("paused", video.paused);
this.Utils.setupInitialState();
// videocontrols.css hides the control bar by default, because if script
// is disabled our binding's script is disabled too (bug 449358). Thus,
@ -405,23 +550,16 @@
//
// (Note: the |controls| attribute is already handled via layout/style/html.css)
if (!(video.autoplay && video.mozAutoplayEnabled) || !this.Utils.dynamicControls) {
this.Utils.controlBar.style.visibility = "visible";
this.Utils.controlBar.style.opacity = 1.0;
this.Utils.controlsVisible = true;
this.Utils.fadingIn = true;
var fader = this.Utils.controlFader;
fader.element.style.visibility = "visible";
fader.element.style.opacity = 1.0;
fader.isVisible = true;
fader.fadingIn = true;
}
// Use Utils.handleEvent() callback for all media events.
video.addEventListener("play", this.Utils, false);
video.addEventListener("pause", this.Utils, false);
video.addEventListener("ended", this.Utils, false);
video.addEventListener("volumechange", this.Utils, false);
video.addEventListener("loadeddata", this.Utils, false);
video.addEventListener("loadstart", this.Utils, false);
video.addEventListener("durationchange", this.Utils, false);
video.addEventListener("timeupdate", this.Utils, false);
video.addEventListener("progress", this.Utils, false);
video.addEventListener("emptied", this.Utils, false);
// Use the Utils.handleEvent() callback for all media events.
for each (var event in this.Utils.videoEvents)
video.addEventListener(event, this.Utils, false);
this.Utils.log("--- videocontrols initialized ---");
]]>

View File

@ -179,6 +179,7 @@ classic.jar:
+ skin/classic/global/media/muteButton.png (media/muteButton.png)
+ skin/classic/global/media/unmuteButton.png (media/unmuteButton.png)
+ skin/classic/global/media/scrubberThumb.png (media/scrubberThumb.png)
+ skin/classic/global/media/throbber.png (media/throbber.png)
+ skin/classic/global/menu/menu-arrow-dis.gif (menu/menu-arrow-dis.gif)
+ skin/classic/global/menu/menu-arrow-hov.gif (menu/menu-arrow-hov.gif)
+ skin/classic/global/menu/menu-arrow.gif (menu/menu-arrow.gif)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -79,3 +79,14 @@
min-width: 11px;
min-height: 20px;
}
.throbberOverlay {
background-color: rgba(0,0,0,0.55);
}
.throbber {
background: url(chrome://global/skin/media/throbber.png) no-repeat center;
height: 36px;
width: 36px;
margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */
}

View File

@ -152,6 +152,7 @@ classic.jar:
skin/classic/global/media/muteButton.png (media/muteButton.png)
skin/classic/global/media/unmuteButton.png (media/unmuteButton.png)
skin/classic/global/media/scrubberThumb.png (media/scrubberThumb.png)
skin/classic/global/media/throbber.png (media/throbber.png)
skin/classic/global/radio/radio-check.gif (radio/radio-check.gif)
skin/classic/global/radio/radio-check-dis.gif (radio/radio-check-dis.gif)
skin/classic/global/scrollbar/slider.gif (scrollbar/slider.gif)
@ -324,6 +325,7 @@ classic.jar:
skin/classic/aero/global/media/muteButton.png (media/muteButton.png)
skin/classic/aero/global/media/unmuteButton.png (media/unmuteButton.png)
skin/classic/aero/global/media/scrubberThumb.png (media/scrubberThumb.png)
skin/classic/aero/global/media/throbber.png (media/throbber.png)
skin/classic/aero/global/radio/radio-check.gif (radio/radio-check.gif)
skin/classic/aero/global/radio/radio-check-dis.gif (radio/radio-check-dis.gif)
skin/classic/aero/global/scrollbar/slider.gif (scrollbar/slider.gif)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -86,3 +86,14 @@
min-width: 11px;
min-height: 20px;
}
.throbberOverlay {
background-color: rgba(0,0,0,0.55);
}
.throbber {
background: url(chrome://global/skin/media/throbber.png) no-repeat center;
height: 36px;
width: 36px;
margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */
}

View File

@ -149,6 +149,8 @@ ah_crap_handler(int signum)
sleep(_gdb_sleep_duration);
printf("Done sleeping...\n");
_exit(signum);
}
#endif // CRAWL_STACK_ON_SIGSEGV