Final merge of bug 596451. a=blocking2.0 - Asynchronous layer-based painting on Windows. Various pieces r=josh/romaxa/karlt/jmathies/roc.

This merge includes the fix for bug 583109 - Add visibility notifications for plugins, so that asynchronous painting can be suspended when plugins are not visible. This collaterally fixes bug 606285, hulu homepage doesn't paint with async layers.

This changeset only implements asynchronous painting for out-of-process plugins. In-process painting will be implemented as a followup. Note that it is still possible to disable asynchronous layers on Windows by setting the preference mozilla.plugins.use_layers to false, but my intention is to remove this preference and sync-painting codepaths soon in a followup.
This commit is contained in:
Benjamin Smedberg 2010-11-10 10:29:09 -05:00
commit abe5e4d05f
322 changed files with 13024 additions and 8461 deletions

View File

@ -2730,6 +2730,9 @@ nsAccessible::InvalidateChildren()
PRBool
nsAccessible::AppendChild(nsAccessible* aChild)
{
if (!aChild)
return PR_FALSE;
if (!mChildren.AppendElement(aChild))
return PR_FALSE;
@ -2743,11 +2746,16 @@ nsAccessible::AppendChild(nsAccessible* aChild)
PRBool
nsAccessible::InsertChildAt(PRUint32 aIndex, nsAccessible* aChild)
{
if (!aChild)
return PR_FALSE;
if (!mChildren.InsertElementAt(aIndex, aChild))
return PR_FALSE;
for (PRUint32 idx = aIndex + 1; idx < mChildren.Length(); idx++)
mChildren[idx]->mIndexInParent++;
for (PRUint32 idx = aIndex + 1; idx < mChildren.Length(); idx++) {
NS_ASSERTION(mChildren[idx]->mIndexInParent == idx - 1, "Accessible child index doesn't match");
mChildren[idx]->mIndexInParent = idx;
}
if (nsAccUtils::IsText(aChild))
mChildrenFlags = eMixedChildren;
@ -2761,23 +2769,28 @@ nsAccessible::InsertChildAt(PRUint32 aIndex, nsAccessible* aChild)
PRBool
nsAccessible::RemoveChild(nsAccessible* aChild)
{
if (aChild->mParent != this || aChild->mIndexInParent == -1)
if (!aChild)
return PR_FALSE;
if (aChild->mIndexInParent >= mChildren.Length() ||
mChildren[aChild->mIndexInParent] != aChild) {
PRInt32 index = aChild->mIndexInParent;
if (aChild->mParent != this || index == -1)
return PR_FALSE;
if (index >= mChildren.Length() || mChildren[index] != aChild) {
NS_ERROR("Child is bound to parent but parent hasn't this child at its index!");
aChild->UnbindFromParent();
return PR_FALSE;
}
for (PRUint32 idx = aChild->mIndexInParent + 1; idx < mChildren.Length(); idx++)
mChildren[idx]->mIndexInParent--;
mChildren.RemoveElementAt(aChild->mIndexInParent);
mEmbeddedObjCollector = nsnull;
for (PRUint32 idx = index + 1; idx < mChildren.Length(); idx++) {
NS_ASSERTION(mChildren[idx]->mIndexInParent == idx, "Accessible child index doesn't match");
mChildren[idx]->mIndexInParent = idx - 1;
}
aChild->UnbindFromParent();
mChildren.RemoveElementAt(index);
mEmbeddedObjCollector = nsnull;
return PR_TRUE;
}

View File

@ -679,8 +679,10 @@ nsDocAccessible::Shutdown()
mParent->RemoveChild(this);
}
PRUint32 childDocCount = mChildDocuments.Length();
for (PRUint32 idx = 0; idx < childDocCount; idx++)
// Walk the array backwards because child documents remove themselves from the
// array as they are shutdown.
PRInt32 childDocCount = mChildDocuments.Length();
for (PRInt32 idx = childDocCount - 1; idx >= 0; idx--)
mChildDocuments[idx]->Shutdown();
mChildDocuments.Clear();

View File

@ -196,8 +196,13 @@ nsOuterDocAccessible::InvalidateChildren()
PRBool
nsOuterDocAccessible::AppendChild(nsAccessible *aAccessible)
{
NS_ASSERTION(!mChildren.Length(),
"Previous child document of outerdoc accessible wasn't removed!");
// We keep showing the old document for a bit after creating the new one,
// and while building the new DOM and frame tree. That's done on purpose
// to avoid weird flashes of default background color.
// The old viewer will be destroyed after the new one is created.
// For a11y, it should be safe to shut down the old document now.
if (mChildren.Length())
mChildren[0]->Shutdown();
if (!nsAccessible::AppendChild(aAccessible))
return PR_FALSE;

View File

@ -51,7 +51,7 @@ tabbrowser {
-moz-transition: opacity .25s;
}
.tabbrowser-tab[pinned] {
.tabbrowser-tabs:not([pinnedonly]) > .tabbrowser-tab[pinned] {
position: fixed;
display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
}

View File

@ -832,31 +832,48 @@
// Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
if (!this._previewMode) {
// We've selected the new tab, so go ahead and notify listeners.
var event = document.createEvent("Events");
let event = document.createEvent("Events");
event.initEvent("TabSelect", true, false);
this.mCurrentTab.dispatchEvent(event);
this._tabAttrModified(oldTab);
this._tabAttrModified(this.mCurrentTab);
// Change focus to the new browser unless the findbar is focused.
if (!gFindBarInitialized ||
gFindBar.hidden ||
gFindBar.getElement("findbar-textbox").getAttribute("focused") != "true") {
// Adjust focus
do {
// Focus the location bar if it was previously focused for that tab.
// In full screen mode, only bother making the location bar visible
// if the tab is a blank one.
oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
if (newBrowser._urlbarFocused && gURLBar) {
if (!window.fullScreen) {
gURLBar.focus();
break;
} else if (isTabEmpty(this.mCurrentTab)) {
focusAndSelectUrlBar();
break;
}
}
var fm = Components.classes["@mozilla.org/focus-manager;1"].
getService(Components.interfaces.nsIFocusManager);
var newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
// If the find bar is focused, keep it focused.
if (gFindBarInitialized &&
!gFindBar.hidden &&
gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
break;
// Otherwise, focus the content area.
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
// for anchors, use FLAG_SHOWRING so that it is clear what link was
// last clicked when switching back to that tab
var focusFlags = fm.FLAG_NOSCROLL;
let focusFlags = fm.FLAG_NOSCROLL;
if (newFocusedElement &&
(newFocusedElement instanceof HTMLAnchorElement ||
newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
focusFlags |= fm.FLAG_SHOWRING;
fm.setFocus(newBrowser, focusFlags);
}
} while (false);
}
]]>
</body>
@ -1902,15 +1919,11 @@
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.visibleTabs.length == 1)
if (this.tabs.length == 1)
return null;
// tell a new window to take the "dropped" tab
return Services.ww.openWindow(window,
getBrowserURL(),
null,
"chrome,dialog=no,all",
aTab);
return window.openDialog(getBrowserURL(), "_blank", "dialog=no,all", aTab);
]]>
</body>
</method>
@ -2708,11 +2721,18 @@
<method name="_positionPinnedTabs">
<body><![CDATA[
var width = 0;
var scrollButtonWidth = this.getAttribute("overflow") != "true" ? 0 :
var pinnedOnly = (this.tabbrowser._numPinnedTabs == this.tabbrowser.visibleTabs.length);
if (pinnedOnly)
this.tabbrowser.tabContainer.setAttribute("pinnedonly", "true");
else
this.tabbrowser.tabContainer.removeAttribute("pinnedonly");
var scrollButtonWidth = (this.getAttribute("overflow") != "true" || pinnedOnly) ? 0 :
this.mTabstrip._scrollButtonDown.scrollWidth;
for (var i = this.tabbrowser._numPinnedTabs - 1; i >= 0; i--) {
let tab = this.childNodes[i];
width += tab.scrollWidth;
width += pinnedOnly ? 0 : tab.scrollWidth;
tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
}
this.style.MozMarginStart = width + "px";

View File

@ -1020,19 +1020,22 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// ----------
// Function: shouldStack
// Returns true if the groupItem, given "count", should stack (instead of grid).
// Returns true if the groupItem should stack (instead of grid).
shouldStack: function GroupItem_shouldStack(count) {
if (count <= 1)
return false;
var bb = this.getContentBounds();
var options = {
pretend: true,
count: count
return: 'widthAndColumns',
count: count || this._children.length
};
let {childWidth, columns} = Items.arrange(null, bb, options);
var rects = Items.arrange(null, bb, options);
return (rects[0].width < 55);
let shouldStack = childWidth < TabItems.minTabWidth * 1.35;
this._columns = shouldStack ? null : columns;
return shouldStack;
},
// ----------
@ -1049,35 +1052,30 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
Items.arrange(this._children, box, Utils.extend({}, options, {z: 99999}));
} else {
var bb = this.getContentBounds();
var count = this._children.length;
if (!this.shouldStack(count)) {
if (!this.shouldStack()) {
if (!options)
options = {};
var animate;
if (typeof options.animate == 'undefined')
animate = true;
else
animate = options.animate;
this._children.forEach(function(child) {
child.removeClass("stacked")
});
this.topChild = null;
var arrangeOptions = Utils.copy(options);
Utils.extend(arrangeOptions, {
pretend: true,
count: count
});
if (!count) {
if (!this._children.length) {
this.xDensity = 0;
this.yDensity = 0;
return;
}
var arrangeOptions = Utils.copy(options);
Utils.extend(arrangeOptions, {
columns: this._columns
});
// Items.arrange will rearrange the children, but also return an array
// of the Rect's used.
var rects = Items.arrange(this._children, bb, arrangeOptions);
// yDensity = (the distance of the bottom of the last tab to the top of the content area)
@ -1098,15 +1096,6 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
}
this.xDensity = (rightMostRight - bb.left) / (bb.width);
this._children.forEach(function(child, index) {
if (!child.locked.bounds) {
child.setBounds(rects[index], !animate);
child.setRotation(0);
if (options.z)
child.setZ(options.z);
}
});
this._isStacked = false;
} else
this._stackArrange(bb, options);

View File

@ -601,7 +601,7 @@ Item.prototype = {
// ___ mousemove
var handleMouseMove = function(e) {
// positioning
var mouse = new Point(e.pageX, e.pageY);
var mouse = new Point(e.pageX, e.pageY);
if (!startSent) {
if(Math.abs(mouse.x - startMouse.x) > self.dragOptions.minDragDistance ||
Math.abs(mouse.y - startMouse.y) > self.dragOptions.minDragDistance) {
@ -892,39 +892,41 @@ let Items = {
// maximizing item size but maintaining standard tab aspect ratio for each
//
// Parameters:
// items - an array of <Item>s. Can be null if the pretend and count options are set.
// items - an array of <Item>s. Can be null, in which case we won't
// actually move anything.
// bounds - a <Rect> defining the space to arrange within
// options - an object with various properites (see below)
//
// Possible "options" properties:
// animate - whether to animate; default: true.
// z - the z index to set all the items; default: don't change z.
// pretend - whether to collect and return the rectangle rather than moving the items; default: false
// count - overrides the item count for layout purposes; default: the actual item count
// return - if set to 'widthAndColumns', it'll return an object with the
// width of children and the columns.
// count - overrides the item count for layout purposes;
// default: the actual item count
// padding - pixels between each item
// columns - (int) a preset number of columns to use
//
// Returns:
// the list of rectangles if the pretend option is set; otherwise null
// an object with the width value of the child items and the number of columns,
// if the return option is set to 'widthAndColumns'; otherwise the list of <Rect>s
arrange: function Items_arrange(items, bounds, options) {
var animate;
if (!options || typeof options.animate == 'undefined')
animate = true;
else
animate = options.animate;
if (typeof options == 'undefined')
options = {};
var rects = null;
if (options.pretend)
rects = [];
var animate = true;
if (typeof options.animate != 'undefined')
animate = options.animate;
var immediately = !animate;
var rects = [];
var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
var count = options.count || (items ? items.length : 0);
if (!count)
return rects;
var columns = 1;
var columns = options.columns || 1;
// We'll assume for the time being that all the items have the same styling
// and that the margin is the same width around.
var itemMargin = items && items.length ?
@ -954,20 +956,17 @@ let Items = {
tabWidth = Math.min(tabWidth, (bounds.height - 2 * itemMargin) / tabAspect);
tabHeight = tabWidth * tabAspect;
}
if (options.return == 'widthAndColumns')
return {childWidth: tabWidth, columns: columns};
var box = new Rect(bounds.left, bounds.top, tabWidth, tabHeight);
var row = 0;
var column = 0;
var immediately;
var a;
for (a = 0; a < count; a++) {
immediately = !animate;
if (rects)
rects.push(new Rect(box));
else if (items && a < items.length) {
var item = items[a];
for (let a = 0; a < count; a++) {
rects.push(new Rect(box));
if (items && a < items.length) {
let item = items[a];
if (!item.locked.bounds) {
item.setBounds(box, immediately);
item.setRotation(0);
@ -982,7 +981,6 @@ let Items = {
box.left = bounds.left;
box.top += (box.height * yScale) + padding;
column = 0;
row++;
}
}

View File

@ -145,6 +145,7 @@ _BROWSER_FILES = \
browser_bug561636.js \
browser_bug562649.js \
browser_bug563588.js \
browser_bug565575.js \
browser_bug575561.js \
browser_bug577121.js \
browser_bug579872.js \
@ -159,6 +160,7 @@ _BROWSER_FILES = \
browser_bug595507.js \
browser_bug596687.js \
browser_bug597218.js \
browser_bug609700.js \
browser_contextSearchTabPosition.js \
browser_ctrlTab.js \
browser_discovery.js \

View File

@ -0,0 +1,13 @@
function test() {
gBrowser.selectedBrowser.focus();
BrowserOpenTab();
ok(gURLBar.focused, "location bar is focused for a new tab");
gBrowser.selectedTab = gBrowser.tabs[0];
ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
gBrowser.selectedTab = gBrowser.tabs[1];
ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
gBrowser.removeCurrentTab();
}

View File

@ -0,0 +1,20 @@
function test() {
waitForExplicitFinish();
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
Services.ww.unregisterNotification(arguments.callee);
ok(true, "duplicateTabIn opened a new window");
aSubject.addEventListener("load", function () {
executeSoon(function () {
aSubject.close();
finish();
});
}, false);
}
});
duplicateTabIn(gBrowser.selectedTab, "window");
}

View File

@ -44,11 +44,8 @@ function test() {
browser2.contentWindow, null, true,
"focusedElement after tab change, focus in new tab");
// switching tabs when the urlbar is focused and nothing in the new tab is focused
// switching tabs when nothing in the new tab is focused
// should focus the browser
expectFocusShift(function () gURLBar.focus(),
window, gURLBar.inputField, true,
"url field focused");
expectFocusShift(function () gBrowser.selectedTab = tab1,
browser1.contentWindow, null, true,
"focusedElement after tab change, focus in new tab");
@ -89,6 +86,9 @@ function test() {
window, gURLBar.inputField, true,
"focusedWindow after url field focused");
is(fm.getFocusedElementForWindow(browser2.contentWindow, false, {}), button2, "url field focused, button in tab");
expectFocusShift(function () gURLBar.blur(),
window, null, true,
"focusedWindow after browser focused");
// when a chrome element is focused, switching tabs to a tab with a button
// with the current focus should focus the button

View File

@ -37,6 +37,8 @@
const kIMig = Components.interfaces.nsIBrowserProfileMigrator;
const kIPStartup = Components.interfaces.nsIProfileStartup;
const kProfileMigratorContractIDPrefix = "@mozilla.org/profile/migrator;1?app=browser&type=";
const Cc = Components.classes;
const Ci = Components.interfaces;
var MigrationWizard = {
_source: "", // Source Profile Migrator ContractID suffix
@ -54,6 +56,7 @@ var MigrationWizard = {
os.addObserver(this, "Migration:Started", false);
os.addObserver(this, "Migration:ItemBeforeMigrate", false);
os.addObserver(this, "Migration:ItemAfterMigrate", false);
os.addObserver(this, "Migration:ItemError", false);
os.addObserver(this, "Migration:Ended", false);
this._wiz = document.documentElement;
@ -81,6 +84,7 @@ var MigrationWizard = {
os.removeObserver(this, "Migration:Started");
os.removeObserver(this, "Migration:ItemBeforeMigrate");
os.removeObserver(this, "Migration:ItemAfterMigrate");
os.removeObserver(this, "Migration:ItemError");
os.removeObserver(this, "Migration:Ended");
},
@ -489,6 +493,35 @@ var MigrationWizard = {
nextButton.click();
}
break;
case "Migration:ItemError":
var type = "undefined";
switch (parseInt(aData)) {
case Ci.nsIBrowserProfileMigrator.SETTINGS:
type = "settings";
break;
case Ci.nsIBrowserProfileMigrator.COOKIES:
type = "cookies";
break;
case Ci.nsIBrowserProfileMigrator.HISTORY:
type = "history";
break;
case Ci.nsIBrowserProfileMigrator.FORMDATA:
type = "form data";
break;
case Ci.nsIBrowserProfileMigrator.PASSWORDS:
type = "passwords";
break;
case Ci.nsIBrowserProfileMigrator.BOOKMARKS:
type = "bookmarks";
break;
case Ci.nsIBrowserProfileMigrator.OTHERDATA:
type = "misc. data";
break;
}
Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService)
.logStringMessage("some " + type + " did not successfully migrate.");
break;
}
},

View File

@ -39,6 +39,7 @@
#define browserprofilemigratorutils___h___
#define MIGRATION_ITEMBEFOREMIGRATE "Migration:ItemBeforeMigrate"
#define MIGRATION_ITEMMIGRATEERROR "Migration:ItemError"
#define MIGRATION_ITEMAFTERMIGRATE "Migration:ItemAfterMigrate"
#define MIGRATION_STARTED "Migration:Started"
#define MIGRATION_ENDED "Migration:Ended"
@ -47,11 +48,12 @@
mObserverService->NotifyObservers(nsnull, message, item)
#define COPY_DATA(func, replace, itemIndex) \
if (NS_SUCCEEDED(rv) && (aItems & itemIndex || !aItems)) { \
if ((aItems & itemIndex || !aItems)) { \
nsAutoString index; \
index.AppendInt(itemIndex); \
NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); \
rv = func(replace); \
if (NS_FAILED(func(replace))) \
NOTIFY_OBSERVERS(MIGRATION_ITEMMIGRATEERROR, index.get()); \
NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); \
}

View File

@ -70,6 +70,8 @@
#include "nsILocalFileWin.h"
#include "nsAutoPtr.h"
#include "prnetdb.h"
#include <objbase.h>
#include <shlguid.h>
#include <urlhist.h>
@ -1919,14 +1921,20 @@ nsIEProfileMigrator::CopyCookiesFromBuffer(char *aBuffer,
nsDependentCString stringName(name),
stringPath(path);
// delete any possible extant matching host cookie
if (hostCopy[0] == '.')
// delete any possible extant matching host cookie and
// check if we're dealing with an IPv4/IPv6 hostname.
PRBool isIPAddress = PR_FALSE;
if (hostCopy[0] == '.') {
aCookieManager->Remove(nsDependentCString(hostCopy+1),
stringName, stringPath, PR_FALSE);
PRNetAddr addr;
if (PR_StringToNetAddr(hostCopy+1, &addr) == PR_SUCCESS)
isIPAddress = PR_TRUE;
}
nsresult onerv;
// Add() makes a new domain cookie
onerv = aCookieManager->Add(nsDependentCString(hostCopy),
onerv = aCookieManager->Add(nsDependentCString(hostCopy + (isIPAddress ? 1 : 0)),
stringPath,
stringName,
nsDependentCString(value),

View File

@ -745,24 +745,59 @@ SessionStoreService.prototype = {
!this._inPrivateBrowsing) {
// default to the most-recently closed window
// don't use popup windows
let state = null;
let newClosedWindows = this._closedWindows.filter(function(aWinState) {
if (!state && !aWinState.isPopup) {
state = aWinState;
return false;
let closedWindowState = null;
let closedWindowIndex;
for (let i = 0; i < this._closedWindows.length; i++) {
// Take the first non-popup, point our object at it, and break out.
if (!this._closedWindows[i].isPopup) {
closedWindowState = this._closedWindows[i];
closedWindowIndex = i;
break;
}
return true;
});
if (state) {
delete state.hidden;
}
if (closedWindowState) {
let newWindowState;
#ifndef XP_MACOSX
if (!this._doResumeSession())
if (!this._doResumeSession()) {
#endif
state.tabs = state.tabs.filter(function (tab) tab.pinned);
if (state.tabs.length > 0) {
this._closedWindows = newClosedWindows;
// We want to split the window up into pinned tabs and unpinned tabs.
// Pinned tabs should be restored. If there are any remaining tabs,
// they should be added back to _closedWindows.
// We'll cheat a little bit and reuse _prepDataForDeferredRestore
// even though it wasn't built exactly for this.
let [appTabsState, normalTabsState] =
this._prepDataForDeferredRestore(JSON.stringify({ windows: [closedWindowState] }));
// These are our pinned tabs, which we should restore
if (appTabsState.windows.length) {
newWindowState = appTabsState.windows[0];
delete newWindowState.__lastSessionWindowID;
}
// In case there were no unpinned tabs, remove the window from _closedWindows
if (!normalTabsState.windows.length) {
this._closedWindows.splice(closedWindowIndex, 1);
}
// Or update _closedWindows with the modified state
else {
delete normalTabsState.windows[0].__lastSessionWindowID;
this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
}
#ifndef XP_MACOSX
}
else {
// If we're just restoring the window, make sure it gets removed from
// _closedWindows.
this._closedWindows.splice(closedWindowIndex, 1);
newWindowState = closedWindowState;
delete newWindowState.hidden;
}
#endif
if (newWindowState) {
// Ensure that the window state isn't hidden
this._restoreCount = 1;
state = { windows: [state] };
let state = { windows: [newWindowState] };
this.restoreWindow(aWindow, state, this._isCmdLineEmpty(aWindow, state));
}
}

View File

@ -120,6 +120,7 @@ _BROWSER_TEST_FILES = \
browser_581593.js \
browser_586147.js \
browser_586068-cascaded_restore.js \
browser_589246.js \
browser_590268.js \
browser_600545.js \
$(NULL)

View File

@ -0,0 +1,276 @@
/* ***** 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 sessionstore test code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Paul OShannessy <paul@oshannessy.com>
*
* 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 ***** */
// Mirrors WINDOW_ATTRIBUTES IN nsSessionStore.js
const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
Cu.import("resource://gre/modules/Services.jsm");
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
let stateBackup = ss.getBrowserState();
let originalWarnOnClose = gPrefService.getBoolPref("browser.tabs.warnOnClose");
let originalStartupPage = gPrefService.getIntPref("browser.startup.page");
let originalWindowType = document.documentElement.getAttribute("windowtype");
let gotLastWindowClosedTopic = false;
let shouldPinTab = false;
let shouldOpenTabs = false;
let shouldCloseTab = false;
let testNum = 0;
let afterTestCallback;
// Set state so we know the closed windows content
let testState = {
windows: [
{ tabs: [{ entries: [{ url: "http://example.org" }] }] }
],
_closedWindows: []
};
// We'll push a set of conditions and callbacks into this array
// Ideally we would also test win/linux under a complete set of conditions, but
// the tests for osx mirror the other set of conditions possible on win/linux.
let tests = [];
// the third & fourth test share a condition check, keep it DRY
function checkOSX34Generator(num) {
return function(aPreviousState, aCurState) {
// In here, we should have restored the pinned tab, so only the unpinned tab
// should be in aCurState. So let's shape our expectations.
let expectedState = JSON.parse(aPreviousState);
expectedState[0].tabs.shift();
// size attributes are stripped out in _prepDataForDeferredRestore in nsSessionStore.
// This isn't the best approach, but neither is comparing JSON strings
WINDOW_ATTRIBUTES.forEach(function (attr) delete expectedState[0][attr]);
is(aCurState, JSON.stringify(expectedState),
"test #" + num + ": closedWindowState is as expected");
};
}
function checkNoWindowsGenerator(num) {
return function(aPreviousState, aCurState) {
is(aCurState, "[]", "test #" + num + ": there should be no closedWindowsLeft");
};
}
// The first test has 0 pinned tabs and 1 unpinned tab
tests.push({
pinned: false,
extra: false,
close: false,
checkWinLin: checkNoWindowsGenerator(1),
checkOSX: function(aPreviousState, aCurState) {
is(aCurState, aPreviousState, "test #1: closed window state is unchanged");
}
});
// The second test has 1 pinned tab and 0 unpinned tabs.
tests.push({
pinned: true,
extra: false,
close: false,
checkWinLin: checkNoWindowsGenerator(2),
checkOSX: checkNoWindowsGenerator(2)
});
// The third test has 1 pinned tab and 2 unpinned tabs.
tests.push({
pinned: true,
extra: true,
close: false,
checkWinLin: checkNoWindowsGenerator(3),
checkOSX: checkOSX34Generator(3)
});
// The fourth test has 1 pinned tab, 2 unpinned tabs, and closes one unpinned tab.
tests.push({
pinned: true,
extra: true,
close: "one",
checkWinLin: checkNoWindowsGenerator(4),
checkOSX: checkOSX34Generator(4)
});
// The fifth test has 1 pinned tab, 2 unpinned tabs, and closes both unpinned tabs.
tests.push({
pinned: true,
extra: true,
close: "both",
checkWinLin: checkNoWindowsGenerator(5),
checkOSX: checkNoWindowsGenerator(5)
});
function test() {
/** Test for Bug 589246 - Closed window state getting corrupted when closing
and reopening last browser window without exiting browser **/
waitForExplicitFinish();
// windows opening & closing, so extending the timeout
requestLongerTimeout(2);
// We don't want the quit dialog pref
gPrefService.setBoolPref("browser.tabs.warnOnClose", false);
// Ensure that we would restore the session (important for Windows)
gPrefService.setIntPref("browser.startup.page", 3);
runNextTestOrFinish();
}
function runNextTestOrFinish() {
if (tests.length) {
setupForTest(tests.shift())
}
else {
// some state is cleaned up at the end of each test, but not all
["browser.tabs.warnOnClose", "browser.startup.page"].forEach(function(p) {
if (gPrefService.prefHasUserValue(p))
gPrefService.clearUserPref(p);
});
ss.setBrowserState(stateBackup);
executeSoon(finish);
}
}
function setupForTest(aConditions) {
// reset some checks
gotLastWindowClosedTopic = false;
shouldPinTab = aConditions.pinned;
shouldOpenTabs = aConditions.extra;
shouldCloseTab = aConditions.close;
testNum++;
// set our test callback
afterTestCallback = /Mac/.test(navigator.platform) ? aConditions.checkOSX
: aConditions.checkWinLin;
// Add observers
Services.obs.addObserver(onLastWindowClosed, "browser-lastwindow-close-granted", false);
// Set the state
Services.obs.addObserver(onStateRestored, "sessionstore-browser-state-restored", false);
ss.setBrowserState(JSON.stringify(testState));
}
function onStateRestored(aSubject, aTopic, aData) {
info("test #" + testNum + ": onStateRestored");
Services.obs.removeObserver(onStateRestored, "sessionstore-browser-state-restored", false);
// change this window's windowtype so that closing a new window will trigger
// browser-lastwindow-close-granted.
document.documentElement.setAttribute("windowtype", "navigator:testrunner");
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "http://example.com");
newWin.addEventListener("load", function(aEvent) {
newWin.removeEventListener("load", arguments.callee, false);
newWin.gBrowser.selectedBrowser.addEventListener("load", function() {
newWin.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
// pin this tab
if (shouldPinTab)
newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab);
newWin.addEventListener("unload", onWindowUnloaded, false);
// Open a new tab as well. On Windows/Linux this will be restored when the
// new window is opened below (in onWindowUnloaded). On OS X we'll just
// restore the pinned tabs, leaving the unpinned tab in the closedWindowsData.
if (shouldOpenTabs) {
let newTab = newWin.gBrowser.addTab("about:config");
let newTab2 = newWin.gBrowser.addTab("about:buildconfig");
newTab.linkedBrowser.addEventListener("load", function() {
newTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
if (shouldCloseTab == "one") {
newWin.gBrowser.removeTab(newTab2);
}
else if (shouldCloseTab == "both") {
newWin.gBrowser.removeTab(newTab);
newWin.gBrowser.removeTab(newTab2);
}
newWin.BrowserTryToCloseWindow();
}, true);
}
else {
newWin.BrowserTryToCloseWindow();
}
}, true);
}, false);
}
// This will be called before the window is actually closed
function onLastWindowClosed(aSubject, aTopic, aData) {
info("test #" + testNum + ": onLastWindowClosed");
Services.obs.removeObserver(onLastWindowClosed, "browser-lastwindow-close-granted", false);
gotLastWindowClosedTopic = true;
}
// This is the unload event listener on the new window (from onStateRestored).
// Unload is fired after the window is closed, so sessionstore has already
// updated _closedWindows (which is important). We'll open a new window here
// which should actually trigger the bug.
function onWindowUnloaded() {
info("test #" + testNum + ": onWindowClosed");
ok(gotLastWindowClosedTopic, "test #" + testNum + ": browser-lastwindow-close-granted was notified prior");
let previousClosedWindowData = ss.getClosedWindowData();
// Now we want to open a new window
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "about:robots");
newWin.addEventListener("load", function(aEvent) {
newWin.removeEventListener("load", arguments.callee, false);
newWin.gBrowser.selectedBrowser.addEventListener("load", function () {
// Good enough for checking the state
afterTestCallback(previousClosedWindowData, ss.getClosedWindowData());
afterTestCleanup(newWin);
}, true);
}, false);
}
function afterTestCleanup(aNewWin) {
executeSoon(function() {
aNewWin.close();
document.documentElement.setAttribute("windowtype", originalWindowType);
runNextTestOrFinish();
});
}

View File

@ -385,8 +385,6 @@
#ifdef MOZ_SERVICES_SYNC
@BINPATH@/components/SyncComponents.manifest
@BINPATH@/components/Weave.js
@BINPATH@/components/WeaveCrypto.manifest
@BINPATH@/components/WeaveCrypto.js
#endif
; Modules

View File

@ -67,6 +67,8 @@ components/nsUrlClassifierTable.js
components/nsXmlRpcClient.js
components/pluginGlue.js
components/sidebar.xpt
components/WeaveCrypto.js
components/WeaveCrypto.manifest
components/xmlextras.xpt
components/xpcom.xpt
components/xpti.dat
@ -621,7 +623,6 @@ xpicleanup@BIN_SUFFIX@
components/storage-Legacy.js
components/storage-mozStorage.js
components/txEXSLTRegExFunctions.js
components/WeaveCrypto.js
components/Weave.js
components/WebContentConverter.js
defaults/autoconfig/platform.js

View File

@ -33,19 +33,12 @@
0 0 2px 1px rgba(255,255,255,.25) inset;
}
#main-window[privatebrowsingmode=temporary] #appmenu-button:not(:-moz-window-inactive) {
#main-window[privatebrowsingmode=temporary] #appmenu-button {
-moz-border-left-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
-moz-border-bottom-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
-moz-border-right-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
}
#appmenu-button:-moz-window-inactive {
-moz-border-left-colors: rgba(255,255,255,.4) rgba(0,0,0,.5);
-moz-border-bottom-colors: rgba(255,255,255,.4) rgba(0,0,0,.5);
-moz-border-right-colors: rgba(255,255,255,.4) rgba(0,0,0,.5);
box-shadow: 0 0 0 1px rgba(255,255,255,.25) inset;
}
/* Bug 413060, comment 16: Vista Aero is a special case where we use a
tooltip appearance for the address bar popup panels */
#identity-popup,

View File

@ -126,16 +126,11 @@
margin: 0;
}
#main-window[privatebrowsingmode=temporary] #appmenu-button:not(:-moz-window-inactive) {
#main-window[privatebrowsingmode=temporary] #appmenu-button {
background-image: -moz-linear-gradient(rgb(153,38,211), rgb(105,19,163) 95%);
border-color: rgba(43,8,65,.9);
}
#appmenu-button:-moz-window-inactive {
background-image: none;
border-color: rgba(0,0,0,.4);
}
#appmenu-button:hover:not(:active):not([open]) {
background-image: -moz-radial-gradient(center bottom, farthest-side, rgba(252,240,89,.5) 10%, rgba(252,240,89,0) 70%),
-moz-radial-gradient(center bottom, farthest-side, rgb(236,133,0), rgba(255,229,172,0)),

View File

@ -457,7 +457,7 @@ EnumerateOverride(nsIURI* aURIKey,
struct EnumerationArgs
{
nsTArray<ChromePackage>& packages;
InfallibleTArray<ChromePackage>& packages;
const nsCString& selectedLocale;
const nsCString& selectedSkin;
};
@ -466,9 +466,9 @@ void
nsChromeRegistryChrome::SendRegisteredChrome(
mozilla::dom::PContentParent* aParent)
{
nsTArray<ChromePackage> packages;
nsTArray<ResourceMapping> resources;
nsTArray<OverrideMapping> overrides;
InfallibleTArray<ChromePackage> packages;
InfallibleTArray<ResourceMapping> resources;
InfallibleTArray<OverrideMapping> overrides;
EnumerationArgs args = {
packages, mSelectedLocale, mSelectedSkin

View File

@ -612,7 +612,6 @@ AIX_OBJMODEL = @AIX_OBJMODEL@
# For OS/2 build
MOZ_OS2_TOOLS = @MOZ_OS2_TOOLS@
MOZ_OS2_USE_DECLSPEC = @MOZ_OS2_USE_DECLSPEC@
MOZ_OS2_HIGH_MEMORY = @MOZ_OS2_HIGH_MEMORY@
HAVE_XIE=@HAVE_XIE@

View File

@ -1249,26 +1249,10 @@ $(DEF_FILE): $(OBJS) $(SHARED_LIBRARY_LIBS)
echo CODE LOADONCALL MOVEABLE DISCARDABLE >> $@
echo DATA PRELOAD MOVEABLE MULTIPLE NONSHARED >> $@
echo EXPORTS >> $@
ifeq ($(IS_COMPONENT),1)
ifeq ($(HAS_EXTRAEXPORTS),1)
ifndef MOZ_OS2_USE_DECLSPEC
$(FILTER) $(OBJS) $(SHARED_LIBRARY_LIBS) >> $@
endif
else
echo _NSModule >> $@
endif
else
ifndef MOZ_OS2_USE_DECLSPEC
$(FILTER) $(OBJS) $(SHARED_LIBRARY_LIBS) >> $@
endif
endif
$(ADD_TO_DEF_FILE)
ifdef MOZ_OS2_USE_DECLSPEC
$(IMPORT_LIBRARY): $(SHARED_LIBRARY)
else
$(IMPORT_LIBRARY): $(DEF_FILE)
endif
rm -f $@
$(IMPLIB) $@ $^
$(RANLIB) $@

View File

@ -2642,7 +2642,7 @@ ia64*-hpux*)
DSO_LDOPTS='-Zdll'
BIN_FLAGS='-Zlinker /ST:0x100000'
IMPLIB='emximp -o'
FILTER='emxexp -o'
FILTER='true'
LDFLAGS='-Zmap'
WARNINGS_AS_ERRORS='-Werror'
MOZ_DEBUG_FLAGS="-g -fno-inline"
@ -2678,9 +2678,8 @@ ia64*-hpux*)
[return 0;],
ac_os2_declspec="yes",
ac_os2_declspec="no")])
if test "$ac_os2_declspec" = "yes"; then
FILTER='true'
MOZ_OS2_USE_DECLSPEC='1'
if test "$ac_os2_declspec" != "yes"; then
AC_MSG_ERROR([Compiler does not support __declspec(dllexport), install GCC-4.3.2 or newer])
fi
;;
@ -8934,7 +8933,6 @@ AC_SUBST(USE_DEPENDENT_LIBS)
AC_SUBST(MOZ_BUILD_ROOT)
AC_SUBST(MOZ_OS2_TOOLS)
AC_SUBST(MOZ_OS2_USE_DECLSPEC)
AC_SUBST(MOZ_POST_DSO_LIB_COMMAND)
AC_SUBST(MOZ_POST_PROGRAM_COMMAND)

View File

@ -1700,6 +1700,14 @@ public:
*/
static bool IsSubDocumentTabbable(nsIContent* aContent);
/**
* Flushes the layout tree (recursively)
*
* @param aWindow the window the flush should start at
*
*/
static void FlushLayoutForTree(nsIDOMWindow* aWindow);
private:
static PRBool InitializeEventTable();

View File

@ -738,7 +738,7 @@ public:
nsSlots* s = GetSlots();
if (s) {
NS_ASSERTION(s->mMutationObservers.IndexOf(aMutationObserver) ==
nsTArray_base::NoIndex,
nsTArray<int>::NoIndex,
"Observer already in the list");
s->mMutationObservers.AppendElement(aMutationObserver);
}

View File

@ -58,8 +58,9 @@ class nsIAtom;
class nsICSSStyleRule;
class nsISVGValue;
class nsIDocument;
template<class E> class nsTArray;
template<class E> class nsTPtrArray;
template<class E, class A> class nsTArray;
template<class E, class A> class nsTPtrArray;
struct nsTArrayDefaultAllocator;
#define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
@ -381,7 +382,7 @@ private:
PRBool aCanBePercent = PR_FALSE,
PRBool* aIsPercent = nsnull) const;
static nsTPtrArray<const EnumTable>* sEnumTableArray;
static nsTPtrArray<const EnumTable, nsTArrayDefaultAllocator>* sEnumTableArray;
PtrBits mBits;
};

View File

@ -6303,6 +6303,40 @@ nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
return !zombieViewer;
}
void
nsContentUtils::FlushLayoutForTree(nsIDOMWindow* aWindow)
{
nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
if (!piWin)
return;
// Note that because FlushPendingNotifications flushes parents, this
// is O(N^2) in docshell tree depth. However, the docshell tree is
// usually pretty shallow.
nsCOMPtr<nsIDOMDocument> domDoc;
aWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->FlushPendingNotifications(Flush_Layout);
}
nsCOMPtr<nsIDocShellTreeNode> node =
do_QueryInterface(piWin->GetDocShell());
if (node) {
PRInt32 i = 0, i_end;
node->GetChildCount(&i_end);
for (; i < i_end; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
node->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDOMWindow> win = do_GetInterface(item);
if (win) {
FlushLayoutForTree(win);
}
}
}
}
void nsContentUtils::RemoveNewlines(nsString &aString)
{
// strip CR/LF and null

View File

@ -205,6 +205,8 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID);
using namespace mozilla::dom;
typedef nsTArray<Link*> LinkArray;
#ifdef PR_LOGGING
static PRLogModuleInfo* gDocumentLeakPRLog;
@ -3902,7 +3904,7 @@ nsDocument::InternalAllowXULXBL()
void
nsDocument::AddObserver(nsIDocumentObserver* aObserver)
{
NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray_base::NoIndex,
NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
"Observer already in the list");
mObservers.AppendElement(aObserver);
AddMutationObserver(aObserver);
@ -5543,8 +5545,10 @@ nsDocument::GetAnimationController()
}
}
// If we're hidden (or being hidden), notify the animation controller.
if (!mIsShowing) {
// If we're hidden (or being hidden), notify the newly-created animation
// controller. (Skip this check for SVG-as-an-image documents, though,
// because they don't get OnPageShow / OnPageHide calls).
if (!mIsShowing && !mIsBeingUsedAsImage) {
mAnimationController->OnPageHide();
}
@ -7546,7 +7550,7 @@ static
PLDHashOperator
EnumerateStyledLinks(nsPtrHashKey<Link>* aEntry, void* aArray)
{
nsTArray<Link*>* array = static_cast<nsTArray<Link*>*>(aArray);
LinkArray* array = static_cast<LinkArray*>(aArray);
(void)array->AppendElement(aEntry->GetKey());
return PL_DHASH_NEXT;
}
@ -7557,12 +7561,12 @@ nsDocument::RefreshLinkHrefs()
// Get a list of all links we know about. We will reset them, which will
// remove them from the document, so we need a copy of what is in the
// hashtable.
nsTArray<Link*> linksToNotify(mStyledLinks.Count());
LinkArray linksToNotify(mStyledLinks.Count());
(void)mStyledLinks.EnumerateEntries(EnumerateStyledLinks, &linksToNotify);
// Reset all of our styled links.
MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_STATE, PR_TRUE);
for (nsTArray_base::size_type i = 0; i < linksToNotify.Length(); i++) {
for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
linksToNotify[i]->ResetLinkState(true);
}
}

View File

@ -723,8 +723,7 @@ nsFrameLoader::ShowRemoteFrame(const nsIntSize& size)
mRemoteBrowser->Show(size);
mRemoteBrowserShown = PR_TRUE;
nsCOMPtr<nsIChromeFrameMessageManager> dummy;
GetMessageManager(getter_AddRefs(dummy)); // Initialize message manager.
EnsureMessageManager();
} else {
mRemoteBrowser->Move(size);
}

View File

@ -234,7 +234,7 @@ nsFrameMessageManager::SendSyncMessage()
nsString json;
nsresult rv = GetParamsForMessage(messageName, json);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsString> retval;
InfallibleTArray<nsString> retval;
if (mSyncCallback(mCallbackData, messageName, json, &retval)) {
nsAXPCNativeCallContext* ncc = nsnull;
rv = nsContentUtils::XPConnect()->GetCurrentNativeCallContext(&ncc);
@ -326,7 +326,7 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
const nsAString& aMessage,
PRBool aSync, const nsAString& aJSON,
JSObject* aObjectsArray,
nsTArray<nsString>* aJSONRetVal,
InfallibleTArray<nsString>* aJSONRetVal,
JSContext* aContext)
{
JSContext* ctx = mContext ? mContext : aContext;
@ -742,7 +742,7 @@ bool SendAsyncMessageToChildProcess(void* aCallbackData,
bool SendSyncMessageToParentProcess(void* aCallbackData,
const nsAString& aMessage,
const nsAString& aJSON,
nsTArray<nsString>* aJSONRetVal)
InfallibleTArray<nsString>* aJSONRetVal)
{
mozilla::dom::ContentChild* cc =
mozilla::dom::ContentChild::GetSingleton();

View File

@ -66,7 +66,7 @@ typedef bool (*nsLoadScriptCallback)(void* aCallbackData, const nsAString& aURL)
typedef bool (*nsSyncMessageCallback)(void* aCallbackData,
const nsAString& aMessage,
const nsAString& aJSON,
nsTArray<nsString>* aJSONRetVal);
InfallibleTArray<nsString>* aJSONRetVal);
typedef bool (*nsAsyncMessageCallback)(void* aCallbackData,
const nsAString& aMessage,
const nsAString& aJSON);
@ -129,7 +129,7 @@ public:
nsresult ReceiveMessage(nsISupports* aTarget, const nsAString& aMessage,
PRBool aSync, const nsAString& aJSON,
JSObject* aObjectsArray,
nsTArray<nsString>* aJSONRetVal,
InfallibleTArray<nsString>* aJSONRetVal,
JSContext* aContext = nsnull);
void AddChildManager(nsFrameMessageManager* aManager,
PRBool aLoadScripts = PR_TRUE);

View File

@ -54,7 +54,7 @@
bool SendSyncMessageToParent(void* aCallbackData,
const nsAString& aMessage,
const nsAString& aJSON,
nsTArray<nsString>* aJSONRetVal)
InfallibleTArray<nsString>* aJSONRetVal)
{
nsInProcessTabChildGlobal* tabChild =
static_cast<nsInProcessTabChildGlobal*>(aCallbackData);

View File

@ -15,7 +15,7 @@
* The Original Code is Mozilla Code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
@ -146,29 +146,6 @@ nsScriptElement::ContentInserted(nsIDocument *aDocument,
MaybeProcessScript();
}
static PRBool
InNonScriptingContainer(nsIContent* aNode)
{
aNode = aNode->GetParent();
while (aNode) {
// XXX noframes and noembed are currently unconditionally not
// displayed and processed. This might change if we support either
// prefs or per-document container settings for not allowing
// frames or plugins.
if (aNode->IsHTML()) {
nsIAtom *localName = aNode->Tag();
if (localName == nsGkAtoms::iframe ||
localName == nsGkAtoms::noframes ||
localName == nsGkAtoms::noembed) {
return PR_TRUE;
}
}
aNode = aNode->GetParent();
}
return PR_FALSE;
}
nsresult
nsScriptElement::MaybeProcessScript()
{
@ -185,16 +162,9 @@ nsScriptElement::MaybeProcessScript()
FreezeUriAsyncDefer();
if (InNonScriptingContainer(cont)) {
// Make sure to flag ourselves as evaluated
mAlreadyStarted = PR_TRUE;
return NS_OK;
}
nsresult scriptresult = NS_OK;
nsRefPtr<nsScriptLoader> loader = cont->GetOwnerDoc()->ScriptLoader();
mAlreadyStarted = PR_TRUE;
scriptresult = loader->ProcessScriptElement(this);
nsresult scriptresult = loader->ProcessScriptElement(this);
// The only error we don't ignore is NS_ERROR_HTMLPARSER_BLOCK
// However we don't want to override other success values

View File

@ -13,11 +13,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=453736
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453736">Mozilla Bug 453736</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 453736 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
@ -26,39 +24,7 @@ addLoadEvent(function() {
function() { return document.createElementNS("http://www.w3.org/2000/svg", "script"); }
];
const noScriptContainers = ["iframe", "noframes", "noembed"];
for (var i = 0; i < noScriptContainers.length; ++i) {
for each (var func in scriptCreationFuncs) {
var cont = noScriptContainers[i];
var node = document.createElement(cont);
document.body.appendChild(node);
var s = func();
s.setAttribute("type", "application/javascript");
s.appendChild(document.createTextNode('window["'+cont+'ScriptRan"] = true'));
window[cont+"ScriptRan"] = false;
document.body.appendChild(s.cloneNode(true));
is(window[cont+"ScriptRan"], true,
"Clone of non-inserted script created with " + func +" should run");
window[cont+"ScriptRan"] = false;
node.appendChild(s);
is(window[cont+"ScriptRan"], false,
"Script created with " + func +" shouldn't run when inserting in <"+cont+">");
window[cont+"ScriptRan"] = false;
document.body.appendChild(s);
is(window[cont+"ScriptRan"], false,
"Script created with " + func + " shouldn't run when moving out of <"+cont+">");
window[cont+"ScriptRan"] = false;
document.body.appendChild(s.cloneNode(true));
is(window[cont+"ScriptRan"], false,
"Clone of script inside <" + cont + "> created with " + func + " shouldn't run");
}
}
const scriptContainers = ["div"];
const scriptContainers = ["div", "iframe", "noframes", "noembed"];
for (var i = 0; i < scriptContainers.length; ++i) {
for each (var func in scriptCreationFuncs) {
var cont = scriptContainers[i];
@ -87,10 +53,6 @@ addLoadEvent(function() {
SimpleTest.finish();
});
</script>
</pre>
</body>

View File

@ -64,40 +64,6 @@ DocumentRendererChild::DocumentRendererChild()
DocumentRendererChild::~DocumentRendererChild()
{}
static void
FlushLayoutForTree(nsIDOMWindow* aWindow)
{
nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
if (!piWin)
return;
// Note that because FlushPendingNotifications flushes parents, this
// is O(N^2) in docshell tree depth. However, the docshell tree is
// usually pretty shallow.
nsCOMPtr<nsIDOMDocument> domDoc;
aWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->FlushPendingNotifications(Flush_Layout);
}
nsCOMPtr<nsIDocShellTreeNode> node =
do_QueryInterface(piWin->GetDocShell());
if (node) {
PRInt32 i = 0, i_end;
node->GetChildCount(&i_end);
for (; i < i_end; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
node->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDOMWindow> win = do_GetInterface(item);
if (win) {
FlushLayoutForTree(win);
}
}
}
}
bool
DocumentRendererChild::RenderDocument(nsIDOMWindow *window,
const nsRect& documentRect,
@ -109,7 +75,7 @@ DocumentRendererChild::RenderDocument(nsIDOMWindow *window,
nsCString& data)
{
if (flushLayout)
FlushLayoutForTree(window);
nsContentUtils::FlushLayoutForTree(window);
nsCOMPtr<nsPresContext> presContext;
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(window);

View File

@ -3562,41 +3562,6 @@ nsCanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op)
return NS_OK;
}
static void
FlushLayoutForTree(nsIDOMWindow* aWindow)
{
nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
if (!piWin)
return;
// Note that because FlushPendingNotifications flushes parents, this
// is O(N^2) in docshell tree depth. However, the docshell tree is
// usually pretty shallow.
nsCOMPtr<nsIDOMDocument> domDoc;
aWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->FlushPendingNotifications(Flush_Layout);
}
nsCOMPtr<nsIDocShellTreeNode> node =
do_QueryInterface(piWin->GetDocShell());
if (node) {
PRInt32 i = 0, i_end;
node->GetChildCount(&i_end);
for (; i < i_end; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
node->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDOMWindow> win = do_GetInterface(item);
if (win) {
FlushLayoutForTree(win);
}
}
}
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::DrawWindow(nsIDOMWindow* aWindow, float aX, float aY,
float aW, float aH,
@ -3625,7 +3590,7 @@ nsCanvasRenderingContext2D::DrawWindow(nsIDOMWindow* aWindow, float aX, float aY
// Flush layout updates
if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH))
FlushLayoutForTree(aWindow);
nsContentUtils::FlushLayoutForTree(aWindow);
nsRefPtr<nsPresContext> presContext;
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow);

View File

@ -373,8 +373,7 @@ nsHTMLCanvasElement::GetContextHelper(const nsAString& aContextId,
{
NS_ENSURE_ARG(aContext);
nsCString ctxId;
ctxId.Assign(NS_LossyConvertUTF16toASCII(aContextId));
NS_LossyConvertUTF16toASCII ctxId(aContextId);
// check that ctxId is clamped to A-Za-z0-9_-
for (PRUint32 i = 0; i < ctxId.Length(); i++) {

View File

@ -21,6 +21,7 @@
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
* Geoff Lankow <geoff@darktrojan.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -540,6 +541,9 @@ UploadLastDir::StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile)
NS_PRECONDITION(aFile, "aFile is null");
nsCOMPtr<nsIFile> parentFile;
aFile->GetParent(getter_AddRefs(parentFile));
if (!parentFile) {
return NS_OK;
}
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(parentFile);
// Store the data in memory instead of the CPS during private browsing mode

View File

@ -1217,8 +1217,7 @@ NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Autofocus, autofocus)
NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Disabled, disabled)
NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Multiple, multiple)
NS_IMPL_STRING_ATTR(nsHTMLSelectElement, Name, name)
NS_IMPL_POSITIVE_INT_ATTR_DEFAULT_VALUE(nsHTMLSelectElement, Size, size,
GetDefaultSize())
NS_IMPL_POSITIVE_INT_ATTR_DEFAULT_VALUE(nsHTMLSelectElement, Size, size, 0)
NS_IMPL_INT_ATTR(nsHTMLSelectElement, TabIndex, tabindex)
NS_IMETHODIMP

View File

@ -495,14 +495,6 @@ protected:
return PR_TRUE;
}
/**
* Helper method to get the default size.
*/
PRInt32 GetDefaultSize() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ? 4 : 1;
}
/** The options[] array */
nsRefPtr<nsHTMLOptionCollection> mOptions;
/** false if the parser is in the middle of adding children. */

View File

@ -240,6 +240,7 @@ _TEST_FILES = \
file_bug297761.html \
test_bug607145.html \
test_bug601061.html \
reflect.js \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,95 @@
/**
* Checks that a given attribute name for a given element is correctly reflected
* as an unsigned int.
*/
function reflectUnsignedInt(aElement, aAttr, aNonNull, aDefault)
{
function checkGetter(aElement, aAttr, aValue)
{
is(aElement[aAttr], aValue, "." + aAttr + " should be equals " + aValue);
is(aElement.getAttribute(aAttr), aValue,
"@" + aAttr + " should be equals " + aValue);
}
if (!aDefault) {
if (aNonNull) {
aDefault = 1;
} else {
aDefault = 0;
}
}
// Check default value.
is(aElement[aAttr], aDefault, "default value should be " + aDefault);
ok(!aElement.hasAttribute(aAttr), aAttr + " shouldn't be present");
var values = [ 1, 3, 42, 2147483647 ];
for each (var value in values) {
aElement[aAttr] = value;
checkGetter(aElement, aAttr, value);
}
for each (var value in values) {
aElement.setAttribute(aAttr, value);
checkGetter(aElement, aAttr, value);
}
// -3000000000 is equivalent to 1294967296 when using the IDL attribute.
aElement[aAttr] = -3000000000;
checkGetter(aElement, aAttr, 1294967296);
// When setting the content atribute, it's a string so it will be unvalid.
aElement.setAttribute(aAttr, -3000000000);
is(aElement.getAttribute(aAttr), -3000000000,
"@" + aAttr + " should be equals to " + -3000000000);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
var nonValidValues = [
/* invalid value, value in the unsigned int range */
[ -2147483648, 2147483648 ],
[ -1, 4294967295 ],
[ 3147483647, 3147483647 ],
];
for each (var values in nonValidValues) {
aElement[aAttr] = values[0];
is(aElement.getAttribute(aAttr), values[1],
"@" + aAttr + " should be equals to " + values[1]);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
}
for each (var values in nonValidValues) {
aElement.setAttribute(aAttr, values[0]);
is(aElement.getAttribute(aAttr), values[0],
"@" + aAttr + " should be equals to " + values[0]);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
}
// Setting to 0 should throw an error if aNonNull is true.
var caught = false;
try {
aElement[aAttr] = 0;
} catch(e) {
caught = true;
is(e.code, DOMException.INDEX_SIZE_ERR, "exception should be INDEX_SIZE_ERR");
}
if (aNonNull) {
ok(caught, "an exception should have been caught");
} else {
ok(!caught, "no exception should have been caught");
}
// If 0 is set in @aAttr, it will be ignored when calling .aAttr.
aElement.setAttribute(aAttr, 0);
is(aElement.getAttribute(aAttr), 0, "@" + aAttr + " should be equals to 0");
if (aNonNull) {
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
} else {
is(aElement[aAttr], 0, "." + aAttr + " should be equals to 0");
}
}

View File

@ -122,36 +122,36 @@ function checkSetSizeException(element)
element.removeAttribute('size');
}
function checkSizeWhenChangeMultiple(element)
function checkSizeWhenChangeMultiple(element, aDefaultNonMultiple, aDefaultMultiple)
{
s.setAttribute('size', -1)
is(s.size, 1, "Size IDL attribute should be 1");
is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
s.multiple = true;
is(s.size, 4, "Size IDL attribute should be 4");
is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
is(s.getAttribute('size'), -1, "Size content attribute should be -1");
s.setAttribute('size', -2);
is(s.size, 4, "Size IDL attribute should be 4");
is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
s.multiple = false;
is(s.size, 1, "Size IDL attribute should be 1");
is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
is(s.getAttribute('size'), -2, "Size content attribute should be -2");
}
var s = document.getElementById('s');
checkSizeReflection(s, 1);
checkSizeReflection(s, 0);
checkSetSizeException(s);
s.setAttribute('multiple', 'true');
checkSizeReflection(s, 4);
checkSizeReflection(s, 0);
checkSetSizeException(s);
s.removeAttribute('multiple');
checkSizeWhenChangeMultiple(s);
checkSizeWhenChangeMultiple(s, 0, 0);
</script>
</pre>

View File

@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=601061
<title>Test for Bug 601061</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="reflect.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
@ -17,100 +18,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=601061
/** Test for Bug 601061 **/
function reflectUnsignedInt(aElement, aAttr, aNonNull, aDefault)
{
function checkGetter(aElement, aAttr, aValue)
{
is(aElement[aAttr], aValue, "." + aAttr + " should be equals " + aValue);
is(aElement.getAttribute(aAttr), aValue,
"@" + aAttr + " should be equals " + aValue);
}
if (!aDefault) {
if (aNonNull) {
aDefault = 1;
} else {
aDefault = 0;
}
}
// Check default value.
is(aElement[aAttr], aDefault, "default value should be " + aDefault);
ok(!aElement.hasAttribute(aAttr), aAttr + " shouldn't be present");
var values = [ 1, 3, 42, 2147483647 ];
for each (var value in values) {
aElement[aAttr] = value;
checkGetter(aElement, aAttr, value);
}
for each (var value in values) {
aElement.setAttribute(aAttr, value);
checkGetter(aElement, aAttr, value);
}
// -3000000000 is equivalent to 1294967296 when using the IDL attribute.
aElement[aAttr] = -3000000000;
checkGetter(aElement, aAttr, 1294967296);
// When setting the content atribute, it's a string so it will be unvalid.
aElement.setAttribute(aAttr, -3000000000);
is(aElement.getAttribute(aAttr), -3000000000,
"@" + aAttr + " should be equals to " + -3000000000);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
var nonValidValues = [
/* invalid value, value in the unsigned int range */
[ -2147483648, 2147483648 ],
[ -1, 4294967295 ],
[ 3147483647, 3147483647 ],
];
for each (var values in nonValidValues) {
aElement[aAttr] = values[0];
is(aElement.getAttribute(aAttr), values[1],
"@" + aAttr + " should be equals to " + values[1]);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
}
for each (var values in nonValidValues) {
aElement.setAttribute(aAttr, values[0]);
is(aElement.getAttribute(aAttr), values[0],
"@" + aAttr + " should be equals to " + values[0]);
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
}
// Setting to 0 should throw an error if aNonNull is true.
var caught = false;
try {
aElement[aAttr] = 0;
} catch(e) {
caught = true;
is(e.code, DOMException.INDEX_SIZE_ERR, "exception should be INDEX_SIZE_ERR");
}
if (aNonNull) {
ok(caught, "an exception should have been caught");
} else {
ok(!caught, "no exception should have been caught");
}
// If 0 is set in @aAttr, it will be ignored when calling .aAttr.
aElement.setAttribute(aAttr, 0);
is(aElement.getAttribute(aAttr), 0, "@" + aAttr + " should be equals to 0");
if (aNonNull) {
is(aElement[aAttr], aDefault,
"." + aAttr + " should be equals to " + aDefault);
} else {
is(aElement[aAttr], 0, "." + aAttr + " should be equals to 0");
}
}
var input = document.createElement("input");
reflectUnsignedInt(input, "size", true, 20);
reflectUnsignedInt(document.createElement("input"), "size", true, 20);
</script>
</pre>

View File

@ -20,11 +20,15 @@ var manager = new MediaTestManager;
function do_loadedmetadata(e) {
var v = e.target;
if (e._finished)
if (v._finished)
return false;
v._seekTime = Math.round(v.duration / 2);
v.currentTime=v._seekTime;
var duration = v.duration;
v._seekTime = Math.round(duration / 2);
ok(!isNaN(duration), v._name + ": duration must be non NaN");
ok(v._seekTime >= 0.0, v._name + ": must have non-negative seek target");
ok(!isNaN(v._seekTime), v._name + ": seek target must be non NaN");
v.currentTime = v._seekTime;
return false;
}
@ -35,9 +39,8 @@ function do_seeking(e) {
v._seeking = true;
ok(v.currentTime == v._seekTime,
"Check currentTime of " + v.currentTime +
" is requested seek time of " + v._seekTime +
" in " + v._name);
v._name + ": currentTime of " + v.currentTime +
" should be requested seek time of " + v._seekTime);
return false;
}
@ -47,12 +50,13 @@ function do_timeupdate(e) {
return false;
ok(v.currentTime == v._seekTime,
"Check currentTime of " + v.currentTime +
" is requested seek time of " + v._seekTime +
" in " + v._name);
v._finished= true;
v._name + ": currentTime of " + v.currentTime +
" should be requested seek time of " + v._seekTime);
v._finished = true;
v.pause();
v.parentNode.removeChild(v);
v.src = "";
ok(true, v._name + ": finished");
manager.finished(v.token);
return false;
}
@ -63,6 +67,7 @@ function startTest(test, token) {
v.token = token;
manager.started(token);
v.src = test.name;
ok(true, test.name + ": started");
v._name = test.name;
v._seeking = false;
v._finished = false;

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<text id="myText">Used Text Element
<set attributeName="display" to="none"/>
</text>
</defs>
<use xlink:href="#myText" x="20" y="40"/>
<text x="20" y="60">Normal Text Element
<set attributeName="display" to="none"/>
</text>
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="reftest-wait">
<script>
function boom()
{
document.getElementById("circleID").removeChild(
document.getElementById("at"));
document.documentElement.removeAttribute("class");
}
window.addEventListener("load", boom, false);
</script>
<circle id="circleID">
<animate/>
<animateTransform id="at" attributeName="transform"/>
</circle>
<animate attributeName="stroke-width"/>
<use xlink:href="#circleID"/>
</svg>

After

Width:  |  Height:  |  Size: 580 B

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<text id="a">Text A</text>
<text id="b">Text B</text>
</defs>
<use xlink:href="#a" x="20" y="40">
<set attributeName="xlink:href" to="#b" dur="2s"/>
</use>
</svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="a">
<path d=""><animate/></path>
</g>
<g display="none">
<use xlink:href="#a" x="80"/>
<set attributeName="display" to="inline"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
<script>
<![CDATA[
function boom()
{
var anim = document.getElementById("a");
var newSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
var oldSvg = document.removeChild(document.documentElement);
document.appendChild(newSvg);
document.removeChild(document.documentElement);
newSvg.pauseAnimations();
document.appendChild(newSvg);
newSvg.appendChild(anim);
oldSvg.removeAttribute("class");
}
window.addEventListener("load", function() { setTimeout(boom, 200); }, false);
]]>
</script>
<animate id="a"/>
</svg>

After

Width:  |  Height:  |  Size: 634 B

View File

@ -0,0 +1,29 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
<script>
<![CDATA[
function boom()
{
try {
document.getElementById("set").beginElementAt(NaN);
return;
} catch (e) {}
try {
document.getElementById("set").endElementAt(NaN);
return;
} catch (e) {}
// If we got here we threw both exceptions and skipped both early-returns, as
// expected.
document.documentElement.removeAttribute("class");
}
window.addEventListener("load", boom, false);
]]>
</script>
<set id="set" attributeName="fill" to="green" begin="indefinite"/>
</svg>

After

Width:  |  Height:  |  Size: 595 B

View File

@ -16,11 +16,17 @@ load 554202-2.svg
load 554141-1.svg
load 555026-1.svg
load 556841-1.svg
load 572938-1.svg
load 572938-2.svg
load 572938-3.svg
load 572938-4.svg
load 588287-1.svg
load 588287-2.svg
load 590425-1.html
load 592477-1.xhtml
load 594653-1.svg
load 596796-1.svg
load 605345-1.svg
load 606101-1.svg
load 608549-1.svg
load 608295-1.html

View File

@ -74,9 +74,7 @@ GetRefreshDriverForDoc(nsIDocument* aDoc)
nsSMILAnimationController::nsSMILAnimationController()
: mResampleNeeded(PR_FALSE),
mDeferredStartSampling(PR_FALSE),
#ifdef DEBUG
mRunningSample(PR_FALSE),
#endif
mDocument(nsnull)
{
mAnimationElementTable.Init();
@ -189,7 +187,6 @@ void
nsSMILAnimationController::RegisterAnimationElement(
nsISMILAnimationElement* aAnimationElement)
{
NS_ASSERTION(!mRunningSample, "Registering content during sample.");
mAnimationElementTable.PutEntry(aAnimationElement);
if (mDeferredStartSampling) {
mDeferredStartSampling = PR_FALSE;
@ -207,7 +204,6 @@ void
nsSMILAnimationController::UnregisterAnimationElement(
nsISMILAnimationElement* aAnimationElement)
{
NS_ASSERTION(!mRunningSample, "Unregistering content during sample.");
mAnimationElementTable.RemoveEntry(aAnimationElement);
}
@ -362,16 +358,11 @@ nsSMILAnimationController::DoSample()
void
nsSMILAnimationController::DoSample(PRBool aSkipUnchangedContainers)
{
// Reset resample flag -- do this before flushing styles since flushing styles
// will also flush animation resample requests
mResampleNeeded = PR_FALSE;
mDocument->FlushPendingNotifications(Flush_Style);
#ifdef DEBUG
// Set running sample flag -- do this before flushing styles so that when we
// flush styles we don't end up requesting extra samples
mRunningSample = PR_TRUE;
#endif
// Reset resample flag again -- flushing styles may have set this flag but
// since we're about to do a sample now, reset it
mResampleNeeded = PR_FALSE;
mDocument->FlushPendingNotifications(Flush_Style);
// STEP 1: Bring model up to date
// (i) Rewind elements where necessary
@ -445,9 +436,7 @@ nsSMILAnimationController::DoSample(PRBool aSkipUnchangedContainers)
// when the inherited value is *also* being animated, we really should be
// traversing our animated nodes in an ancestors-first order (bug 501183)
currentCompositorTable->EnumerateEntries(DoComposeAttribute, nsnull);
#ifdef DEBUG
mRunningSample = PR_FALSE;
#endif
// Update last compositor table
mLastCompositorTable = currentCompositorTable.forget();

View File

@ -94,7 +94,12 @@ public:
// (A resample performs the same operations as a sample but doesn't advance
// the current time and doesn't check if the container is paused)
void Resample() { DoSample(PR_FALSE); }
void SetResampleNeeded() { mResampleNeeded = PR_TRUE; }
void SetResampleNeeded()
{
if (!mRunningSample) {
mResampleNeeded = PR_TRUE;
}
}
void FlushResampleRequests()
{
if (!mResampleNeeded)
@ -203,9 +208,7 @@ protected:
// record the time, set the following flag, and then wait until we have an
// animation element. Then we'll reset this flag and actually start sampling.
PRPackedBool mDeferredStartSampling;
#ifdef DEBUG
PRPackedBool mRunningSample;
#endif
// Store raw ptr to mDocument. It owns the controller, so controller
// shouldn't outlive it

View File

@ -246,7 +246,7 @@ nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
mSimpleDuration.IsIndefinite() || mLastValue,
"Unresolved simple duration for active or frozen animation");
nsSMILValue result(aResult.mType);
nsSMILValue result;
if (mSimpleDuration.IsIndefinite() ||
(values.Length() == 1 && TreatSingleValueAsStatic())) {
@ -402,15 +402,19 @@ nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
// NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail.
double intervalProgress = -1.f;
if (IsToAnimation()) {
from = &aBaseValue;
to = &aValues[0];
if (calcMode == CALC_PACED) {
// Note: key[Times/Splines/Points] are ignored for calcMode="paced"
intervalProgress = simpleProgress;
if (aBaseValue.IsNull()) {
rv = NS_ERROR_FAILURE;
} else {
double scaledSimpleProgress =
ScaleSimpleProgress(simpleProgress, calcMode);
intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
from = &aBaseValue;
to = &aValues[0];
if (calcMode == CALC_PACED) {
// Note: key[Times/Splines/Points] are ignored for calcMode="paced"
intervalProgress = simpleProgress;
} else {
double scaledSimpleProgress =
ScaleSimpleProgress(simpleProgress, calcMode);
intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
}
}
} else {
if (calcMode == CALC_PACED) {

View File

@ -201,7 +201,7 @@ public:
* @return True if the animation will replace, false if it will add or
* otherwise build on the passed in value.
*/
PRBool WillReplace() const;
virtual PRBool WillReplace() const;
/**
* Indicates if the parameters for this animation have changed since the last

View File

@ -102,18 +102,21 @@ nsSMILCSSProperty::GetBaseValue() const
// from ALL return points. This function must only return THIS variable:
nsSMILValue baseValue;
// SPECIAL CASE: Shorthands
if (nsCSSProps::IsShorthand(mPropID)) {
// SPECIAL CASE: (a) Shorthands
// (b) 'display'
if (nsCSSProps::IsShorthand(mPropID) || mPropID == eCSSProperty_display) {
// We can't look up the base (computed-style) value of shorthand
// properties, because they aren't guaranteed to have a consistent computed
// value. However, that's not a problem, because it turns out the caller
// isn't going to end up using the value we return anyway. Base values only
// get used when there's interpolation or addition, and the shorthand
// properties we know about don't support those operations. So, we can just
// return a dummy value (initialized with the right type, so as not to
// indicate failure).
nsSMILValue tmpVal(&nsSMILCSSValueType::sSingleton);
baseValue.Swap(tmpVal);
// properties because they aren't guaranteed to have a consistent computed
// value.
//
// Also, although we can look up the base value of the display property,
// doing so involves clearing and resetting the property which can cause
// frames to be recreated which we'd like to avoid.
//
// In either case, simply returning a null-typed nsSMILValue to indicate
// failure is acceptable because the caller only uses base values when
// there's interpolation or addition, and for both the shorthand properties
// we know about and the display property those operations aren't supported.
return baseValue;
}

View File

@ -108,10 +108,9 @@ nsSMILCompositor::ComposeAttribute()
PRUint32 firstFuncToCompose = GetFirstFuncToAffectSandwich();
// FOURTH: Get & cache base value
nsSMILValue sandwichResultValue = smilAttr->GetBaseValue();
if (sandwichResultValue.IsNull()) {
NS_WARNING("nsISMILAttr::GetBaseValue failed");
return;
nsSMILValue sandwichResultValue;
if (!mAnimationFunctions[firstFuncToCompose]->WillReplace()) {
sandwichResultValue = smilAttr->GetBaseValue();
}
UpdateCachedBaseValue(sandwichResultValue);
@ -124,6 +123,10 @@ nsSMILCompositor::ComposeAttribute()
for (PRUint32 i = firstFuncToCompose; i < length; ++i) {
mAnimationFunctions[i]->ComposeResult(*smilAttr, sandwichResultValue);
}
if (sandwichResultValue.IsNull()) {
smilAttr->ClearAnimValue();
return;
}
// SIXTH: Set the animated value to the final composited result.
nsresult rv = smilAttr->SetAnimValue(sandwichResultValue);

View File

@ -124,3 +124,9 @@ nsSMILSetAnimationFunction::GetAttr(nsIAtom* aAttName,
return nsSMILAnimationFunction::GetAttr(aAttName, aResult);
}
PRBool
nsSMILSetAnimationFunction::WillReplace() const
{
return PR_TRUE;
}

View File

@ -84,6 +84,7 @@ protected:
NS_OVERRIDE virtual const nsAttrValue* GetAttr(nsIAtom* aAttName) const;
NS_OVERRIDE virtual PRBool GetAttr(nsIAtom* aAttName,
nsAString& aResult) const;
NS_OVERRIDE virtual PRBool WillReplace() const;
PRBool IsDisallowedAttribute(const nsIAtom* aAttribute) const;
};

View File

@ -215,6 +215,14 @@ nsSMILTimeContainer::SetParent(nsSMILTimeContainer* aParent)
{
if (mParent) {
mParent->RemoveChild(*this);
// When we're not attached to a parent time container, GetParentTime() will
// return 0. We need to adjust our pause state information to be relative to
// this new time base.
// Note that since "current time = parent time - parent offset" setting the
// parent offset and pause start as follows preserves our current time even
// while parent time = 0.
mParentOffset = -mCurrentTime;
mPauseStart = 0L;
}
mParent = aParent;
@ -314,6 +322,7 @@ nsSMILTimeContainer::UpdateCurrentTime()
{
nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime();
mCurrentTime = now - mParentOffset;
NS_ABORT_IF_FALSE(mCurrentTime >= 0, "Container has negative time");
}
void

View File

@ -1104,6 +1104,10 @@ nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const
void
nsSMILTimedElement::BindToTree(nsIContent* aContextNode)
{
// Reset previously registered milestone since we may be registering with
// a different time container now.
mPrevRegisteredMilestone = sMaxMilestone;
// If we were already active then clear all our timing information and start
// afresh
if (mElementState != STATE_STARTUP) {
@ -1122,8 +1126,6 @@ nsSMILTimedElement::BindToTree(nsIContent* aContextNode)
mEndSpecs[j]->ResolveReferences(aContextNode);
}
// Register new milestone
mPrevRegisteredMilestone = sMaxMilestone;
RegisterMilestone();
}

View File

@ -113,12 +113,18 @@ const gValidPath = [
"m0 0", "M0, 0"
];
// paths must start with at least a valid "M" segment to be valid
const gInvalidPath = [
"m0 0 L30,,30",
"M20 20em",
"M20in 20",
"h30",
"L50 50",
"abc",
];
// paths that at least start with a valid "M" segment are valid - the spec says
// to parse everything up to the first invalid token
const gValidPathWithErrors = [
"M20 20em",
"m0 0 L30,,30",
"M10 10 L50 50 abc",
];

View File

@ -157,6 +157,7 @@ function main()
testAttr("path", gValidPath, true, false);
testAttr("path", gInvalidPath, false, false);
testAttr("path", gValidPathWithErrors, true, false);
testMpathElem(gValidPath, true, false);
testMpathElem(gInvalidPath, false, false);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,276 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
#ifndef MOZILLA_DOMSVGPATHSEG_H__
#define MOZILLA_DOMSVGPATHSEG_H__
#include "nsIDOMSVGPathSeg.h"
#include "DOMSVGPathSegList.h"
#include "SVGPathSegUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsAutoPtr.h"
class nsSVGElement;
// We make DOMSVGPathSeg a pseudo-interface to allow us to QI to it in order to
// check that the objects that scripts pass to DOMSVGPathSegList methods are
// our *native* path seg objects.
//
// {494A7566-DC26-40C8-9122-52ABD76870C4}
#define MOZILLA_DOMSVGPATHSEG_IID \
{ 0x494A7566, 0xDC26, 0x40C8, { 0x91, 0x22, 0x52, 0xAB, 0xD7, 0x68, 0x70, 0xC4 } }
namespace mozilla {
/**
* Class DOMSVGPathSeg
*
* This class is the base class of the classes that create the DOM objects that
* wrap the internal path segments that are encoded in an SVGPathData. Its
* sub-classes are also used to create the objects returned by
* SVGPathElement.createSVGPathSegXxx().
*
* See the architecture comment in DOMSVGPathSegList.h for an overview of the
* important points regarding these DOM wrapper structures.
*
* See the architecture comment in DOMSVGLength.h (yes, LENGTH) for an overview
* of the important points regarding how this specific class works.
*
* The main differences between this class and DOMSVGLength is that we have
* sub-classes (it does not), and the "internal counterpart" that we provide a
* DOM wrapper for is a list of floats, not an instance of an internal class.
*/
class DOMSVGPathSeg : public nsIDOMSVGPathSeg
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGPATHSEG_IID)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DOMSVGPathSeg)
NS_DECL_NSIDOMSVGPATHSEG
/**
* This convenient factory method creates instances of the correct sub-class.
*/
static DOMSVGPathSeg *CreateFor(DOMSVGPathSegList *aList,
PRUint32 aListIndex,
PRBool aIsAnimValItem);
/**
* Create an unowned copy of this object. The caller is responsible for the
* first AddRef()!
*/
virtual DOMSVGPathSeg* Clone() = 0;
PRBool IsInList() const {
return !!mList;
}
/**
* In future, if this class is used for non-list segments, this will be
* different to IsInList().
*/
PRBool HasOwner() const {
return !!mList;
}
/**
* This method is called to notify this DOM object that it is being inserted
* into a list, and give it the information it needs as a result.
*
* This object MUST NOT already belong to a list when this method is called.
* That's not to say that script can't move these DOM objects between
* lists - it can - it's just that the logic to handle that (and send out
* the necessary notifications) is located elsewhere (in DOMSVGPathSegList).)
*/
void InsertingIntoList(DOMSVGPathSegList *aList,
PRUint32 aListIndex,
PRBool aIsAnimValItem);
/// This method is called to notify this object that its list index changed.
void UpdateListIndex(PRUint8 aListIndex) {
mListIndex = aListIndex;
}
/**
* This method is called to notify this DOM object that it is about to be
* removed from its current DOM list so that it can first make a copy of its
* internal counterpart's values. (If it didn't do this, then it would
* "lose" its value on being removed.)
*/
void RemovingFromList();
/**
* This method converts the segment to a string of floats as found in
* SVGPathData (i.e. the first float contains the type of the segment,
* encoded into a float, followed by its arguments in the same order as they
* are given in the <path> element's 'd' attribute).
*/
void ToSVGPathSegEncodedData(float *aData);
/**
* The type of this path segment.
*/
virtual PRUint32 Type() const = 0;
protected:
/**
* Generic ctor for DOMSVGPathSeg objects that are created for an attribute.
*/
DOMSVGPathSeg(DOMSVGPathSegList *aList,
PRUint32 aListIndex,
PRBool aIsAnimValItem);
/**
* Ctor for creating the objects returned by
* SVGPathElement.createSVGPathSegXxx(), which do not initially belong to an
* attribute.
*/
DOMSVGPathSeg();
virtual ~DOMSVGPathSeg() {
// Our mList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mList is null.
if (mList) {
mList->ItemAt(mListIndex) = nsnull;
}
}
nsSVGElement* Element() {
return mList->Element();
}
/**
* Get a reference to the internal SVGPathSeg list item that this DOM wrapper
* object currently wraps.
*
* To simplify the code we just have this one method for obtaining both
* baseVal and animVal internal items. This means that animVal items don't
* get const protection, but then our setter methods guard against changing
* animVal items.
*/
float* InternalItem();
virtual float* PtrToMemberArgs() = 0;
#ifdef DEBUG
PRBool IndexIsValid();
#endif
nsRefPtr<DOMSVGPathSegList> mList;
// Bounds for the following are checked in the ctor, so be sure to update
// that if you change the capacity of any of the following.
PRUint32 mListIndex:31;
PRUint32 mIsAnimValItem:1; // PRUint32 because MSVC won't pack otherwise
};
NS_DEFINE_STATIC_IID_ACCESSOR(DOMSVGPathSeg, MOZILLA_DOMSVGPATHSEG_IID)
} // namespace mozilla
nsIDOMSVGPathSeg*
NS_NewSVGPathSegClosePath();
nsIDOMSVGPathSeg*
NS_NewSVGPathSegMovetoAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegMovetoRel(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoRel(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicAbs(float x, float y,
float x1, float y1,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicRel(float x, float y,
float x1, float y1,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticAbs(float x, float y,
float x1, float y1);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticRel(float x, float y,
float x1, float y1);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegArcAbs(float x, float y,
float r1, float r2, float angle,
PRBool largeArcFlag, PRBool sweepFlag);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegArcRel(float x, float y,
float r1, float r2, float angle,
PRBool largeArcFlag, PRBool sweepFlag);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoHorizontalAbs(float x);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoHorizontalRel(float x);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoVerticalAbs(float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoVerticalRel(float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicSmoothAbs(float x, float y,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicSmoothRel(float x, float y,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticSmoothRel(float x, float y);
#endif // MOZILLA_DOMSVGPATHSEG_H__

View File

@ -0,0 +1,489 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 "nsSVGElement.h"
#include "DOMSVGPathSegList.h"
#include "DOMSVGPathSeg.h"
#include "nsDOMError.h"
#include "SVGAnimatedPathSegList.h"
#include "nsCOMPtr.h"
#include "nsSVGAttrTearoffTable.h"
// See the comment in this file's header.
using namespace mozilla;
static nsSVGAttrTearoffTable<void, DOMSVGPathSegList>
sSVGPathSegListTearoffTable;
NS_SVG_VAL_IMPL_CYCLE_COLLECTION(DOMSVGPathSegList, mElement)
NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPathSegList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPathSegList)
DOMCI_DATA(SVGPathSegList, DOMSVGPathSegList)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPathSegList)
NS_INTERFACE_MAP_ENTRY(nsIDOMSVGPathSegList)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGPathSegList)
NS_INTERFACE_MAP_END
/* static */ already_AddRefed<DOMSVGPathSegList>
DOMSVGPathSegList::GetDOMWrapper(void *aList,
nsSVGElement *aElement,
PRBool aIsAnimValList)
{
DOMSVGPathSegList *wrapper =
sSVGPathSegListTearoffTable.GetTearoff(aList);
if (!wrapper) {
wrapper = new DOMSVGPathSegList(aElement, aIsAnimValList);
sSVGPathSegListTearoffTable.AddTearoff(aList, wrapper);
}
NS_ADDREF(wrapper);
return wrapper;
}
/* static */ DOMSVGPathSegList*
DOMSVGPathSegList::GetDOMWrapperIfExists(void *aList)
{
return sSVGPathSegListTearoffTable.GetTearoff(aList);
}
DOMSVGPathSegList::~DOMSVGPathSegList()
{
// We no longer have any list items, and there are no script references to
// us.
//
// Do NOT use InternalList() here! That's different!
void *key = mIsAnimValList ?
InternalAList().GetAnimValKey() :
InternalAList().GetBaseValKey();
sSVGPathSegListTearoffTable.RemoveTearoff(key);
}
void
DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue)
{
// When the number of items in our internal counterpart changes, we MUST stay
// in sync. Everything in the scary comment in
// DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as
// much, but we have the additional issue that failing to stay in sync would
// mean that - assuming we aren't reading bad memory - we would likely end up
// decoding command types from argument floats when looking in our
// SVGPathData's data array! Either way, we'll likely then go down
// NS_NOTREACHED code paths, or end up reading/setting more bad memory!!
// The only time that our other DOM list type implementations remove items is
// if those items become surplus items due to an attribute change or SMIL
// animation sample shortening the list. In general though, they try to keep
// their existing DOM items, even when things change. To be consistent, we'd
// really like to do the same thing. However, because different types of path
// segment correspond to different DOMSVGPathSeg subclasses, the type of
// items in our list are generally not the same, which makes this harder for
// us. We have to remove DOM segments if their type is not the same as the
// type of the new internal segment at their index.
//
// We also need to sync up mInternalDataIndex, but since we need to loop over
// all the items in the new list checking types anyway, that's almost
// insignificant in terms of overhead.
//
// Note that this method is called on every single SMIL animation resample
// and we have no way to short circuit the overhead since we don't have a
// way to tell if the call is due to a new animation, or a resample of an
// existing animation (when the number and type of items would be the same).
// (Note that a new animation could start overriding an existing animation at
// any time, so checking IsAnimating() wouldn't work.) Because we get called
// on every sample, it would not be acceptable alternative to throw away all
// our items and let them be recreated lazily, since that would break what
// script sees!
PRUint32 length = mItems.Length();
PRUint32 index = 0;
PRUint32 dataLength = aNewValue.mData.Length();
PRUint32 dataIndex = 0; // index into aNewValue's raw data array
PRUint32 newSegType;
while (index < length && dataIndex < dataLength) {
newSegType = SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]);
if (ItemAt(index) && ItemAt(index)->Type() != newSegType) {
ItemAt(index)->RemovingFromList();
ItemAt(index) = nsnull;
}
// Only after the RemovingFromList() can we touch mInternalDataIndex!
mItems[index].mInternalDataIndex = dataIndex;
++index;
dataIndex += 1 + SVGPathSegUtils::ArgCountForType(newSegType);
}
NS_ABORT_IF_FALSE((index == length && dataIndex <= dataLength) ||
(index <= length && dataIndex == dataLength),
"very bad - list corruption?");
if (index < length) {
// aNewValue has fewer items than our previous internal counterpart
PRUint32 newLength = index;
// Remove excess items from the list:
for (; index < length; ++index) {
if (ItemAt(index)) {
ItemAt(index)->RemovingFromList();
ItemAt(index) = nsnull;
}
}
// Only now may we truncate mItems
mItems.SetLength(newLength);
} else if (dataIndex < dataLength) {
// aNewValue has more items than our previous internal counterpart
// Sync mItems:
while (dataIndex < dataLength) {
if (!mItems.AppendElement(ItemProxy(nsnull, dataIndex))) {
// OOM
Clear();
return;
}
dataIndex += 1 + SVGPathSegUtils::ArgCountForType(SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]));
}
}
NS_ABORT_IF_FALSE(dataIndex == dataLength, "Serious processing error");
NS_ABORT_IF_FALSE(index == length, "Serious counting error");
}
PRBool
DOMSVGPathSegList::AttrIsAnimating() const
{
return const_cast<DOMSVGPathSegList*>(this)->InternalAList().IsAnimating();
}
SVGPathData&
DOMSVGPathSegList::InternalList()
{
SVGAnimatedPathSegList *alist = mElement->GetAnimPathSegList();
return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal : alist->mBaseVal;
}
SVGAnimatedPathSegList&
DOMSVGPathSegList::InternalAList()
{
NS_ABORT_IF_FALSE(mElement->GetAnimPathSegList(), "Internal error");
return *mElement->GetAnimPathSegList();
}
// ----------------------------------------------------------------------------
// nsIDOMSVGPathSegList implementation:
NS_IMETHODIMP
DOMSVGPathSegList::GetNumberOfItems(PRUint32 *aNumberOfItems)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
*aNumberOfItems = Length();
return NS_OK;
}
NS_IMETHODIMP
DOMSVGPathSegList::Clear()
{
if (IsAnimValList()) {
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
if (Length() > 0) {
// DOM list items that are to be removed must be removed before we change
// the internal list, otherwise they wouldn't be able to copy their
// internal counterparts' values!
InternalListWillChangeTo(SVGPathData()); // clears mItems
if (!AttrIsAnimating()) {
// The anim val list is in sync with the base val list
DOMSVGPathSegList *animList =
GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
if (animList) {
animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems
}
}
InternalList().Clear();
Element()->DidChangePathSegList(PR_TRUE);
#ifdef MOZ_SMIL
if (AttrIsAnimating()) {
Element()->AnimationNeedsResample();
}
#endif
}
return NS_OK;
}
NS_IMETHODIMP
DOMSVGPathSegList::Initialize(nsIDOMSVGPathSeg *aNewItem,
nsIDOMSVGPathSeg **_retval)
{
*_retval = nsnull;
if (IsAnimValList()) {
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
// If aNewItem is already in a list we should insert a clone of aNewItem,
// and for consistency, this should happen even if *this* is the list that
// aNewItem is currently in. Note that in the case of aNewItem being in this
// list, the Clear() call before the InsertItemBefore() call would remove it
// from this list, and so the InsertItemBefore() call would not insert a
// clone of aNewItem, it would actually insert aNewItem. To prevent that
// from happening we have to do the clone here, if necessary.
nsCOMPtr<DOMSVGPathSeg> domItem = do_QueryInterface(aNewItem);
if (!domItem) {
return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
}
if (domItem->HasOwner()) {
aNewItem = domItem->Clone();
}
Clear();
return InsertItemBefore(aNewItem, 0, _retval);
}
NS_IMETHODIMP
DOMSVGPathSegList::GetItem(PRUint32 aIndex,
nsIDOMSVGPathSeg **_retval)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
NS_ADDREF(*_retval = ItemAt(aIndex));
return NS_OK;
}
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
NS_IMETHODIMP
DOMSVGPathSegList::InsertItemBefore(nsIDOMSVGPathSeg *aNewItem,
PRUint32 aIndex,
nsIDOMSVGPathSeg **_retval)
{
*_retval = nsnull;
if (IsAnimValList()) {
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
nsCOMPtr<DOMSVGPathSeg> domItem = do_QueryInterface(aNewItem);
if (!domItem) {
return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
}
if (domItem->HasOwner()) {
domItem = domItem->Clone(); // must do this before changing anything!
}
PRUint32 internalIndex;
if (aIndex < Length()) {
internalIndex = mItems[aIndex].mInternalDataIndex;
} else {
aIndex = Length();
internalIndex = InternalList().mData.Length();
}
PRUint32 argCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
// Ensure we have enough memory so we can avoid complex error handling below:
if (!mItems.SetCapacity(mItems.Length() + 1) ||
!InternalList().mData.SetCapacity(InternalList().mData.Length() + 1 + argCount)) {
return NS_ERROR_OUT_OF_MEMORY;
}
float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
domItem->ToSVGPathSegEncodedData(segAsRaw);
InternalList().mData.InsertElementsAt(internalIndex, segAsRaw, 1 + argCount);
mItems.InsertElementAt(aIndex, ItemProxy(domItem.get(), internalIndex));
// This MUST come after the insertion into InternalList(), or else under the
// insertion into InternalList() the data read from domItem would be bad data
// from InternalList() itself!:
domItem->InsertingIntoList(this, aIndex, IsAnimValList());
for (PRUint32 i = aIndex + 1; i < Length(); ++i) {
mItems[i].mInternalDataIndex += 1 + argCount;
if (ItemAt(i)) {
ItemAt(i)->UpdateListIndex(i);
}
}
Element()->DidChangePathSegList(PR_TRUE);
#ifdef MOZ_SMIL
if (AttrIsAnimating()) {
Element()->AnimationNeedsResample();
}
#endif
*_retval = domItem.forget().get();
return NS_OK;
}
NS_IMETHODIMP
DOMSVGPathSegList::ReplaceItem(nsIDOMSVGPathSeg *aNewItem,
PRUint32 aIndex,
nsIDOMSVGPathSeg **_retval)
{
*_retval = nsnull;
if (IsAnimValList()) {
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
nsCOMPtr<DOMSVGPathSeg> domItem = do_QueryInterface(aNewItem);
if (!domItem) {
return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
}
if (aIndex >= Length()) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
if (domItem->HasOwner()) {
domItem = domItem->Clone(); // must do this before changing anything!
}
if (ItemAt(aIndex)) {
// Notify any existing DOM item of removal *before* modifying the lists so
// that the DOM item can copy the *old* value at its index:
ItemAt(aIndex)->RemovingFromList();
}
PRUint32 internalIndex = mItems[aIndex].mInternalDataIndex;
PRUint32 oldArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
PRUint32 newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
domItem->ToSVGPathSegEncodedData(segAsRaw);
PRBool ok = !!InternalList().mData.ReplaceElementsAt(
internalIndex, 1 + oldArgCount,
segAsRaw, 1 + newArgCount);
if (!ok) {
return NS_ERROR_OUT_OF_MEMORY;
}
ItemAt(aIndex) = domItem;
// This MUST come after the ToSVGPathSegEncodedData call otherwise that call
// would end up reading bad data from InternalList()!
domItem->InsertingIntoList(this, aIndex, IsAnimValList());
PRUint32 delta = newArgCount - oldArgCount;
if (delta != 0) {
for (PRUint32 i = aIndex + 1; i < Length(); ++i) {
mItems[i].mInternalDataIndex += delta;
}
}
Element()->DidChangePathSegList(PR_TRUE);
#ifdef MOZ_SMIL
if (AttrIsAnimating()) {
Element()->AnimationNeedsResample();
}
#endif
NS_ADDREF(*_retval = domItem.get());
return NS_OK;
}
NS_IMETHODIMP
DOMSVGPathSegList::RemoveItem(PRUint32 aIndex,
nsIDOMSVGPathSeg **_retval)
{
*_retval = nsnull;
if (IsAnimValList()) {
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
if (aIndex >= Length()) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
// We have to return the removed item, so make sure it exists:
EnsureItemAt(aIndex);
// Notify the DOM item of removal *before* modifying the lists so that the
// DOM item can copy its *old* value:
ItemAt(aIndex)->RemovingFromList();
NS_ADDREF(*_retval = ItemAt(aIndex));
PRUint32 internalIndex = mItems[aIndex].mInternalDataIndex;
PRUint32 segType = SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
PRUint32 argCount = SVGPathSegUtils::ArgCountForType(segType);
InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
mItems.RemoveElementAt(aIndex);
for (PRUint32 i = aIndex; i < Length(); ++i) {
mItems[i].mInternalDataIndex -= 1 + argCount;
if (ItemAt(i)) {
ItemAt(i)->UpdateListIndex(i);
}
}
Element()->DidChangePathSegList(PR_TRUE);
#ifdef MOZ_SMIL
if (AttrIsAnimating()) {
Element()->AnimationNeedsResample();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
DOMSVGPathSegList::AppendItem(nsIDOMSVGPathSeg *aNewItem,
nsIDOMSVGPathSeg **_retval)
{
return InsertItemBefore(aNewItem, Length(), _retval);
}
void
DOMSVGPathSegList::EnsureItemAt(PRUint32 aIndex)
{
if (!ItemAt(aIndex)) {
ItemAt(aIndex) = DOMSVGPathSeg::CreateFor(this, aIndex, IsAnimValList());
}
}

View File

@ -0,0 +1,235 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
#ifndef MOZILLA_DOMSVGPATHSEGLIST_H__
#define MOZILLA_DOMSVGPATHSEGLIST_H__
#include "nsIDOMSVGPathSegList.h"
#include "SVGPathData.h"
#include "SVGPathSegUtils.h"
#include "nsCOMArray.h"
#include "nsAutoPtr.h"
class nsSVGElement;
namespace mozilla {
class DOMSVGPathSeg;
class SVGAnimatedPathSegList;
/**
* Class DOMSVGPathSegList
*
* This class is used to create the DOM tearoff objects that wrap internal
* SVGPathData objects.
*
* See the architecture comment in DOMSVGAnimatedLengthList.h first (that's
* LENGTH list), then continue reading the remainder of this comment.
*
* The architecture of this class is very similar to that of DOMSVGLengthList
* except that, since there is no nsIDOMSVGAnimatedPathSegList interface
* in SVG, we have no parent DOMSVGAnimatedPathSegList (unlike DOMSVGLengthList
* which has a parent DOMSVGAnimatedLengthList class). (There is an
* SVGAnimatedPathData interface, but that is quite different to
* DOMSVGAnimatedLengthList, since it is inherited by elements rather than
* elements having members of that type.) As a consequence, much of the logic
* that would otherwise be in DOMSVGAnimatedPathSegList (and is in
* DOMSVGAnimatedLengthList) is contained in this class.
*
* This class is strongly intertwined with DOMSVGPathSeg. Our DOMSVGPathSeg
* items are friends of us and responsible for nulling out our pointers to
* them when they die.
*
* Our DOM items are created lazily on demand as and when script requests them.
*/
class DOMSVGPathSegList : public nsIDOMSVGPathSegList
{
friend class DOMSVGPathSeg;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DOMSVGPathSegList)
NS_DECL_NSIDOMSVGPATHSEGLIST
/**
* Factory method to create and return a DOMSVGPathSegList wrapper
* for a given internal SVGPathData object. The factory takes care
* of caching the object that it returns so that the same object can be
* returned for the given SVGPathData each time it is requested.
* The cached object is only removed from the cache when it is destroyed due
* to there being no more references to it or to any of its descendant
* objects. If that happens, any subsequent call requesting the DOM wrapper
* for the SVGPathData will naturally result in a new
* DOMSVGPathSegList being returned.
*
* It's unfortunate that aList is a void* instead of a typed argument. This
* is because the mBaseVal and mAnimVal members of SVGAnimatedPathSegList are
* of different types - a plain SVGPathData, and a SVGPathData*. We
* use the addresses of these members as the key for the hash table, and
* clearly SVGPathData* and a SVGPathData** are not the same type.
*/
static already_AddRefed<DOMSVGPathSegList>
GetDOMWrapper(void *aList,
nsSVGElement *aElement,
PRBool aIsAnimValList);
/**
* This method returns the DOMSVGPathSegList wrapper for an internal
* SVGPathData object if it currently has a wrapper. If it does
* not, then nsnull is returned.
*/
static DOMSVGPathSegList*
GetDOMWrapperIfExists(void *aList);
/**
* This will normally be the same as InternalList().CountItems(), except if
* we've hit OOM, in which case our length will be zero.
*/
PRUint32 Length() const {
NS_ABORT_IF_FALSE(mItems.Length() == 0 ||
mItems.Length() ==
const_cast<DOMSVGPathSegList*>(this)->InternalList().CountItems(),
"DOM wrapper's list length is out of sync");
return mItems.Length();
}
/**
* WATCH OUT! If you add code to call this on a baseVal wrapper, then you
* must also call it on the animVal wrapper too if necessary!! See other
* callers!
*
* Called by internal code to notify us when we need to sync the length of
* this DOM list with its internal list. This is called immediately prior to
* the length of the internal list being changed so that any DOM list items
* that need to be removed from the DOM list can first copy their values from
* their internal counterpart.
*
* The only time this method could fail is on OOM when trying to increase the
* length of the DOM list. If that happens then this method simply clears the
* list and returns. Callers just proceed as normal, and we simply accept
* that the DOM list will be empty (until successfully set to a new value).
*/
void InternalListWillChangeTo(const SVGPathData& aNewValue);
/**
* Returns true if our attribute is animating (in which case our animVal is
* not simply a mirror of our baseVal).
*/
PRBool AttrIsAnimating() const;
private:
/**
* Only our static GetDOMWrapper() factory method may create objects of our
* type.
*/
DOMSVGPathSegList(nsSVGElement *aElement, PRBool aIsAnimValList)
: mElement(aElement)
, mIsAnimValList(aIsAnimValList)
{
// This call populates mItems with the same number of items as there are
// segments contained in the internal list. We ignore OOM failure since
// being out of sync is safe so long as we have *fewer* items than our
// internal list.
InternalListWillChangeTo(InternalList());
}
~DOMSVGPathSegList();
nsSVGElement* Element() {
return mElement.get();
}
/// Used to determine if this list is the baseVal or animVal list.
PRBool IsAnimValList() const {
return mIsAnimValList;
}
/**
* Get a reference to this object's corresponding internal SVGPathData.
*
* To simplify the code we just have this one method for obtaining both
* base val and anim val internal lists. This means that anim val lists don't
* get const protection, but our setter methods guard against changing
* anim val lists.
*/
SVGPathData& InternalList();
SVGAnimatedPathSegList& InternalAList();
/// Creates an instance of the appropriate DOMSVGPathSeg sub-class for
// aIndex, if it doesn't already exist.
void EnsureItemAt(PRUint32 aIndex);
DOMSVGPathSeg*& ItemAt(PRUint32 aIndex) {
return mItems[aIndex].mItem;
}
/**
* This struct is used in our array of mItems to provide us with somewhere to
* store the indexes into the internal SVGPathData of the internal seg data
* that our DOMSVGPathSeg items wrap (the internal segment data is or varying
* length, so we can't just use the index of our DOMSVGPathSeg items
* themselves). The reason that we have this separate struct rather than
* just storing the internal indexes in the DOMSVGPathSeg items is because we
* want to create the DOMSVGPathSeg items lazily on demand.
*/
struct ItemProxy {
ItemProxy(){}
ItemProxy(DOMSVGPathSeg *aItem, PRUint32 aInternalDataIndex)
: mItem(aItem)
, mInternalDataIndex(aInternalDataIndex)
{}
DOMSVGPathSeg *mItem;
PRUint32 mInternalDataIndex;
};
// Weak refs to our DOMSVGPathSeg items. The items are friends and take care
// of clearing our pointer to them when they die.
nsTArray<ItemProxy> mItems;
// Strong ref to our element to keep it alive. We hold this not only for
// ourself, but also for our DOMSVGPathSeg items too.
nsRefPtr<nsSVGElement> mElement;
PRPackedBool mIsAnimValList;
};
} // namespace mozilla
#endif // MOZILLA_DOMSVGPATHSEGLIST_H__

View File

@ -52,6 +52,8 @@ CPPSRCS = \
DOMSVGAnimatedLengthList.cpp \
DOMSVGLength.cpp \
DOMSVGLengthList.cpp \
DOMSVGPathSeg.cpp \
DOMSVGPathSegList.cpp \
nsDOMSVGZoomEvent.cpp \
nsDOMSVGEvent.cpp \
nsSVGAElement.cpp \
@ -90,8 +92,6 @@ CPPSRCS = \
nsSVGPathDataParser.cpp \
nsSVGPathElement.cpp \
nsSVGPathGeometryElement.cpp \
nsSVGPathSeg.cpp \
nsSVGPathSegList.cpp \
nsSVGPatternElement.cpp \
nsSVGPoint.cpp \
nsSVGPointList.cpp \
@ -123,8 +123,11 @@ CPPSRCS = \
nsSVGValue.cpp \
nsSVGViewBox.cpp \
SVGAnimatedLengthList.cpp \
SVGAnimatedPathSegList.cpp \
SVGLength.cpp \
SVGLengthList.cpp \
SVGPathData.cpp \
SVGPathSegUtils.cpp \
$(NULL)
ifdef MOZ_SMIL
@ -142,6 +145,7 @@ CPPSRCS += nsSVGAnimateElement.cpp \
SVGMotionSMILAnimationFunction.cpp \
SVGMotionSMILPathUtils.cpp \
SVGOrientSMILType.cpp \
SVGPathSegListSMILType.cpp \
SVGViewBoxSMILType.cpp \
$(NULL)
endif

View File

@ -0,0 +1,240 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 "SVGAnimatedPathSegList.h"
#include "DOMSVGPathSegList.h"
#include "nsSVGElement.h"
#include "nsSVGAttrTearoffTable.h"
#ifdef MOZ_SMIL
#include "nsSMILValue.h"
#include "SVGPathSegListSMILType.h"
#endif // MOZ_SMIL
// See the comments in this file's header!
using namespace mozilla;
nsresult
SVGAnimatedPathSegList::SetBaseValueString(const nsAString& aValue)
{
SVGPathData newBaseValue;
// The spec says that the path data is parsed and accepted up to the first
// error encountered, so we don't return early if an error occurs. However,
// we do want to throw any error code from setAttribute if there's a problem.
nsresult rv = newBaseValue.SetValueFromString(aValue);
// We must send these notifications *before* changing mBaseVal! Our baseVal's
// DOM wrapper list may have to remove DOM items from itself, and any removed
// DOM items need to copy their internal counterpart's values *before* we
// change them. See the comments in
// DOMSVGPathSegList::InternalListWillChangeTo().
DOMSVGPathSegList *baseValWrapper =
DOMSVGPathSegList::GetDOMWrapperIfExists(GetBaseValKey());
if (baseValWrapper) {
baseValWrapper->InternalListWillChangeTo(newBaseValue);
}
DOMSVGPathSegList *animValWrapper;
if (!IsAnimating()) { // DOM anim val wraps our base val too!
animValWrapper = DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey());
if (animValWrapper) {
animValWrapper->InternalListWillChangeTo(newBaseValue);
}
}
// Only now may we modify mBaseVal!
// We don't need to call DidChange* here - we're only called by
// nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
// which takes care of notifying.
nsresult rv2 = mBaseVal.CopyFrom(newBaseValue);
if (NS_FAILED(rv2)) {
// Attempting to increase mBaseVal's length failed (mBaseVal is left
// unmodified). We MUST keep any DOM wrappers in sync:
if (baseValWrapper) {
baseValWrapper->InternalListWillChangeTo(mBaseVal);
}
if (animValWrapper) {
animValWrapper->InternalListWillChangeTo(mBaseVal);
}
return rv2;
}
return rv;
}
void
SVGAnimatedPathSegList::ClearBaseValue()
{
// We must send these notifications *before* changing mBaseVal! (See above.)
DOMSVGPathSegList *baseValWrapper =
DOMSVGPathSegList::GetDOMWrapperIfExists(GetBaseValKey());
if (baseValWrapper) {
baseValWrapper->InternalListWillChangeTo(SVGPathData());
}
if (!IsAnimating()) { // DOM anim val wraps our base val too!
DOMSVGPathSegList *animValWrapper =
DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey());
if (animValWrapper) {
animValWrapper->InternalListWillChangeTo(SVGPathData());
}
}
mBaseVal.Clear();
// Caller notifies
}
nsresult
SVGAnimatedPathSegList::SetAnimValue(const SVGPathData& aNewAnimValue,
nsSVGElement *aElement)
{
// Note that a new animation may totally change the number of items in the
// animVal list, either replacing what was essentially a mirror of the
// baseVal list, or else replacing and overriding an existing animation.
// Unfortunately it is not possible for us to reliably distinguish between
// calls to this method that are setting a new sample for an existing
// animation, and calls that are setting the first sample of an animation
// that will override an existing animation. In the case of DOMSVGPathSegList
// the InternalListWillChangeTo method is not virtually free as it is for the
// other DOM list classes, so this is a shame. We'd quite like to be able to
// skip the call if possible.
// We must send these notifications *before* changing mAnimVal! (See above.)
DOMSVGPathSegList *domWrapper =
DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey());
if (domWrapper) {
domWrapper->InternalListWillChangeTo(aNewAnimValue);
}
if (!mAnimVal) {
mAnimVal = new SVGPathData();
}
nsresult rv = mAnimVal->CopyFrom(aNewAnimValue);
if (NS_FAILED(rv)) {
// OOM. We clear the animation and, importantly, ClearAnimValue() ensures
// that mAnimVal's DOM wrapper (if any) is kept in sync!
ClearAnimValue(aElement);
}
aElement->DidAnimatePathSegList();
return rv;
}
void
SVGAnimatedPathSegList::ClearAnimValue(nsSVGElement *aElement)
{
// We must send these notifications *before* changing mAnimVal! (See above.)
DOMSVGPathSegList *domWrapper =
DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey());
if (domWrapper) {
// When all animation ends, animVal simply mirrors baseVal, which may have
// a different number of items to the last active animated value.
//
domWrapper->InternalListWillChangeTo(mBaseVal);
}
mAnimVal = nsnull;
aElement->DidAnimatePathSegList();
}
#ifdef MOZ_SMIL
nsISMILAttr*
SVGAnimatedPathSegList::ToSMILAttr(nsSVGElement *aElement)
{
return new SMILAnimatedPathSegList(this, aElement);
}
nsresult
SVGAnimatedPathSegList::
SMILAnimatedPathSegList::ValueFromString(const nsAString& aStr,
const nsISMILAnimationElement* /*aSrcElement*/,
nsSMILValue& aValue,
PRBool& aPreventCachingOfSandwich) const
{
nsSMILValue val(&SVGPathSegListSMILType::sSingleton);
SVGPathDataAndOwner *list = static_cast<SVGPathDataAndOwner*>(val.mU.mPtr);
nsresult rv = list->SetValueFromString(aStr);
if (NS_SUCCEEDED(rv)) {
list->SetElement(mElement);
aValue.Swap(val);
}
aPreventCachingOfSandwich = PR_FALSE;
return rv;
}
nsSMILValue
SVGAnimatedPathSegList::SMILAnimatedPathSegList::GetBaseValue() const
{
// To benefit from Return Value Optimization and avoid copy constructor calls
// due to our use of return-by-value, we must return the exact same object
// from ALL return points. This function must only return THIS variable:
nsSMILValue val;
nsSMILValue tmp(&SVGPathSegListSMILType::sSingleton);
SVGPathDataAndOwner *list = static_cast<SVGPathDataAndOwner*>(tmp.mU.mPtr);
nsresult rv = list->CopyFrom(mVal->mBaseVal);
if (NS_SUCCEEDED(rv)) {
list->SetElement(mElement);
val.Swap(tmp);
}
return val;
}
nsresult
SVGAnimatedPathSegList::SMILAnimatedPathSegList::SetAnimValue(const nsSMILValue& aValue)
{
NS_ASSERTION(aValue.mType == &SVGPathSegListSMILType::sSingleton,
"Unexpected type to assign animated value");
if (aValue.mType == &SVGPathSegListSMILType::sSingleton) {
mVal->SetAnimValue(*static_cast<SVGPathDataAndOwner*>(aValue.mU.mPtr),
mElement);
}
return NS_OK;
}
void
SVGAnimatedPathSegList::SMILAnimatedPathSegList::ClearAnimValue()
{
if (mVal->mAnimVal) {
mVal->ClearAnimValue(mElement);
}
}
#endif // MOZ_SMIL

View File

@ -0,0 +1,160 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
#ifndef MOZILLA_SVGANIMATEDPATHSEGLIST_H__
#define MOZILLA_SVGANIMATEDPATHSEGLIST_H__
#include "SVGPathData.h"
class nsSVGElement;
#ifdef MOZ_SMIL
#include "nsISMILAttr.h"
#endif // MOZ_SMIL
namespace mozilla {
/**
* Class SVGAnimatedPathSegList
*
* Despite the fact that no SVGAnimatedPathSegList interface or objects exist
* in the SVG specification (unlike e.g. SVGAnimated*Length*List), we
* nevertheless have this internal class. (Note that there is an
* SVGAnimatedPathData interface, but that's quite different to
* SVGAnimatedLengthList since it is inherited by elements, as opposed to
* elements having members of that type.) The reason that we have this class is
* to provide a single locked down point of entry to the SVGPathData objects,
* which helps ensure that the DOM wrappers for SVGPathData objects' are always
* kept in sync. This is vitally important (see the comment in
* DOMSVGPathSegList::InternalListWillChangeTo) and frees consumers from having
* to know or worry about wrappers (or forget about them!) for the most part.
*/
class SVGAnimatedPathSegList
{
// friends so that they can get write access to mBaseVal and mAnimVal
friend class DOMSVGPathSeg;
friend class DOMSVGPathSegList;
public:
SVGAnimatedPathSegList() {}
/**
* Because it's so important that mBaseVal and its DOMSVGPathSegList wrapper
* (if any) be kept in sync (see the comment in
* DOMSVGPathSegList::InternalListWillChangeTo), this method returns a const
* reference. Only our friend classes may get mutable references to mBaseVal.
*/
const SVGPathData& GetBaseValue() const {
return mBaseVal;
}
nsresult SetBaseValueString(const nsAString& aValue);
void ClearBaseValue();
/**
* const! See comment for GetBaseValue!
*/
const SVGPathData& GetAnimValue() const {
return mAnimVal ? *mAnimVal : mBaseVal;
}
nsresult SetAnimValue(const SVGPathData& aValue,
nsSVGElement *aElement);
void ClearAnimValue(nsSVGElement *aElement);
/**
* Needed for correct DOM wrapper construction since GetAnimValue may
* actually return the baseVal!
*/
void *GetBaseValKey() const {
return (void*)&mBaseVal;
}
void *GetAnimValKey() const {
return (void*)&mAnimVal;
}
PRBool IsAnimating() const {
return !!mAnimVal;
}
#ifdef MOZ_SMIL
/// Callers own the returned nsISMILAttr
nsISMILAttr* ToSMILAttr(nsSVGElement* aElement);
#endif // MOZ_SMIL
private:
// mAnimVal is a pointer to allow us to determine if we're being animated or
// not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check
// if we're animating is not an option, since that would break animation *to*
// the empty string (<set to="">).
SVGPathData mBaseVal;
nsAutoPtr<SVGPathData> mAnimVal;
#ifdef MOZ_SMIL
struct SMILAnimatedPathSegList : public nsISMILAttr
{
public:
SMILAnimatedPathSegList(SVGAnimatedPathSegList* aVal,
nsSVGElement* aElement)
: mVal(aVal)
, mElement(aElement)
{}
// These will stay alive because a nsISMILAttr only lives as long
// as the Compositing step, and DOM elements don't get a chance to
// die during that.
SVGAnimatedPathSegList *mVal;
nsSVGElement *mElement;
// nsISMILAttr methods
virtual nsresult ValueFromString(const nsAString& aStr,
const nsISMILAnimationElement* aSrcElement,
nsSMILValue& aValue,
PRBool& aPreventCachingOfSandwich) const;
virtual nsSMILValue GetBaseValue() const;
virtual void ClearAnimValue();
virtual nsresult SetAnimValue(const nsSMILValue& aValue);
};
#endif // MOZ_SMIL
};
} // namespace mozilla
#endif // MOZILLA_SVGANIMATEDPATHSEGLIST_H__

View File

@ -41,7 +41,6 @@
#include "SVGMotionSMILType.h"
#include "SVGMotionSMILPathUtils.h"
#include "nsSVGPathDataParser.h"
#include "nsSVGPathSeg.h"
#include "nsSVGPathElement.h" // for nsSVGPathList
#include "nsSVGMpathElement.h"
@ -253,13 +252,15 @@ SVGMotionSMILAnimationFunction::
// Use the path that's the target of our chosen <mpath> child.
nsSVGPathElement* pathElem = aMpathElem->GetReferencedPath();
if (pathElem) {
const nsAttrValue* value = pathElem->GetParsedAttr(nsGkAtoms::d);
if (value) {
const nsAString& pathSpec = value->GetStringValue();
nsresult rv = SetPathVerticesFromPathString(pathSpec);
if (NS_SUCCEEDED(rv)) {
const SVGPathData &path = pathElem->GetAnimPathSegList()->GetAnimValue();
// Path data must contain of at least one path segment (if the path data
// doesn't begin with a valid "M", then it's invalid).
if (path.Length()) {
PRBool ok =
path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
if (ok && mPathVertices.Length()) {
mPath = pathElem->GetFlattenedPath(
pathElem->PrependLocalTransformTo(gfxMatrix()));
pathElem->PrependLocalTransformTo(gfxMatrix()));
}
}
}
@ -272,65 +273,25 @@ SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr()
mPathSourceType = ePathSourceType_PathAttr;
// Generate gfxFlattenedPath from |path| attr
nsresult rv;
nsSVGPathList pathData;
nsSVGPathDataParserToInternal pathParser(&pathData);
rv = pathParser.Parse(pathSpec);
if (NS_FAILED(rv)) {
// Parse error.
SVGPathData path;
nsSVGPathDataParserToInternal pathParser(&path);
// We ignore any failure returned from Parse() since the SVG spec says to
// accept all segments up to the first invalid token. Instead we must
// explicitly check that the parse produces at least one path segment (if
// the path data doesn't begin with a valid "M", then it's invalid).
pathParser.Parse(pathSpec);
if (!path.Length()) {
return;
}
mPath = pathData.GetFlattenedPath(gfxMatrix());
// Generate list of vertices from |path| attr
rv = SetPathVerticesFromPathString(pathSpec);
if (NS_FAILED(rv)) {
// The first parser liked our string, but the second did not. (unexpected,
// but possible depending on parser implementations.) Clear path so we
// completely (instead of partially) fail.
mPath = path.ToFlattenedPath(gfxMatrix());
PRBool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
if (!ok || !mPathVertices.Length()) {
mPath = nsnull;
NS_WARNING("nsSVGPathDataParserToInternal successfully parsed path string, but "
"nsSVGPathDataParserToDOM did not");
}
}
nsresult
SVGMotionSMILAnimationFunction::SetPathVerticesFromPathString(const nsAString& aPathSpec)
{
// Parse the string to an array of path segments.
nsCOMArray<nsIDOMSVGPathSeg> pathSegments;
nsSVGPathDataParserToDOM segmentParser(&pathSegments);
nsresult rv = segmentParser.Parse(aPathSpec);
if (NS_FAILED(rv)) {
return rv;
}
// Iterate across the parsed segments to populate our mPathVertices array.
PRUint32 numSegments = pathSegments.Count();
nsSVGPathSegTraversalState ts;
double runningDistTotal = 0.0;
for (PRUint32 i = 0; i < numSegments; ++i) {
nsSVGPathSeg* segment = static_cast<nsSVGPathSeg*>(pathSegments[i]);
PRUint16 type = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
segment->GetPathSegType(&type);
if (i == 0 ||
(type != nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS &&
type != nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL)) {
// Increment running length total (note that MoveTo's have 0 length)
runningDistTotal += segment->GetLength(&ts);
// Add an entry for the current point.
if (!mPathVertices.AppendElement(runningDistTotal)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
}
return NS_OK;
}
// Helper to regenerate our path representation & its list of vertices
void
SVGMotionSMILAnimationFunction::

View File

@ -95,7 +95,6 @@ protected:
// Helpers for GetValues
void MarkStaleIfAttributeAffectsPath(nsIAtom* aAttribute);
nsresult SetPathVerticesFromPathString(const nsAString& aPathSpec);
void RebuildPathAndVertices(const nsIContent* aContextElem);
void RebuildPathAndVerticesFromMpathElem(nsSVGMpathElement* aMpathElem);
void RebuildPathAndVerticesFromPathAttr();

View File

@ -44,7 +44,6 @@
#include "nsSVGAngle.h"
#include "nsIDOMSVGAngle.h"
#include "nsSVGPathElement.h"
#include "nsSVGPathSeg.h"
#include "nsIDOMSVGPathSeg.h"
#include "nsIDOMSVGPathSegList.h"
#include "nsMathUtils.h"

View File

@ -0,0 +1,750 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 "SVGPathData.h"
#include "SVGAnimatedPathSegList.h"
#include "SVGPathSegUtils.h"
#include "nsSVGElement.h"
#include "nsISVGValueUtils.h"
#include "nsDOMError.h"
#include "nsContentUtils.h"
#include "nsString.h"
#include "nsSVGUtils.h"
#include "string.h"
#include "nsSVGPathDataParser.h"
#include "nsSVGPathGeometryElement.h" // for nsSVGMark
#include "gfxPlatform.h"
#include <stdarg.h>
using namespace mozilla;
nsresult
SVGPathData::CopyFrom(const SVGPathData& rhs)
{
if (!mData.SetCapacity(rhs.mData.Length())) {
// Yes, we do want fallible alloc here
return NS_ERROR_OUT_OF_MEMORY;
}
mData = rhs.mData;
return NS_OK;
}
void
SVGPathData::GetValueAsString(nsAString& aValue) const
{
// we need this function in DidChangePathSegList
aValue.Truncate();
if (!Length()) {
return;
}
PRUint32 i = 0;
for (;;) {
nsAutoString segAsString;
SVGPathSegUtils::GetValueAsString(&mData[i], segAsString);
// We ignore OOM, since it's not useful for us to return an error.
aValue.Append(segAsString);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
if (i >= mData.Length()) {
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return;
}
aValue.Append(' ');
}
}
nsresult
SVGPathData::SetValueFromString(const nsAString& aValue)
{
// We don't use a temp variable since the spec says to parse everything up to
// the first error. We still return any error though so that callers know if
// there's a problem.
nsSVGPathDataParserToInternal pathParser(this);
return pathParser.Parse(aValue);
}
nsresult
SVGPathData::AppendSeg(PRUint32 aType, ...)
{
PRUint32 oldLength = mData.Length();
PRUint32 newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
if (!mData.SetLength(newLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
va_list args;
va_start(args, aType);
for (PRUint32 i = oldLength + 1; i < newLength; ++i) {
// NOTE! 'float' is promoted to 'double' when passed through '...'!
mData[i] = float(va_arg(args, double));
}
va_end(args);
return NS_OK;
}
float
SVGPathData::GetPathLength() const
{
float length = 0.0;
SVGPathTraversalState state;
PRUint32 i = 0;
while (i < mData.Length()) {
length += SVGPathSegUtils::GetLength(&mData[i], state);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return length;
}
#ifdef DEBUG
PRUint32
SVGPathData::CountItems() const
{
PRUint32 i = 0, count = 0;
while (i < mData.Length()) {
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
count++;
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return count;
}
#endif
PRBool
SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
{
aLengths->Clear();
SVGPathTraversalState state;
PRUint32 i = 0;
while (i < mData.Length()) {
if (!aLengths->AppendElement(SVGPathSegUtils::GetLength(&mData[i], state))) {
aLengths->Clear();
return PR_FALSE;
}
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return PR_TRUE;
}
PRBool
SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOutput) const
{
double distRunningTotal = 0.0;
SVGPathTraversalState state;
aOutput->Clear();
PRUint32 i = 0;
while (i < mData.Length()) {
PRUint32 segType = SVGPathSegUtils::DecodeType(mData[i]);
// We skip all moveto commands except an initial moveto. See the text 'A
// "move to" command does not count as an additional point when dividing up
// the duration...':
//
// http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
//
// This is important in the non-default case of calcMode="linear". In
// this case an equal amount of time is spent on each path segment,
// except on moveto segments which are jumped over immediately.
if (i == 0 || (segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS &&
segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL)) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (!aOutput->AppendElement(distRunningTotal)) {
return PR_FALSE;
}
}
i += 1 + SVGPathSegUtils::ArgCountForType(segType);
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt?");
return PR_TRUE;
}
PRUint32
SVGPathData::GetPathSegAtLength(float aDistance) const
{
// TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or
// 'aDistance' > the length of the path, or the seg list is empty.
// Return -1? Throwing would better help authors avoid tricky bugs (DOM
// could do that if we return -1).
double distRunningTotal = 0.0;
PRUint32 i = 0, segIndex = 0;
SVGPathTraversalState state;
while (i < mData.Length()) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (distRunningTotal >= aDistance) {
return segIndex;
}
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
segIndex++;
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return NS_MAX(0U, segIndex - 1); // -1 because while loop takes us 1 too far
}
void
SVGPathData::ConstructPath(gfxContext *aCtx) const
{
PRUint32 segType, prevSegType = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
gfxPoint pathStart(0.0, 0.0); // start point of [sub]path
gfxPoint segEnd(0.0, 0.0); // end point of previous/current segment
gfxPoint cp1, cp2; // previous bezier's control points
gfxPoint tcp1, tcp2; // temporaries
// Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
// then cp2 is its second control point. If the previous segment was a
// quadratic curve, then cp1 is its (only) control point.
PRUint32 i = 0;
while (i < mData.Length()) {
segType = SVGPathSegUtils::DecodeType(mData[i++]);
switch (segType)
{
case nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH:
segEnd = pathStart;
aCtx->ClosePath();
break;
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS:
pathStart = segEnd = gfxPoint(mData[i], mData[i+1]);
aCtx->MoveTo(segEnd);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL:
pathStart = segEnd += gfxPoint(mData[i], mData[i+1]);
aCtx->MoveTo(segEnd);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS:
segEnd = gfxPoint(mData[i], mData[i+1]);
aCtx->LineTo(segEnd);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL:
segEnd += gfxPoint(mData[i], mData[i+1]);
aCtx->LineTo(segEnd);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS:
cp1 = gfxPoint(mData[i], mData[i+1]);
cp2 = gfxPoint(mData[i+2], mData[i+3]);
segEnd = gfxPoint(mData[i+4], mData[i+5]);
aCtx->CurveTo(cp1, cp2, segEnd);
i += 6;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL:
cp1 = segEnd + gfxPoint(mData[i], mData[i+1]);
cp2 = segEnd + gfxPoint(mData[i+2], mData[i+3]);
segEnd += gfxPoint(mData[i+4], mData[i+5]);
aCtx->CurveTo(cp1, cp2, segEnd);
i += 6;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS:
cp1 = gfxPoint(mData[i], mData[i+1]);
// Convert quadratic curve to cubic curve:
tcp1 = segEnd + (cp1 - segEnd) * 2 / 3;
segEnd = gfxPoint(mData[i+2], mData[i+3]); // changed before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
aCtx->CurveTo(tcp1, tcp2, segEnd);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL:
cp1 = segEnd + gfxPoint(mData[i], mData[i+1]);
// Convert quadratic curve to cubic curve:
tcp1 = segEnd + (cp1 - segEnd) * 2 / 3;
segEnd += gfxPoint(mData[i+2], mData[i+3]); // changed before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
aCtx->CurveTo(tcp1, tcp2, segEnd);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_ARC_REL:
{
gfxPoint radii(mData[i], mData[i+1]);
gfxPoint start = segEnd;
gfxPoint end = gfxPoint(mData[i+5], mData[i+6]);
if (segType == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) {
end += start;
}
segEnd = end;
if (start != end) {
if (radii.x == 0.0f || radii.y == 0.0f) {
aCtx->LineTo(end);
i += 7;
break;
}
nsSVGArcConverter converter(start, end, radii, mData[i+2],
mData[i+3] != 0, mData[i+4] != 0);
while (converter.GetNextSegment(&cp1, &cp2, &end)) {
aCtx->CurveTo(cp1, cp2, end);
}
}
i += 7;
break;
}
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS:
segEnd = gfxPoint(mData[i++], segEnd.y);
aCtx->LineTo(segEnd);
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL:
segEnd += gfxPoint(mData[i++], 0.0f);
aCtx->LineTo(segEnd);
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS:
segEnd = gfxPoint(segEnd.x, mData[i++]);
aCtx->LineTo(segEnd);
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL:
segEnd += gfxPoint(0.0f, mData[i++]);
aCtx->LineTo(segEnd);
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segEnd * 2 - cp2 : segEnd;
cp2 = gfxPoint(mData[i], mData[i+1]);
segEnd = gfxPoint(mData[i+2], mData[i+3]);
aCtx->CurveTo(cp1, cp2, segEnd);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segEnd * 2 - cp2 : segEnd;
cp2 = segEnd + gfxPoint(mData[i], mData[i+1]);
segEnd += gfxPoint(mData[i+2], mData[i+3]);
aCtx->CurveTo(cp1, cp2, segEnd);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segEnd * 2 - cp1 : segEnd;
// Convert quadratic curve to cubic curve:
tcp1 = segEnd + (cp1 - segEnd) * 2 / 3;
segEnd = gfxPoint(mData[i], mData[i+1]); // changed before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
aCtx->CurveTo(tcp1, tcp2, segEnd);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segEnd * 2 - cp1 : segEnd;
// Convert quadratic curve to cubic curve:
tcp1 = segEnd + (cp1 - segEnd) * 2 / 3;
segEnd = segEnd + gfxPoint(mData[i], mData[i+1]); // changed before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
aCtx->CurveTo(tcp1, tcp2, segEnd);
i += 2;
break;
default:
NS_NOTREACHED("Bad path segment type");
return; // according to spec we'd use everything up to the bad seg anyway
}
prevSegType = segType;
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
}
already_AddRefed<gfxFlattenedPath>
SVGPathData::ToFlattenedPath(const gfxMatrix& aMatrix) const
{
nsRefPtr<gfxContext> ctx =
new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
ctx->SetMatrix(aMatrix);
ConstructPath(ctx);
ctx->IdentityMatrix();
return ctx->GetFlattenedPath();
}
static PRBool IsMoveto(PRUint16 aSegType)
{
return aSegType == nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS ||
aSegType == nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL;
}
static float AngleOfVector(gfxPoint v)
{
// C99 says about atan2 "A domain error may occur if both arguments are
// zero" and "On a domain error, the function returns an implementation-
// defined value". In the case of atan2 the implementation-defined value
// seems to commonly be zero, but it could just as easily be a NaN value.
// We specifically want zero in this case, hence the check:
return (v != gfxPoint(0.0f, 0.0f)) ? atan2(v.y, v.x) : 0.0f;
}
// TODO replace callers with calls to AngleOfVector
static double
CalcVectorAngle(double ux, double uy, double vx, double vy)
{
double ta = atan2(uy, ux);
double tb = atan2(vy, vx);
if (tb >= ta)
return tb-ta;
return 2 * M_PI - (ta-tb);
}
void
SVGPathData::GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const
{
// This code should assume that ANY type of segment can appear at ANY index.
// It should also assume that segments such as M and Z can appear in weird
// places, and repeat multiple times consecutively.
gfxPoint pathStart, segStart, segEnd;
gfxPoint cp1, cp2; // control points for current bezier curve
gfxPoint prevCP; // last control point of previous bezier curve
PRUint16 segType, prevSegType = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
// info on the current [sub]path (reset every M command):
gfxPoint pathStartPoint(0, 0);
float pathStartAngle = 0;
float prevSegEndAngle = 0, segStartAngle = 0, segEndAngle = 0;
PRUint32 i = 0;
while (i < mData.Length()) {
segType = SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args
switch (segType) // to find segStartAngle, segEnd and segEndAngle
{
case nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH:
segEnd = pathStart;
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
break;
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS) {
segEnd = gfxPoint(mData[i], mData[i+1]);
} else {
segEnd = segStart + gfxPoint(mData[i], mData[i+1]);
}
pathStart = segEnd;
// If authors are going to specify multiple consecutive moveto commands
// with markers, me might as well make the angle do something useful:
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS) {
segEnd = gfxPoint(mData[i], mData[i+1]);
} else {
segEnd = segStart + gfxPoint(mData[i], mData[i+1]);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
i += 2;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS) {
cp1 = gfxPoint(mData[i], mData[i+1]);
cp2 = gfxPoint(mData[i+2], mData[i+3]);
segEnd = gfxPoint(mData[i+4], mData[i+5]);
} else {
cp1 = segStart + gfxPoint(mData[i], mData[i+1]);
cp2 = segStart + gfxPoint(mData[i+2], mData[i+3]);
segEnd = segStart + gfxPoint(mData[i+4], mData[i+5]);
}
prevCP = cp2;
if (cp1 == segStart) {
cp1 = cp2;
}
if (cp2 == segEnd) {
cp2 = cp1;
}
segStartAngle = AngleOfVector(cp1 - segStart);
segEndAngle = AngleOfVector(segEnd - cp2);
i += 6;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS) {
cp1 = gfxPoint(mData[i], mData[i+1]);
segEnd = gfxPoint(mData[i+2], mData[i+3]);
} else {
cp1 = segStart + gfxPoint(mData[i], mData[i+1]);
segEnd = segStart + gfxPoint(mData[i+2], mData[i+3]);
}
prevCP = cp1;
segStartAngle = AngleOfVector(cp1 - segStart);
segEndAngle = AngleOfVector(segEnd - cp1);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_ARC_REL:
{
float rx = mData[i];
float ry = mData[i+1];
float angle = mData[i+2];
PRBool largeArcFlag = mData[i+3] != 0.0f;
PRBool sweepFlag = mData[i+4] != 0.0f;
if (segType == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS) {
segEnd = gfxPoint(mData[i+5], mData[i+6]);
} else {
segEnd = segStart + gfxPoint(mData[i+5], mData[i+6]);
}
// See section F.6 of SVG 1.1 for details on what we're doing here:
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
if (segStart == segEnd) {
// F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
// then this is equivalent to omitting the elliptical arc segment
// entirely." We take that very literally here, not adding a mark, and
// not even setting any of the 'prev' variables so that it's as if this
// arc had never existed; note the difference this will make e.g. if
// the arc is proceeded by a bezier curve and followed by a "smooth"
// bezier curve of the same degree!
i += 7;
continue;
}
// Below we have funny interleaving of F.6.6 (Correction of out-of-range
// radii) and F.6.5 (Conversion from endpoint to center parameterization)
// which is designed to avoid some unnecessary calculations.
if (rx == 0.0 || ry == 0.0) {
// F.6.6 step 1 - straight line or coincidental points
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
i += 7;
break;
}
rx = fabs(rx); // F.6.6.1
ry = fabs(ry);
// F.6.5.1:
angle = angle * M_PI/180.0;
float x1p = cos(angle) * (segStart.x - segEnd.x) / 2.0
+ sin(angle) * (segStart.y - segEnd.y) / 2.0;
float y1p = -sin(angle) * (segStart.x - segEnd.x) / 2.0
+ cos(angle) *(segStart.y - segEnd.y) / 2.0;
// This is the root in F.6.5.2 and the numerator under that root:
float root;
float numerator = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p;
if (numerator < 0.0) {
// F.6.6 step 3 - |numerator < 0.0| is equivalent to the result of
// F.6.6.2 (lamedh) being greater than one. What we have here is radii
// that do not reach between segStart and segEnd, so we need to correct
// them.
float lamedh = 1.0 - numerator/(rx*rx*ry*ry); // equiv to eqn F.6.6.2
float s = sqrt(lamedh);
rx *= s; // F.6.6.3
ry *= s;
// rx and ry changed, so we have to recompute numerator
numerator = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p;
NS_ABORT_IF_FALSE(numerator >= 0,
"F.6.6.3 should prevent this. Will sqrt(-num)!");
}
root = sqrt(numerator/(rx*rx*y1p*y1p + ry*ry*x1p*x1p));
if (largeArcFlag == sweepFlag)
root = -root;
float cxp = root * rx * y1p / ry; // F.6.5.2
float cyp = -root * ry * x1p / rx;
float theta, delta;
theta = CalcVectorAngle(1.0, 0.0, (x1p-cxp)/rx, (y1p-cyp)/ry); // F.6.5.5
delta = CalcVectorAngle((x1p-cxp)/rx, (y1p-cyp)/ry,
(-x1p-cxp)/rx, (-y1p-cyp)/ry); // F.6.5.6
if (!sweepFlag && delta > 0)
delta -= 2.0 * M_PI;
else if (sweepFlag && delta < 0)
delta += 2.0 * M_PI;
float tx1, ty1, tx2, ty2;
tx1 = -cos(angle)*rx*sin(theta) - sin(angle)*ry*cos(theta);
ty1 = -sin(angle)*rx*sin(theta) + cos(angle)*ry*cos(theta);
tx2 = -cos(angle)*rx*sin(theta+delta) - sin(angle)*ry*cos(theta+delta);
ty2 = -sin(angle)*rx*sin(theta+delta) + cos(angle)*ry*cos(theta+delta);
if (delta < 0.0f) {
tx1 = -tx1;
ty1 = -ty1;
tx2 = -tx2;
ty2 = -ty2;
}
segStartAngle = atan2(ty1, tx1);
segEndAngle = atan2(ty2, tx2);
i += 7;
break;
}
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS) {
segEnd = gfxPoint(mData[i++], segStart.y);
} else {
segEnd = segStart + gfxPoint(mData[i++], 0.0f);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL:
if (segType == nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS) {
segEnd = gfxPoint(segStart.x, mData[i++]);
} else {
segEnd = segStart + gfxPoint(0.0f, mData[i++]);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd - segStart);
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - prevCP : segStart;
if (segType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) {
cp2 = gfxPoint(mData[i], mData[i+1]);
segEnd = gfxPoint(mData[i+2], mData[i+3]);
} else {
cp2 = segStart + gfxPoint(mData[i], mData[i+1]);
segEnd = segStart + gfxPoint(mData[i+2], mData[i+3]);
}
prevCP = cp2;
if (cp1 == segStart) {
cp1 = cp2;
}
if (cp2 == segEnd) {
cp2 = cp1;
}
segStartAngle = AngleOfVector(cp1 - segStart);
segEndAngle = AngleOfVector(segEnd - cp2);
i += 4;
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - prevCP : segStart;
if (segType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS) {
segEnd = gfxPoint(mData[i], mData[i+1]);
} else {
segEnd = segStart + gfxPoint(mData[i], mData[i+1]);
}
prevCP = cp1;
segStartAngle = AngleOfVector(cp1 - segStart);
segEndAngle = AngleOfVector(segEnd - cp1);
i += 2;
break;
default:
// Leave any existing marks in aMarks so we have a visual indication of
// when things went wrong.
NS_ABORT_IF_FALSE(PR_FALSE, "Unknown segment type - path corruption?");
return;
}
// Set the angle of the mark at the start of this segment:
if (aMarks->Length()) {
nsSVGMark &mark = aMarks->ElementAt(aMarks->Length() - 1);
if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
// start of new subpath
pathStartAngle = mark.angle = segStartAngle;
} else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
// end of a subpath
if (prevSegType != nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH)
mark.angle = prevSegEndAngle;
} else {
if (!(segType == nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH &&
prevSegType == nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH))
mark.angle = nsSVGUtils::AngleBisect(prevSegEndAngle, segStartAngle);
}
}
// Add the mark at the end of this segment, and set its position:
if (!aMarks->AppendElement(nsSVGMark(segEnd.x, segEnd.y, 0))) {
aMarks->Clear(); // OOM, so try to free some
return;
}
if (segType == nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH &&
prevSegType != nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH) {
aMarks->ElementAt(aMarks->Length() - 1).angle =
//aMarks->ElementAt(pathStartIndex).angle =
nsSVGUtils::AngleBisect(segEndAngle, pathStartAngle);
}
prevSegType = segType;
prevSegEndAngle = segEndAngle;
segStart = segEnd;
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
if (aMarks->Length() &&
prevSegType != nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH)
aMarks->ElementAt(aMarks->Length() - 1).angle = prevSegEndAngle;
}

View File

@ -0,0 +1,284 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
#ifndef MOZILLA_SVGPATHDATA_H__
#define MOZILLA_SVGPATHDATA_H__
#include "SVGPathSegUtils.h"
#include "nsTArray.h"
#include "nsSVGElement.h"
class gfxContext;
class gfxMatrix;
class gfxFlattenedPath;
class nsSVGPathDataParserToInternal;
struct nsSVGMark;
namespace mozilla {
/**
* ATTENTION! WARNING! WATCH OUT!!
*
* Consumers that modify objects of this type absolutely MUST keep the DOM
* wrappers for those lists (if any) in sync!! That's why this class is so
* locked down.
*
* The DOM wrapper class for this class is DOMSVGPathSegList.
*
* This class is not called |class SVGPathSegList| for one very good reason;
* this class does not provide a list of "SVGPathSeg" items, it provides an
* array of floats into which path segments are encoded. See the paragraphs
* that follow for why. Note that the Length() method returns the number of
* floats in our array, not the number of encoded segments, and the index
* operator indexes floats in the array, not segments. If this class were
* called SVGPathSegList the names of these methods would be very misleading.
*
* The reason this class is designed in this way is because there are many
* different types of path segment, each taking a different numbers of
* arguments. We want to store the segments in an nsTArray to avoid individual
* allocations for each item, but the different size of segments means we can't
* have one single segment type for the nsTArray (not without using a space
* wasteful union or something similar). Since the internal code does not need
* to index into the list (the DOM wrapper does, but it handles that itself)
* the obvious solution is to have the items in this class take up variable
* width and have the internal code iterate over these lists rather than index
* into them.
*
* Implementing indexing to segments with O(1) performance would require us to
* allocate and maintain a separate segment index table (keeping that table in
* sync when items are inserted or removed from the list). So long as the
* internal code doesn't require indexing to segments, we can avoid that
* overhead and additional complexity.
*
* Segment encoding: the first float in the encoding of a segment contains the
* segment's type. The segment's type is encoded to/decoded from this float
* using the static methods SVGPathSegUtils::EncodeType(PRUint32)/
* SVGPathSegUtils::DecodeType(float). If the path segment type in question
* takes any arguments then these follow the first float, and are in the same
* order as they are given in a <path> element's 'd' attribute (NOT in the
* order of the createSVGPathSegXxx() methods' arguments from the SVG DOM
* interface SVGPathElement, which are different...grr). Consumers can use
* SVGPathSegUtils::ArgCountForType(type) to determine how many arguments
* there are (if any), and thus where the current encoded segment ends, and
* where the next segment (if any) begins.
*/
class SVGPathData
{
friend class SVGAnimatedPathSegList;
friend class DOMSVGPathSegList;
friend class DOMSVGPathSeg;
friend class ::nsSVGPathDataParserToInternal;
// nsSVGPathDataParserToInternal will not keep wrappers in sync, so consumers
// are responsible for that!
public:
SVGPathData(){}
~SVGPathData(){}
// Only methods that don't make/permit modification to this list are public.
// Only our friend classes can access methods that may change us.
/// This may return an incomplete string on OOM, but that's acceptable.
void GetValueAsString(nsAString& aValue) const;
PRBool IsEmpty() const {
return mData.IsEmpty();
}
#ifdef DEBUG
/**
* This method iterates over the encoded segment data and countes the number
* of segments we currently have.
*/
PRUint32 CountItems() const;
#endif
/**
* Returns the number of *floats* in the encoding array, and NOT the number
* of segments encoded in this object. (For that, see CountItems() above.)
*/
PRUint32 Length() const {
return mData.Length();
}
const float& operator[](PRUint32 aIndex) const {
return mData[aIndex];
}
// Used by nsSMILCompositor to check if the cached base val is out of date
PRBool operator==(const SVGPathData& rhs) const {
// We use memcmp so that we don't need to worry that the data encoded in
// the first float may have the same bit pattern as a NaN.
return mData.Length() == rhs.mData.Length() &&
memcmp(mData.Elements(), rhs.mData.Elements(),
mData.Length() * sizeof(float)) == 0;
}
PRBool SetCapacity(PRUint32 aSize) {
return mData.SetCapacity(aSize);
}
void Compact() {
mData.Compact();
}
float GetPathLength() const;
PRUint32 GetPathSegAtLength(float aLength) const;
void GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const;
/**
* Returns PR_TRUE, except on OOM, in which case returns PR_FALSE.
*/
PRBool GetSegmentLengths(nsTArray<double> *aLengths) const;
/**
* Returns PR_TRUE, except on OOM, in which case returns PR_FALSE.
*/
PRBool GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aArray) const;
already_AddRefed<gfxFlattenedPath>
ToFlattenedPath(const gfxMatrix& aMatrix) const;
void ConstructPath(gfxContext *aCtx) const;
// Access to methods that can modify objects of this type is deliberately
// limited. This is to reduce the chances of someone modifying objects of
// this type without taking the necessary steps to keep DOM wrappers in sync.
// If you need wider access to these methods, consider adding a method to
// SVGAnimatedPathSegList and having that class act as an intermediary so it
// can take care of keeping DOM wrappers in sync.
protected:
/**
* This may fail on OOM if the internal capacity needs to be increased, in
* which case the list will be left unmodified.
*/
nsresult CopyFrom(const SVGPathData& rhs);
float& operator[](PRUint32 aIndex) {
return mData[aIndex];
}
/**
* This may fail (return PR_FALSE) on OOM if the internal capacity is being
* increased, in which case the list will be left unmodified.
*/
PRBool SetLength(PRUint32 aLength) {
return mData.SetLength(aLength);
}
nsresult SetValueFromString(const nsAString& aValue);
void Clear() {
mData.Clear();
}
// Our DOM wrappers have direct access to our mData, so they directly
// manipulate it rather than us implementing:
//
// * InsertItem(PRUint32 aDataIndex, PRUint32 aType, const float *aArgs);
// * ReplaceItem(PRUint32 aDataIndex, PRUint32 aType, const float *aArgs);
// * RemoveItem(PRUint32 aDataIndex);
// * PRBool AppendItem(PRUint32 aType, const float *aArgs);
nsresult AppendSeg(PRUint32 aType, ...); // variable number of float args
nsTArray<float> mData;
};
/**
* This SVGPathData subclass is for SVGPathSegListSMILType which needs to
* have write access to the lists it works with.
*
* Instances of this class do not have DOM wrappers that need to be kept in
* sync, so we can safely expose any protected base class methods required by
* the SMIL code.
*/
class SVGPathDataAndOwner : public SVGPathData
{
public:
SVGPathDataAndOwner(nsSVGElement *aElement = nsnull)
: mElement(aElement)
{}
void SetElement(nsSVGElement *aElement) {
mElement = aElement;
}
nsSVGElement* Element() const {
return mElement;
}
nsresult CopyFrom(const SVGPathDataAndOwner& rhs) {
mElement = rhs.mElement;
return SVGPathData::CopyFrom(rhs);
}
/**
* Exposed so that SVGPathData baseVals can be copied to
* SVGPathDataAndOwner objects. Note that callers should also call
* SetElement() when using this method!
*/
nsresult CopyFrom(const SVGPathData& rhs) {
return SVGPathData::CopyFrom(rhs);
}
const float& operator[](PRUint32 aIndex) const {
return SVGPathData::operator[](aIndex);
}
float& operator[](PRUint32 aIndex) {
return SVGPathData::operator[](aIndex);
}
PRBool SetLength(PRUint32 aNumberOfItems) {
return SVGPathData::SetLength(aNumberOfItems);
}
private:
// We must keep a strong reference to our element because we may belong to a
// cached baseVal nsSMILValue. See the comments starting at:
// https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15
nsRefPtr<nsSVGElement> mElement;
};
} // namespace mozilla
#endif // MOZILLA_SVGPATHDATA_H__

View File

@ -0,0 +1,242 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 "SVGPathSegListSMILType.h"
#include "nsSMILValue.h"
#include "SVGPathData.h"
#include <math.h>
using namespace mozilla;
/*static*/ SVGPathSegListSMILType SVGPathSegListSMILType::sSingleton;
//----------------------------------------------------------------------
// nsISMILType implementation
void
SVGPathSegListSMILType::Init(nsSMILValue &aValue) const
{
NS_ABORT_IF_FALSE(aValue.IsNull(), "Unexpected value type");
aValue.mU.mPtr = new SVGPathDataAndOwner();
aValue.mType = this;
}
void
SVGPathSegListSMILType::Destroy(nsSMILValue& aValue) const
{
NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value type");
delete static_cast<SVGPathDataAndOwner*>(aValue.mU.mPtr);
aValue.mU.mPtr = nsnull;
aValue.mType = &nsSMILNullType::sSingleton;
}
nsresult
SVGPathSegListSMILType::Assign(nsSMILValue& aDest,
const nsSMILValue& aSrc) const
{
NS_PRECONDITION(aDest.mType == aSrc.mType, "Incompatible SMIL types");
NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL value");
const SVGPathDataAndOwner* src =
static_cast<const SVGPathDataAndOwner*>(aSrc.mU.mPtr);
SVGPathDataAndOwner* dest =
static_cast<SVGPathDataAndOwner*>(aDest.mU.mPtr);
return dest->CopyFrom(*src);
}
PRBool
SVGPathSegListSMILType::IsEqual(const nsSMILValue& aLeft,
const nsSMILValue& aRight) const
{
NS_PRECONDITION(aLeft.mType == aRight.mType, "Incompatible SMIL types");
NS_PRECONDITION(aLeft.mType == this, "Unexpected type for SMIL value");
return *static_cast<const SVGPathDataAndOwner*>(aLeft.mU.mPtr) ==
*static_cast<const SVGPathDataAndOwner*>(aRight.mU.mPtr);
}
nsresult
SVGPathSegListSMILType::Add(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd,
PRUint32 aCount) const
{
NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL type");
NS_PRECONDITION(aValueToAdd.mType == this, "Incompatible SMIL type");
SVGPathDataAndOwner& dest =
*static_cast<SVGPathDataAndOwner*>(aDest.mU.mPtr);
const SVGPathDataAndOwner& valueToAdd =
*static_cast<const SVGPathDataAndOwner*>(aValueToAdd.mU.mPtr);
if (dest.Length() != valueToAdd.Length()) {
// Allow addition to empty dest:
if (dest.Length() == 0) {
return dest.CopyFrom(valueToAdd);
}
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
return NS_ERROR_FAILURE;
}
PRUint32 i = 0;
while (i < dest.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(dest[i]);
if (type != SVGPathSegUtils::DecodeType(valueToAdd[i])) {
// nsSVGUtils::ReportToConsole - can't yet animate between different
// types, although it would make sense to allow animation between
// some.
return NS_ERROR_FAILURE;
}
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(dest[i+3] != valueToAdd[i+3] || dest[i+4] != valueToAdd[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
dest[i] += valueToAdd[i];
}
}
NS_ABORT_IF_FALSE(i == dest.Length(), "Very, very bad - path data corrupt");
// For now we only support pure 'to' animation.
// nsSVGUtils::ReportToConsole
return NS_OK;
}
nsresult
SVGPathSegListSMILType::ComputeDistance(const nsSMILValue& aFrom,
const nsSMILValue& aTo,
double& aDistance) const
{
NS_PRECONDITION(aFrom.mType == this, "Unexpected SMIL type");
NS_PRECONDITION(aTo.mType == this, "Incompatible SMIL type");
// See https://bugzilla.mozilla.org/show_bug.cgi?id=522306#c18
// nsSVGUtils::ReportToConsole
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
const nsSMILValue& aEndVal,
double aUnitDistance,
nsSMILValue& aResult) const
{
NS_PRECONDITION(aStartVal.mType == aEndVal.mType,
"Trying to interpolate different types");
NS_PRECONDITION(aStartVal.mType == this,
"Unexpected types for interpolation");
NS_PRECONDITION(aResult.mType == this, "Unexpected result type");
const SVGPathDataAndOwner& start =
*static_cast<const SVGPathDataAndOwner*>(aStartVal.mU.mPtr);
const SVGPathDataAndOwner& end =
*static_cast<const SVGPathDataAndOwner*>(aEndVal.mU.mPtr);
SVGPathDataAndOwner& result =
*static_cast<SVGPathDataAndOwner*>(aResult.mU.mPtr);
if (start.Length() != end.Length() && start.Length() != 0) {
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
return NS_ERROR_FAILURE;
}
if (!result.SetLength(end.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
PRUint32 i = 0;
if (start.Length() == 0) { // identity path
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
result[i] = end[i];
i++;
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL)) {
result[i] = end[i] * aUnitDistance;
result[i+1] = end[i+1] * aUnitDistance;
result[i+2] = end[i+2] * aUnitDistance;
// boolean args largeArcFlag and sweepFlag must be the same
result[i+3] = end[i+3];
result[i+4] = end[i+4];
result[i+5] = end[i+5] * aUnitDistance;
result[i+6] = end[i+6] * aUnitDistance;
i = segEnd;
} else {
for (; i < segEnd; ++i) {
result[i] = end[i] * aUnitDistance;
}
}
}
} else {
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
if (type != SVGPathSegUtils::DecodeType(start[i])) {
// nsSVGUtils::ReportToConsole - can't yet interpolate between different
// types, although it would make sense to allow interpolation between
// some.
return NS_ERROR_FAILURE;
}
result[i] = end[i];
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(start[i+3] != end[i+3] || start[i+4] != end[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
result[i] = start[i] + (end[i] - start[i]) * aUnitDistance;
}
}
}
NS_ABORT_IF_FALSE(i == end.Length(), "Very, very bad - path data corrupt");
return NS_OK;
}

View File

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 ***** */
#ifndef MOZILLA_SVGPATHSEGLISTSMILTYPE_H_
#define MOZILLA_SVGPATHSEGLISTSMILTYPE_H_
#include "nsISMILType.h"
class nsSMILValue;
namespace mozilla {
////////////////////////////////////////////////////////////////////////
// SVGPathSegListSMILType
//
// Operations for animating an SVGPathData.
//
class SVGPathSegListSMILType : public nsISMILType
{
public:
// Singleton for nsSMILValue objects to hold onto.
static SVGPathSegListSMILType sSingleton;
protected:
// nsISMILType Methods
// -------------------
virtual void Init(nsSMILValue& aValue) const;
virtual void Destroy(nsSMILValue& aValue) const;
virtual nsresult Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const;
virtual PRBool IsEqual(const nsSMILValue& aLeft,
const nsSMILValue& aRight) const;
virtual nsresult Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd,
PRUint32 aCount) const;
virtual nsresult ComputeDistance(const nsSMILValue& aFrom,
const nsSMILValue& aTo,
double& aDistance) const;
virtual nsresult Interpolate(const nsSMILValue& aStartVal,
const nsSMILValue& aEndVal,
double aUnitDistance,
nsSMILValue& aResult) const;
private:
// Private constructor & destructor: prevent instances beyond my singleton,
// and prevent others from deleting my singleton.
SVGPathSegListSMILType() {}
~SVGPathSegListSMILType() {}
};
} // namespace mozilla
#endif // MOZILLA_SVGPATHSEGLISTSMILTYPE_H_

View File

@ -0,0 +1,461 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 "SVGPathSegUtils.h"
#include "nsSVGElement.h"
#include "nsSVGSVGElement.h"
#include "nsSVGPathDataParser.h"
#include "nsString.h"
#include "nsSVGUtils.h"
#include "nsContentUtils.h"
#include "nsTextFormatter.h"
#include "prdtoa.h"
#include <limits>
#include "nsMathUtils.h"
#include "prtypes.h"
using namespace mozilla;
static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f;
static const PRUint32 MAX_RECURSION = 10;
/* static */ void
SVGPathSegUtils::GetValueAsString(const float *aSeg, nsAString& aValue)
{
// Adding new seg type? Is the formatting below acceptable for the new types?
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_MAX_ARGS == 7);
PRUint32 type = DecodeType(aSeg[0]);
PRUnichar typeAsChar = GetPathSegTypeAsLetter(type);
// Special case arcs:
if (type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) {
PRBool largeArcFlag = aSeg[4] != 0.0f;
PRBool sweepFlag = aSeg[5] != 0.0f;
nsTextFormatter::ssprintf(aValue,
NS_LITERAL_STRING("%c%g,%g %g %d,%d %g,%g").get(),
typeAsChar, aSeg[1], aSeg[2], aSeg[3],
largeArcFlag, sweepFlag, aSeg[6], aSeg[7]);
} else {
switch (ArgCountForType(type)) {
case 0:
aValue = typeAsChar;
break;
case 1:
nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g").get(),
typeAsChar, aSeg[1]);
break;
case 2:
nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g,%g").get(),
typeAsChar, aSeg[1], aSeg[2]);
break;
case 4:
nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g,%g %g,%g").get(),
typeAsChar, aSeg[1], aSeg[2], aSeg[3], aSeg[4]);
break;
case 6:
nsTextFormatter::ssprintf(aValue,
NS_LITERAL_STRING("%c%g,%g %g,%g %g,%g").get(),
typeAsChar, aSeg[1], aSeg[2], aSeg[3], aSeg[4],
aSeg[5], aSeg[6]);
break;
default:
NS_ABORT_IF_FALSE(PR_FALSE, "Unknown segment type");
aValue = NS_LITERAL_STRING("<unknown-segment-type>").get();
return;
}
}
// nsTextFormatter::ssprintf is one of the nsTextFormatter methods that
// randomly appends '\0' to its output string, which means that the length
// of the output string is one too long. We need to manually remove that '\0'
// until nsTextFormatter is fixed.
//
if (aValue[aValue.Length() - 1] == PRUnichar('\0')) {
aValue.SetLength(aValue.Length() - 1);
}
}
static float
CalcDistanceBetweenPoints(const gfxPoint &p1, const gfxPoint &p2)
{
return NS_hypot(p2.x - p1.x, p2.y - p1.y);
}
static void SplitQuadraticBezier(const gfxPoint *curve,
gfxPoint *left,
gfxPoint *right)
{
left[0].x = curve[0].x;
left[0].y = curve[0].y;
right[2].x = curve[2].x;
right[2].y = curve[2].y;
left[1].x = (curve[0].x + curve[1].x) / 2;
left[1].y = (curve[0].y + curve[1].y) / 2;
right[1].x = (curve[1].x + curve[2].x) / 2;
right[1].y = (curve[1].y + curve[2].y) / 2;
left[2].x = right[0].x = (left[1].x + right[1].x) / 2;
left[2].y = right[0].y = (left[1].y + right[1].y) / 2;
}
static void SplitCubicBezier(const gfxPoint *curve,
gfxPoint *left,
gfxPoint *right)
{
gfxPoint tmp;
tmp.x = (curve[1].x + curve[2].x) / 4;
tmp.y = (curve[1].y + curve[2].y) / 4;
left[0].x = curve[0].x;
left[0].y = curve[0].y;
right[3].x = curve[3].x;
right[3].y = curve[3].y;
left[1].x = (curve[0].x + curve[1].x) / 2;
left[1].y = (curve[0].y + curve[1].y) / 2;
right[2].x = (curve[2].x + curve[3].x) / 2;
right[2].y = (curve[2].y + curve[3].y) / 2;
left[2].x = left[1].x / 2 + tmp.x;
left[2].y = left[1].y / 2 + tmp.y;
right[1].x = right[2].x / 2 + tmp.x;
right[1].y = right[2].y / 2 + tmp.y;
left[3].x = right[0].x = (left[2].x + right[1].x) / 2;
left[3].y = right[0].y = (left[2].y + right[1].y) / 2;
}
static gfxFloat CalcBezLengthHelper(gfxPoint *curve, PRUint32 numPts,
PRUint32 recursion_count,
void (*split)(const gfxPoint*, gfxPoint*, gfxPoint*))
{
gfxPoint left[4];
gfxPoint right[4];
gfxFloat length = 0, dist;
for (PRUint32 i = 0; i < numPts - 1; i++) {
length += CalcDistanceBetweenPoints(curve[i], curve[i+1]);
}
dist = CalcDistanceBetweenPoints(curve[0], curve[numPts - 1]);
if (length - dist > PATH_SEG_LENGTH_TOLERANCE && recursion_count < MAX_RECURSION) {
split(curve, left, right);
++recursion_count;
return CalcBezLengthHelper(left, numPts, recursion_count, split) +
CalcBezLengthHelper(right, numPts, recursion_count, split);
}
return length;
}
static inline gfxFloat
CalcLengthOfCubicBezier(const gfxPoint &pos, const gfxPoint &cp1,
const gfxPoint &cp2, const gfxPoint &to)
{
gfxPoint curve[4] = { pos, cp1, cp2, to };
return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier);
}
static inline gfxFloat
CalcLengthOfQuadraticBezier(const gfxPoint &pos, const gfxPoint &cp,
const gfxPoint &to)
{
gfxPoint curve[3] = { pos, cp, to };
return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier);
}
static float GetLengthOfClosePath(const float *aArgs, SVGPathTraversalState &aState)
{
float dist = CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.pos = aState.cp1 = aState.cp2 = aState.start;
return dist;
}
static float GetLengthOfMovetoAbs(const float *aArgs, SVGPathTraversalState &aState)
{
aState.start = aState.pos = aState.cp1 = aState.cp2 = gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
}
static float GetLengthOfMovetoRel(const float *aArgs, SVGPathTraversalState &aState)
{
// aState.pos must be second from right due to +=
aState.start = aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
}
static float GetLengthOfLinetoAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint to(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
static float GetLengthOfLinetoRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
static float
GetLengthOfLinetoHorizontalAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint to(aArgs[0], aState.pos.y);
float dist = fabs(to.x - aState.pos.x);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
static float
GetLengthOfLinetoHorizontalRel(const float *aArgs, SVGPathTraversalState &aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], 0.0);
return fabs(aArgs[0]);
}
static float
GetLengthOfLinetoVerticalAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint to(aState.pos.x, aArgs[0]);
float dist = fabs(to.y - aState.pos.y);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
static float
GetLengthOfLinetoVerticalRel(const float *aArgs, SVGPathTraversalState &aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(0.0, aArgs[0]);
return fabs(aArgs[0]);
}
static float GetLengthOfCurvetoCubicAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp1(aArgs[0], aArgs[1]);
gfxPoint cp2(aArgs[2], aArgs[3]);
gfxPoint to(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoCubicSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoCubicRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoCubicSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfArcAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
static float
GetLengthOfArcRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
}
typedef float (*getLengthFunc)(const float*, SVGPathTraversalState&);
/* static */ float
SVGPathSegUtils::GetLength(const float *seg, SVGPathTraversalState &aState)
{
PRUint32 type = DecodeType(seg[0]);
static getLengthFunc lengthFuncTable[20] = {
nsnull, // 0 == PATHSEG_UNKNOWN
GetLengthOfClosePath,
GetLengthOfMovetoAbs,
GetLengthOfMovetoRel,
GetLengthOfLinetoAbs,
GetLengthOfLinetoRel,
GetLengthOfCurvetoCubicAbs,
GetLengthOfCurvetoCubicRel,
GetLengthOfCurvetoQuadraticAbs,
GetLengthOfCurvetoQuadraticRel,
GetLengthOfArcAbs,
GetLengthOfArcRel,
GetLengthOfLinetoHorizontalAbs,
GetLengthOfLinetoHorizontalRel,
GetLengthOfLinetoVerticalAbs,
GetLengthOfLinetoVerticalRel,
GetLengthOfCurvetoCubicSmoothAbs,
GetLengthOfCurvetoCubicSmoothRel,
GetLengthOfCurvetoQuadraticSmoothAbs,
GetLengthOfCurvetoQuadraticSmoothRel
};
NS_ABORT_IF_FALSE(IsValidType(type), "Seg type not recognized");
NS_ABORT_IF_FALSE(type > 0 && type < NS_ARRAY_LENGTH(lengthFuncTable),
"Seg type not recognized");
return lengthFuncTable[type](seg + 1, aState);
}

View File

@ -0,0 +1,217 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 SVG Project code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
#ifndef MOZILLA_SVGPATHSEGUTILS_H__
#define MOZILLA_SVGPATHSEGUTILS_H__
#include "nsIDOMSVGPathSeg.h"
#include "nsIContent.h"
#include "nsAString.h"
#include "nsContentUtils.h"
#include "gfxPoint.h"
#define NS_SVG_PATH_SEG_MAX_ARGS 7
namespace mozilla {
/**
* Code that works with path segments can use an instance of this class to
* store/provide information about the start of the current subpath and the
* last path segment (if any).
*/
struct SVGPathTraversalState
{
SVGPathTraversalState()
: start(0.0, 0.0)
, pos(0.0, 0.0)
, cp1(0.0, 0.0)
, cp2(0.0, 0.0)
{}
gfxPoint start; // start point of current sub path (reset each moveto)
gfxPoint pos; // current position (end point of previous segment)
gfxPoint cp1; // quadratic control point - if the previous segment was a
// quadratic bezier curve then this is set to the absolute
// position of its control point, otherwise its set to pos
gfxPoint cp2; // cubic control point - if the previous segment was a cubic
// bezier curve then this is set to the absolute position of
// its second control point, otherwise it's set to pos
};
/**
* This class is just a collection of static methods - it doesn't have any data
* members, and it's not possible to create instances of this class. This class
* exists purely as a convenient place to gather together a bunch of methods
* related to manipulating and answering questions about path segments.
* Internally we represent path segments purely as an array of floats. See the
* comment documenting SVGPathData for more info on that.
*
* The DOM wrapper classes for encoded path segments (data contained in
* instances of SVGPathData) is DOMSVGPathSeg and its sub-classes. Note that
* there are multiple different DOM classes for path segs - one for each of the
* 19 SVG 1.1 segment types.
*/
class SVGPathSegUtils
{
private:
SVGPathSegUtils(){} // private to prevent instances
public:
static void GetValueAsString(const float *aSeg, nsAString& aValue);
/**
* Encode a segment type enum to a float.
*
* At some point in the future we will likely want to encode other
* information into the float, such as whether the command was explicit or
* not. For now all this method does is save on int to float runtime
* conversion by requiring PRUint32 and float to be of the same size so we
* can simply do a bitwise PRUint32<->float copy.
*/
static float EncodeType(PRUint32 aType) {
PR_STATIC_ASSERT(sizeof(PRUint32) == sizeof(float));
NS_ABORT_IF_FALSE(IsValidType(aType), "Seg type not recognized");
return *(reinterpret_cast<float*>(&aType));
}
static PRUint32 DecodeType(float aType) {
PR_STATIC_ASSERT(sizeof(PRUint32) == sizeof(float));
PRUint32 type = *(reinterpret_cast<PRUint32*>(&aType));
NS_ABORT_IF_FALSE(IsValidType(type), "Seg type not recognized");
return type;
}
static PRUnichar GetPathSegTypeAsLetter(PRUint32 aType) {
NS_ABORT_IF_FALSE(IsValidType(aType), "Seg type not recognized");
static const PRUnichar table[] = {
PRUnichar('x'), // 0 == PATHSEG_UNKNOWN
PRUnichar('z'), // 1 == PATHSEG_CLOSEPATH
PRUnichar('M'), // 2 == PATHSEG_MOVETO_ABS
PRUnichar('m'), // 3 == PATHSEG_MOVETO_REL
PRUnichar('L'), // 4 == PATHSEG_LINETO_ABS
PRUnichar('l'), // 5 == PATHSEG_LINETO_REL
PRUnichar('C'), // 6 == PATHSEG_CURVETO_CUBIC_ABS
PRUnichar('c'), // 7 == PATHSEG_CURVETO_CUBIC_REL
PRUnichar('Q'), // 8 == PATHSEG_CURVETO_QUADRATIC_ABS
PRUnichar('q'), // 9 == PATHSEG_CURVETO_QUADRATIC_REL
PRUnichar('A'), // 10 == PATHSEG_ARC_ABS
PRUnichar('a'), // 11 == PATHSEG_ARC_REL
PRUnichar('H'), // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
PRUnichar('h'), // 13 == PATHSEG_LINETO_HORIZONTAL_REL
PRUnichar('V'), // 14 == PATHSEG_LINETO_VERTICAL_ABS
PRUnichar('v'), // 15 == PATHSEG_LINETO_VERTICAL_REL
PRUnichar('S'), // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
PRUnichar('s'), // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
PRUnichar('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
PRUnichar('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
return table[aType];
}
static PRUint32 ArgCountForType(PRUint32 aType) {
NS_ABORT_IF_FALSE(IsValidType(aType), "Seg type not recognized");
static const PRUint8 table[] = {
0, // 0 == PATHSEG_UNKNOWN
0, // 1 == PATHSEG_CLOSEPATH
2, // 2 == PATHSEG_MOVETO_ABS
2, // 3 == PATHSEG_MOVETO_REL
2, // 4 == PATHSEG_LINETO_ABS
2, // 5 == PATHSEG_LINETO_REL
6, // 6 == PATHSEG_CURVETO_CUBIC_ABS
6, // 7 == PATHSEG_CURVETO_CUBIC_REL
4, // 8 == PATHSEG_CURVETO_QUADRATIC_ABS
4, // 9 == PATHSEG_CURVETO_QUADRATIC_REL
7, // 10 == PATHSEG_ARC_ABS
7, // 11 == PATHSEG_ARC_REL
1, // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
1, // 13 == PATHSEG_LINETO_HORIZONTAL_REL
1, // 14 == PATHSEG_LINETO_VERTICAL_ABS
1, // 15 == PATHSEG_LINETO_VERTICAL_REL
4, // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
4, // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
return table[aType];
}
/**
* Convenience so that callers can pass a float containing an encoded type
* and have it decoded implicitly.
*/
static PRUint32 ArgCountForType(float aType) {
return ArgCountForType(DecodeType(aType));
}
static inline PRBool IsValidType(PRUint32 aType) {
return aType >= nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH &&
aType <= nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
}
static inline PRBool IsCubicType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
}
static inline PRBool IsQuadraticType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
}
/**
* Returns the user unit length of tracing along the path segment.
*/
static float GetLength(const float *aSeg, SVGPathTraversalState &aState);
static void ToString(const float *aSeg, nsAString& aValue);
};
} // namespace mozilla
#endif // MOZILLA_SVGPATHSEGUTILS_H__

View File

@ -46,7 +46,7 @@ load 436418-mpathRoot-1.svg
load 448244-1.svg
load 466576-1.xhtml
load 499879-1.svg
asserts(0-2) load 535691-1.svg # Bug 535691
load 535691-1.svg
load 539167-1.svg
load 573316-1.svg
load 579356-1.svg

View File

@ -74,6 +74,11 @@ public:
nsIAtom **aLocalName) const;
virtual nsSMILTargetAttrType GetTargetAttributeType() const;
// nsSVGElement
virtual nsIAtom* GetPathDataAttrName() const {
return nsGkAtoms::path;
}
// Utility method to let our <mpath> children tell us when they've changed,
// so we can make sure our mAnimationFunction is marked as having changed.
void MpathChanged() { mAnimationFunction.MpathChanged(); }

View File

@ -458,6 +458,8 @@ nsSVGAnimationElement::BeginElement(void)
NS_IMETHODIMP
nsSVGAnimationElement::BeginElementAt(float offset)
{
NS_ENSURE_FINITE(offset, NS_ERROR_ILLEGAL_VALUE);
// This will fail if we're not attached to a time container (SVG document
// fragment).
nsresult rv = mTimedElement.BeginElementAt(offset);
@ -480,6 +482,8 @@ nsSVGAnimationElement::EndElement(void)
NS_IMETHODIMP
nsSVGAnimationElement::EndElementAt(float offset)
{
NS_ENSURE_FINITE(offset, NS_ERROR_ILLEGAL_VALUE);
nsresult rv = mTimedElement.EndElementAt(offset);
if (NS_FAILED(rv))
return rv;

View File

@ -75,6 +75,7 @@
#include "nsSVGViewBox.h"
#include "nsSVGString.h"
#include "SVGAnimatedLengthList.h"
#include "SVGAnimatedPathSegList.h"
#include "nsIDOMSVGUnitTypes.h"
#include "nsIDOMSVGNumberList.h"
#include "nsIDOMSVGAnimatedNumberList.h"
@ -179,6 +180,9 @@ nsSVGElement::Init()
lengthListInfo.Reset(i);
}
// No need to reset SVGPathData since the default value in always the same
// (an empty list).
StringAttributesInfo stringInfo = GetStringInfo();
for (i = 0; i < stringInfo.mStringCount; i++) {
@ -373,6 +377,22 @@ nsSVGElement::ParseAttribute(PRInt32 aNamespaceID,
}
}
if (!foundMatch) {
// Check for SVGAnimatedPathSegList attribute
if (GetPathDataAttrName() == aAttribute) {
SVGAnimatedPathSegList* segList = GetAnimPathSegList();
if (segList) {
rv = segList->SetBaseValueString(aValue);
if (NS_FAILED(rv)) {
ReportAttributeParseFailure(GetOwnerDoc(), aAttribute, aValue);
// The spec says we parse everything up to the failure, so we don't
// call segList->ClearBaseValue()
}
foundMatch = PR_TRUE;
}
}
}
if (!foundMatch) {
// Check for nsSVGNumber2 attribute
NumberAttributesInfo numberInfo = GetNumberInfo();
@ -568,6 +588,18 @@ nsSVGElement::UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
}
}
if (!foundMatch) {
// Check if this is a path segment list attribute going away
if (GetPathDataAttrName() == aName) {
SVGAnimatedPathSegList *segList = GetAnimPathSegList();
if (segList) {
segList->ClearBaseValue();
DidChangePathSegList(PR_FALSE);
foundMatch = PR_TRUE;
}
}
}
if (!foundMatch) {
// Check if this is a number attribute going away
NumberAttributesInfo numInfo = GetNumberInfo();
@ -1546,6 +1578,36 @@ nsSVGElement::GetAnimatedLengthList(PRUint8 aAttrEnum)
return nsnull;
}
void
nsSVGElement::DidChangePathSegList(PRBool aDoSetAttr)
{
NS_ABORT_IF_FALSE(GetPathDataAttrName(), "Changing non-existant path data?");
if (!aDoSetAttr)
return;
nsAutoString newStr;
GetAnimPathSegList()->GetBaseValue().GetValueAsString(newStr);
SetAttr(kNameSpaceID_None, GetPathDataAttrName(), newStr, PR_TRUE);
}
void
nsSVGElement::DidAnimatePathSegList()
{
NS_ABORT_IF_FALSE(GetPathDataAttrName(),
"Animatinging non-existant path data?");
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
frame->AttributeChanged(kNameSpaceID_None,
GetPathDataAttrName(),
nsIDOMMutationEvent::MODIFICATION);
}
}
nsSVGElement::NumberAttributesInfo
nsSVGElement::GetNumberInfo()
{
@ -2215,6 +2277,16 @@ nsSVGElement::GetAnimatedAttr(PRInt32 aNamespaceID, nsIAtom* aName)
}
}
// PathSegLists:
{
if (GetPathDataAttrName() == aName) {
SVGAnimatedPathSegList *segList = GetAnimPathSegList();
if (segList) {
return segList->ToSMILAttr(this);
}
}
}
// Mapped attributes:
if (IsAttributeMapped(aName)) {
nsCSSProperty prop =

View File

@ -74,6 +74,7 @@ struct gfxMatrix;
namespace mozilla {
class SVGAnimatedLengthList;
class SVGUserUnitList;
class SVGAnimatedPathSegList;
}
typedef nsStyledElement nsSVGElementBase;
@ -87,6 +88,10 @@ protected:
virtual ~nsSVGElement();
public:
typedef mozilla::SVGUserUnitList SVGUserUnitList;
typedef mozilla::SVGAnimatedLengthList SVGAnimatedLengthList;
typedef mozilla::SVGAnimatedPathSegList SVGAnimatedPathSegList;
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
@ -167,6 +172,7 @@ public:
virtual void DidChangeViewBox(PRBool aDoSetAttr);
virtual void DidChangePreserveAspectRatio(PRBool aDoSetAttr);
virtual void DidChangeLengthList(PRUint8 aAttrEnum, PRBool aDoSetAttr);
virtual void DidChangePathSegList(PRBool aDoSetAttr);
virtual void DidChangeString(PRUint8 aAttrEnum) {}
virtual void DidAnimateLength(PRUint8 aAttrEnum);
@ -178,19 +184,30 @@ public:
virtual void DidAnimateViewBox();
virtual void DidAnimatePreserveAspectRatio();
virtual void DidAnimateLengthList(PRUint8 aAttrEnum);
virtual void DidAnimatePathSegList();
virtual void DidAnimateTransform();
virtual void DidAnimateString(PRUint8 aAttrEnum);
void GetAnimatedLengthValues(float *aFirst, ...);
void GetAnimatedNumberValues(float *aFirst, ...);
void GetAnimatedIntegerValues(PRInt32 *aFirst, ...);
void GetAnimatedLengthListValues(mozilla::SVGUserUnitList *aFirst, ...);
mozilla::SVGAnimatedLengthList* GetAnimatedLengthList(PRUint8 aAttrEnum);
void GetAnimatedLengthListValues(SVGUserUnitList *aFirst, ...);
SVGAnimatedLengthList* GetAnimatedLengthList(PRUint8 aAttrEnum);
virtual SVGAnimatedPathSegList* GetAnimPathSegList() {
// DOM interface 'SVGAnimatedPathData' (*inherited* by nsSVGPathElement)
// has a member called 'animatedPathSegList' member, so we have a shorter
// name so we don't get hidden by the GetAnimatedPathSegList declared by
// NS_DECL_NSIDOMSVGANIMATEDPATHDATA.
return nsnull;
}
#ifdef MOZ_SMIL
virtual nsISMILAttr* GetAnimatedAttr(PRInt32 aNamespaceID, nsIAtom* aName);
void AnimationNeedsResample();
void FlushAnimations();
#else
void AnimationNeedsResample() { /* do nothing */ }
void FlushAnimations() { /* do nothing */ }
#endif
virtual void RecompileScriptEventListeners();
@ -198,6 +215,10 @@ public:
void GetStringBaseValue(PRUint8 aAttrEnum, nsAString& aResult) const;
void SetStringBaseValue(PRUint8 aAttrEnum, const nsAString& aValue);
virtual nsIAtom* GetPathDataAttrName() const {
return nsnull;
}
protected:
virtual nsresult AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
@ -357,11 +378,11 @@ protected:
};
struct LengthListAttributesInfo {
mozilla::SVGAnimatedLengthList* mLengthLists;
SVGAnimatedLengthList* mLengthLists;
LengthListInfo* mLengthListInfo;
PRUint32 mLengthListCount;
LengthListAttributesInfo(mozilla::SVGAnimatedLengthList *aLengthLists,
LengthListAttributesInfo(SVGAnimatedLengthList *aLengthLists,
LengthListInfo *aLengthListInfo,
PRUint32 aLengthListCount)
: mLengthLists(aLengthLists)

View File

@ -38,13 +38,14 @@
#include "nsSVGPathDataParser.h"
#include "nsSVGDataParser.h"
#include "nsSVGPathSeg.h"
#include "nsSVGPathElement.h"
#include "prdtoa.h"
#include "nsSVGUtils.h"
#include <stdlib.h>
#include <math.h>
using namespace mozilla;
nsresult nsSVGPathDataParser::Match()
{
return MatchSvgPath();
@ -866,195 +867,7 @@ PRBool nsSVGPathDataParser::IsTokenEllipticalArcArgStarter()
//-----------------------------------------------------------------------
// ---------------------------------------------------------------
// nsSVGPathDataParserToInternal
nsresult
nsSVGPathDataParserToInternal::Parse(const nsAString &aValue)
{
mPathData->Clear();
mPx = mPy = mCx = mCy = mStartX = mStartY = 0;
mNumCommands = mNumArguments = 0;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
nsresult rv = nsSVGPathDataParser::Parse(aValue);
PathFini();
return rv;
}
nsresult
nsSVGPathDataParserToInternal::StoreMoveTo(PRBool absCoords, float x, float y)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS;
} else {
x += mPx;
y += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL;
}
return PathMoveTo(x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreClosePath()
{
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH;
return PathClose();
}
nsresult
nsSVGPathDataParserToInternal::StoreLineTo(PRBool absCoords, float x, float y)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS;
} else {
x += mPx;
y += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_REL;
}
return PathLineTo(x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreHLineTo(PRBool absCoords, float x)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS;
} else {
x += mPx;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL;
}
return PathLineTo(x, mPy);
}
nsresult
nsSVGPathDataParserToInternal::StoreVLineTo(PRBool absCoords, float y)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS;
} else {
y += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL;
}
return PathLineTo(mPx, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1,
float x2, float y2)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS;
} else {
x += mPx; x1 += mPx; x2 += mPx;
y += mPy; y1 += mPy; y2 += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL;
}
mCx = x2;
mCy = y2;
return PathCurveTo(x1, y1, x2, y2, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreSmoothCurveTo(PRBool absCoords,
float x, float y,
float x2, float y2)
{
float x1, y1;
// first controlpoint = reflection last one about current point
if (mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS ) {
x1 = 2 * mPx - mCx;
y1 = 2 * mPy - mCy;
} else {
x1 = mPx;
y1 = mPy;
}
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
} else {
x += mPx;
x2 += mPx;
y += mPy;
y2 += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
}
mCx = x2;
mCy = y2;
return PathCurveTo(x1, y1, x2, y2, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreQuadCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS;
} else {
x += mPx;
x1 += mPx;
y += mPy;
y1 += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL;
}
float x31, y31, x32, y32;
// conversion of quadratic bezier curve to cubic bezier curve:
x31 = mPx + (x1 - mPx) * 2 / 3;
y31 = mPy + (y1 - mPy) * 2 / 3;
x32 = x1 + (x - x1) / 3;
y32 = y1 + (y - y1) / 3;
mCx = x1;
mCy = y1;
return PathCurveTo(x31, y31, x32, y32, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreSmoothQuadCurveTo(PRBool absCoords,
float x, float y)
{
float x1, y1;
// first controlpoint = reflection last one about current point
if (mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
mPrevSeg == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS ) {
x1 = 2 * mPx - mCx;
y1 = 2 * mPy - mCy;
} else {
x1 = mPx;
y1 = mPy;
}
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
} else {
x += mPx;
y += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
}
float x31, y31, x32, y32;
// conversion of quadratic bezier curve to cubic bezier curve:
x31 = mPx + (x1 - mPx) * 2 / 3;
y31 = mPy + (y1 - mPy) * 2 / 3;
x32 = x1 + (x - x1) / 3;
y32 = y1 + (y - y1) / 3;
mCx = x1;
mCy = y1;
return PathCurveTo(x31, y31, x32, y32, x, y);
}
static double
CalcVectorAngle(double ux, double uy, double vx, double vy)
@ -1066,300 +879,26 @@ CalcVectorAngle(double ux, double uy, double vx, double vy)
return 2 * M_PI - (ta-tb);
}
nsresult
nsSVGPathDataParserToInternal::ConvertArcToCurves(float x2, float y2,
float rx, float ry,
float angle,
PRBool largeArcFlag,
PRBool sweepFlag)
{
float x1=mPx, y1=mPy, x3, y3;
// Treat out-of-range parameters as described in
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// If the endpoints (x1, y1) and (x2, y2) are identical, then this
// is equivalent to omitting the elliptical arc segment entirely
if (x1 == x2 && y1 == y2) {
return NS_OK;
}
// If rX = 0 or rY = 0 then this arc is treated as a straight line
// segment (a "lineto") joining the endpoints.
if (rx == 0.0f || ry == 0.0f) {
return PathLineTo(x2, y2);
}
nsSVGArcConverter converter(x1, y1, x2, y2, rx, ry, angle,
largeArcFlag, sweepFlag);
while (converter.GetNextSegment(&x1, &y1, &x2, &y2, &x3, &y3)) {
// c) draw the cubic bezier:
nsresult rv = PathCurveTo(x1, y1, x2, y2, x3, y3);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsSVGPathDataParserToInternal::StoreEllipticalArc(PRBool absCoords,
float x, float y,
float r1, float r2,
float angle,
PRBool largeArcFlag,
PRBool sweepFlag)
{
if (absCoords) {
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_ARC_ABS;
} else {
x += mPx;
y += mPy;
mPrevSeg = nsIDOMSVGPathSeg::PATHSEG_ARC_REL;
}
return ConvertArcToCurves(x, y, r1, r2, angle, largeArcFlag, sweepFlag);
}
nsresult
nsSVGPathDataParserToInternal::PathEnsureSpace(PRUint32 aNumArgs)
{
if (!(mNumCommands % 4) &&
!mCommands.AppendElement())
return NS_ERROR_OUT_OF_MEMORY;
if (!mArguments.SetLength(mArguments.Length()+aNumArgs))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
void
nsSVGPathDataParserToInternal::PathAddCommandCode(PRUint8 aCommand)
{
PRUint32 offset = mNumCommands / 4;
PRUint32 shift = 2 * (mNumCommands % 4);
if (shift == 0) {
// make sure we set the byte, to avoid false UMR reports
mCommands[offset] = aCommand;
} else {
mCommands[offset] |= aCommand << shift;
}
mNumCommands++;
}
nsresult
nsSVGPathDataParserToInternal::PathMoveTo(float x, float y)
{
nsresult rv = PathEnsureSpace(2);
NS_ENSURE_SUCCESS(rv, rv);
PathAddCommandCode(nsSVGPathList::MOVETO);
mArguments[mNumArguments++] = x;
mArguments[mNumArguments++] = y;
mPx = mStartX = x;
mPy = mStartY = y;
return NS_OK;
}
nsresult
nsSVGPathDataParserToInternal::PathLineTo(float x, float y)
{
nsresult rv = PathEnsureSpace(2);
NS_ENSURE_SUCCESS(rv, rv);
PathAddCommandCode(nsSVGPathList::LINETO);
mArguments[mNumArguments++] = x;
mArguments[mNumArguments++] = y;
mPx = x;
mPy = y;
return NS_OK;
}
nsresult
nsSVGPathDataParserToInternal::PathCurveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
nsresult rv = PathEnsureSpace(6);
NS_ENSURE_SUCCESS(rv, rv);
PathAddCommandCode(nsSVGPathList::CURVETO);
mArguments[mNumArguments++] = x1;
mArguments[mNumArguments++] = y1;
mArguments[mNumArguments++] = x2;
mArguments[mNumArguments++] = y2;
mArguments[mNumArguments++] = x3;
mArguments[mNumArguments++] = y3;
mPx = x3;
mPy = y3;
return NS_OK;
}
nsresult
nsSVGPathDataParserToInternal::PathClose()
{
nsresult rv = PathEnsureSpace(0);
NS_ENSURE_SUCCESS(rv, rv);
PathAddCommandCode(nsSVGPathList::CLOSEPATH);
mPx = mStartX;
mPy = mStartY;
return NS_OK;
}
void
nsSVGPathDataParserToInternal::PathFini()
{
// We're done adding data to the arrays - copy to a straight array
// in mPathData, which allows us to remove the 8-byte overhead per
// nsTArray. For a bonus savings we allocate a single array instead
// of two.
PRUint32 argArraySize;
argArraySize = mArguments.Length() * sizeof(float);
mPathData->mArguments = (float *)malloc(argArraySize + mCommands.Length());
if (!mPathData->mArguments)
return;
memcpy(mPathData->mArguments, mArguments.Elements(), argArraySize);
memcpy(mPathData->mArguments + mNumArguments,
mCommands.Elements(),
mCommands.Length());
mPathData->mNumArguments = mNumArguments;
mPathData->mNumCommands = mNumCommands;
}
// ---------------------------------------------------------------
// nsSVGPathDataParserToDOM
nsresult
nsSVGPathDataParserToDOM::AppendSegment(nsIDOMSVGPathSeg* seg)
{
NS_ENSURE_TRUE(seg, NS_ERROR_OUT_OF_MEMORY);
mData->AppendObject(seg);
return NS_OK;
}
nsresult
nsSVGPathDataParserToDOM::StoreMoveTo(PRBool absCoords, float x, float y)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegMovetoAbs(x, y)
: NS_NewSVGPathSegMovetoRel(x, y));
}
nsresult
nsSVGPathDataParserToDOM::StoreClosePath()
{
return AppendSegment(NS_NewSVGPathSegClosePath());
}
nsresult
nsSVGPathDataParserToDOM::StoreLineTo(PRBool absCoords, float x, float y)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegLinetoAbs(x, y)
: NS_NewSVGPathSegLinetoRel(x, y));
}
nsresult
nsSVGPathDataParserToDOM::StoreHLineTo(PRBool absCoords, float x)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegLinetoHorizontalAbs(x)
: NS_NewSVGPathSegLinetoHorizontalRel(x));
}
nsresult
nsSVGPathDataParserToDOM::StoreVLineTo(PRBool absCoords, float y)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegLinetoVerticalAbs(y)
: NS_NewSVGPathSegLinetoVerticalRel(y));
}
nsresult
nsSVGPathDataParserToDOM::StoreCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1,
float x2, float y2)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2)
: NS_NewSVGPathSegCurvetoCubicRel(x, y, x1, y1, x2, y2));
}
nsresult
nsSVGPathDataParserToDOM::StoreSmoothCurveTo(PRBool absCoords,
float x, float y,
float x2, float y2)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2)
: NS_NewSVGPathSegCurvetoCubicSmoothRel(x, y, x2, y2));
}
nsresult
nsSVGPathDataParserToDOM::StoreQuadCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1)
: NS_NewSVGPathSegCurvetoQuadraticRel(x, y, x1, y1));
}
nsresult
nsSVGPathDataParserToDOM::StoreSmoothQuadCurveTo(PRBool absCoords,
float x, float y)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegCurvetoQuadraticSmoothAbs(x, y)
: NS_NewSVGPathSegCurvetoQuadraticSmoothRel(x, y));
}
nsresult
nsSVGPathDataParserToDOM::StoreEllipticalArc(PRBool absCoords,
float x, float y,
float r1, float r2,
float angle,
PRBool largeArcFlag,
PRBool sweepFlag)
{
return AppendSegment(
absCoords ? NS_NewSVGPathSegArcAbs(x, y, r1, r2, angle,
largeArcFlag, sweepFlag)
: NS_NewSVGPathSegArcRel(x, y, r1, r2, angle,
largeArcFlag, sweepFlag));
}
nsSVGArcConverter::nsSVGArcConverter(float x1, float y1,
float x2, float y2,
float rx, float ry,
float angle,
nsSVGArcConverter::nsSVGArcConverter(const gfxPoint &from,
const gfxPoint &to,
const gfxPoint &radii,
double angle,
PRBool largeArcFlag,
PRBool sweepFlag)
{
const double radPerDeg = M_PI/180.0;
// If rX or rY have negative signs, these are dropped; the absolute
// value is used instead.
mRx = fabs(rx);
mRy = fabs(ry);
// Convert to center parameterization as shown in
// http://www.w3.org/TR/SVG/implnote.html
mRx = fabs(radii.x);
mRy = fabs(radii.y);
mSinPhi = sin(angle*radPerDeg);
mCosPhi = cos(angle*radPerDeg);
double x1dash = mCosPhi * (x1-x2)/2.0 + mSinPhi * (y1-y2)/2.0;
double y1dash = -mSinPhi * (x1-x2)/2.0 + mCosPhi * (y1-y2)/2.0;
double x1dash = mCosPhi * (from.x-to.x)/2.0 + mSinPhi * (from.y-to.y)/2.0;
double y1dash = -mSinPhi * (from.x-to.x)/2.0 + mCosPhi * (from.y-to.y)/2.0;
double root;
double numerator = mRx*mRx*mRy*mRy - mRx*mRx*y1dash*y1dash -
@ -1367,13 +906,13 @@ nsSVGArcConverter::nsSVGArcConverter(float x1, float y1,
if (numerator < 0.0) {
// If mRx , mRy and are such that there is no solution (basically,
// the ellipse is not big enough to reach from (x1, y1) to (x2,
// y2)) then the ellipse is scaled up uniformly until there is
// the ellipse is not big enough to reach from 'from' to 'to'
// then the ellipse is scaled up uniformly until there is
// exactly one solution (until the ellipse is just big enough).
// -> find factor s, such that numerator' with mRx'=s*mRx and
// mRy'=s*mRy becomes 0 :
float s = (float)sqrt(1.0 - numerator/(mRx*mRx*mRy*mRy));
double s = sqrt(1.0 - numerator/(mRx*mRx*mRy*mRy));
mRx *= s;
mRy *= s;
@ -1388,8 +927,8 @@ nsSVGArcConverter::nsSVGArcConverter(float x1, float y1,
double cxdash = root*mRx*y1dash/mRy;
double cydash = -root*mRy*x1dash/mRx;
mCx = mCosPhi * cxdash - mSinPhi * cydash + (x1+x2)/2.0;
mCy = mSinPhi * cxdash + mCosPhi * cydash + (y1+y2)/2.0;
mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x+to.x)/2.0;
mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y+to.y)/2.0;
mTheta = CalcVectorAngle(1.0, 0.0, (x1dash-cxdash)/mRx, (y1dash-cydash)/mRy);
double dtheta = CalcVectorAngle((x1dash-cxdash)/mRx, (y1dash-cydash)/mRy,
(-x1dash-cxdash)/mRx, (-y1dash-cydash)/mRy);
@ -1403,43 +942,179 @@ nsSVGArcConverter::nsSVGArcConverter(float x1, float y1,
mDelta = dtheta/mNumSegs;
mT = 8.0/3.0 * sin(mDelta/4.0) * sin(mDelta/4.0) / sin(mDelta/2.0);
mX1 = x1;
mY1 = y1;
mFrom = from;
mSegIndex = 0;
}
PRBool
nsSVGArcConverter::GetNextSegment(float *x1, float *y1,
float *x2, float *y2,
float *x3, float *y3)
nsSVGArcConverter::GetNextSegment(gfxPoint *cp1, gfxPoint *cp2, gfxPoint *to)
{
if (mSegIndex == mNumSegs) {
return PR_FALSE;
return PR_FALSE;
}
float cosTheta1 = cos(mTheta);
float sinTheta1 = sin(mTheta);
float theta2 = mTheta + mDelta;
float cosTheta2 = cos(theta2);
float sinTheta2 = sin(theta2);
double cosTheta1 = cos(mTheta);
double sinTheta1 = sin(mTheta);
double theta2 = mTheta + mDelta;
double cosTheta2 = cos(theta2);
double sinTheta2 = sin(theta2);
// a) calculate endpoint of the segment:
*x3 = mCosPhi * mRx*cosTheta2 - mSinPhi * mRy*sinTheta2 + mCx;
*y3 = mSinPhi * mRx*cosTheta2 + mCosPhi * mRy*sinTheta2 + mCy;
to->x = mCosPhi * mRx*cosTheta2 - mSinPhi * mRy*sinTheta2 + mC.x;
to->y = mSinPhi * mRx*cosTheta2 + mCosPhi * mRy*sinTheta2 + mC.y;
// b) calculate gradients at start/end points of segment:
*x1 = mX1 + mT * ( - mCosPhi * mRx*sinTheta1 - mSinPhi * mRy*cosTheta1);
*y1 = mY1 + mT * ( - mSinPhi * mRx*sinTheta1 + mCosPhi * mRy*cosTheta1);
cp1->x = mFrom.x + mT * ( - mCosPhi * mRx*sinTheta1 - mSinPhi * mRy*cosTheta1);
cp1->y = mFrom.y + mT * ( - mSinPhi * mRx*sinTheta1 + mCosPhi * mRy*cosTheta1);
*x2 = *x3 + mT * ( mCosPhi * mRx*sinTheta2 + mSinPhi * mRy*cosTheta2);
*y2 = *y3 + mT * ( mSinPhi * mRx*sinTheta2 - mCosPhi * mRy*cosTheta2);
cp2->x = to->x + mT * ( mCosPhi * mRx*sinTheta2 + mSinPhi * mRy*cosTheta2);
cp2->y = to->y + mT * ( mSinPhi * mRx*sinTheta2 - mCosPhi * mRy*cosTheta2);
// do next segment
mTheta = theta2;
mX1 = *x3;
mY1 = *y3;
mFrom = *to;
++mSegIndex;
return PR_TRUE;
}
// ---------------------------------------------------------------
// nsSVGPathDataParserToInternal
nsresult
nsSVGPathDataParserToInternal::Parse(const nsAString &aValue)
{
mPathSegList->Clear();
return nsSVGPathDataParser::Parse(aValue);
}
nsresult
nsSVGPathDataParserToInternal::StoreMoveTo(PRBool absCoords, float x, float y)
{
// Because our IDL compiler doesn't know any better, each seg type constant
// in nsIDOMSVGPathSeg is in a separate enum. This results in "warning:
// enumeral mismatch in conditional expression" under GCC if two bare
// nsIDOMSVGPathSeg constants are used as operands of the ?: operator below.
// In newer versions of GCC we would be able to turn off this warning using:
//
//#pragma GCC diagnostic push
//#pragma GCC diagnostic ignored "-Wenum-compare"
//...
//#pragma GCC diagnostic pop
//
// Unfortunately we need to support older versions of GCC. Instead, to
// eliminate this warning noise being sent to the console, we wrap the
// operands with PRUint32(...).
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL);
return mPathSegList->AppendSeg(type, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreClosePath()
{
return mPathSegList->AppendSeg(nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH);
}
nsresult
nsSVGPathDataParserToInternal::StoreLineTo(PRBool absCoords, float x, float y)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_REL);
return mPathSegList->AppendSeg(type, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreHLineTo(PRBool absCoords, float x)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL);
return mPathSegList->AppendSeg(type, x);
}
nsresult
nsSVGPathDataParserToInternal::StoreVLineTo(PRBool absCoords, float y)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL);
return mPathSegList->AppendSeg(type, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1,
float x2, float y2)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL);
return mPathSegList->AppendSeg(type, x1, y1, x2, y2, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreSmoothCurveTo(PRBool absCoords,
float x, float y,
float x2, float y2)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL);
return mPathSegList->AppendSeg(type, x2, y2, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreQuadCurveTo(PRBool absCoords,
float x, float y,
float x1, float y1)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL);
return mPathSegList->AppendSeg(type, x1, y1, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreSmoothQuadCurveTo(PRBool absCoords,
float x, float y)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return mPathSegList->AppendSeg(type, x, y);
}
nsresult
nsSVGPathDataParserToInternal::StoreEllipticalArc(PRBool absCoords,
float x, float y,
float r1, float r2,
float angle,
PRBool largeArcFlag,
PRBool sweepFlag)
{
PRUint32 type = absCoords ?
PRUint32(nsIDOMSVGPathSeg::PATHSEG_ARC_ABS) :
PRUint32(nsIDOMSVGPathSeg::PATHSEG_ARC_REL);
// We can only pass floats after 'type', and per the SVG spec for arc,
// non-zero args are treated at 'true'.
return mPathSegList->AppendSeg(type, r1, r2, angle,
largeArcFlag ? 1.0f : 0.0f,
sweepFlag ? 1.0f : 0.0f,
x, y);
}

View File

@ -44,9 +44,14 @@
#include "nsCOMArray.h"
#include "nsIDOMSVGPathSeg.h"
#include "nsTArray.h"
#include "gfxPoint.h"
class nsSVGPathList;
namespace mozilla {
class SVGPathData;
}
////////////////////////////////////////////////////////////////////////
// nsSVGPathDataParser: a simple recursive descent parser that builds
// nsIDOMSVGPathSegs from path data strings. The grammar for path data
@ -140,10 +145,30 @@ protected:
};
class nsSVGArcConverter
{
public:
nsSVGArcConverter(const gfxPoint &from,
const gfxPoint &to,
const gfxPoint &radii,
double angle,
PRBool largeArcFlag,
PRBool sweepFlag);
PRBool GetNextSegment(gfxPoint *cp1, gfxPoint *cp2, gfxPoint *to);
protected:
PRInt32 mNumSegs, mSegIndex;
double mTheta, mDelta, mT;
double mSinPhi, mCosPhi;
double mRx, mRy;
gfxPoint mFrom, mC;
};
class nsSVGPathDataParserToInternal : public nsSVGPathDataParser
{
public:
nsSVGPathDataParserToInternal(nsSVGPathList *data) : mPathData(data) {}
nsSVGPathDataParserToInternal(mozilla::SVGPathData *aList)
: mPathSegList(aList)
{}
nsresult Parse(const nsAString &aValue);
protected:
@ -165,78 +190,7 @@ protected:
PRBool largeArcFlag, PRBool sweepFlag);
private:
nsSVGPathList *mPathData;
PRUint16 mPrevSeg; // previous segment type for "smooth" segments"
float mPx, mPy; // current point
float mCx, mCy; // last control point for "smooth" segments
float mStartX, mStartY; // start of current subpath, for closepath
// information used to construct PathList
nsTArray<PRUint8> mCommands;
nsTArray<float> mArguments;
PRUint32 mNumArguments;
PRUint32 mNumCommands;
// Pathdata helpers
nsresult ConvertArcToCurves(float x2, float y2, float rx, float ry,
float angle, PRBool largeArcFlag, PRBool sweepFlag);
nsresult PathEnsureSpace(PRUint32 aNumArgs);
void PathAddCommandCode(PRUint8 aCommand);
nsresult PathMoveTo(float x, float y);
nsresult PathLineTo(float x, float y);
nsresult PathCurveTo(float x1, float y1, float x2, float y2, float x3, float y3);
nsresult PathClose();
void PathFini();
};
class nsSVGPathDataParserToDOM : public nsSVGPathDataParser
{
public:
nsSVGPathDataParserToDOM(nsCOMArray<nsIDOMSVGPathSeg>* data) : mData(data) {}
protected:
virtual nsresult StoreMoveTo(PRBool absCoords, float x, float y);
virtual nsresult StoreClosePath();
virtual nsresult StoreLineTo(PRBool absCoords, float x, float y);
virtual nsresult StoreHLineTo(PRBool absCoords, float x);
virtual nsresult StoreVLineTo(PRBool absCoords, float y);
virtual nsresult StoreCurveTo(PRBool absCoords, float x, float y,
float x1, float y1, float x2, float y2);
virtual nsresult StoreSmoothCurveTo(PRBool absCoords, float x, float y,
float x2, float y2);
virtual nsresult StoreQuadCurveTo(PRBool absCoords, float x, float y,
float x1, float y1);
virtual nsresult StoreSmoothQuadCurveTo(PRBool absCoords,
float x, float y);
virtual nsresult StoreEllipticalArc(PRBool absCoords, float x, float y,
float r1, float r2, float angle,
PRBool largeArcFlag, PRBool sweepFlag);
private:
nsresult AppendSegment(nsIDOMSVGPathSeg* seg);
nsCOMArray<nsIDOMSVGPathSeg>* mData;
};
class nsSVGArcConverter
{
public:
nsSVGArcConverter(float x1, float y1,
float x2, float y2,
float rx, float ry,
float angle,
PRBool largeArcFlag,
PRBool sweepFlag);
PRBool GetNextSegment(float *x1, float *y1,
float *x2, float *y2,
float *x3, float *y3);
protected:
PRInt32 mNumSegs, mSegIndex;
float mTheta, mDelta, mT;
float mSinPhi, mCosPhi;
float mX1, mY1, mRx, mRy, mCx, mCy;
mozilla::SVGPathData *mPathSegList;
};
#endif // __NS_SVGPATHDATAPARSER_H__

View File

@ -37,9 +37,9 @@
* ***** END LICENSE BLOCK ***** */
#include "nsGkAtoms.h"
#include "nsSVGPathSegList.h"
#include "nsIDOMSVGPathSeg.h"
#include "nsSVGPathSeg.h"
#include "DOMSVGPathSeg.h"
#include "DOMSVGPathSegList.h"
#include "nsCOMPtr.h"
#include "nsIFrame.h"
#include "nsSVGPathDataParser.h"
@ -50,6 +50,8 @@
#include "gfxContext.h"
#include "gfxPlatform.h"
using namespace mozilla;
nsSVGElement::NumberInfo nsSVGPathElement::sNumberInfo =
{ &nsGkAtoms::pathLength, 0 };
@ -78,12 +80,6 @@ nsSVGPathElement::nsSVGPathElement(already_AddRefed<nsINodeInfo> aNodeInfo)
{
}
nsSVGPathElement::~nsSVGPathElement()
{
if (mSegments)
NS_REMOVE_SVGVALUE_OBSERVER(mSegments);
}
//----------------------------------------------------------------------
// nsIDOMNode methods
@ -141,38 +137,7 @@ NS_IMETHODIMP
nsSVGPathElement::GetPathSegAtLength(float distance, PRUint32 *_retval)
{
NS_ENSURE_FINITE(distance, NS_ERROR_ILLEGAL_VALUE);
//Check if mSegments is null
nsresult rv = CreatePathSegList();
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 i = 0, numSegments;
float distCovered = 0;
nsSVGPathSegTraversalState ts;
mSegments->GetNumberOfItems(&numSegments);
// There is no need to check to see if distance falls within the last segment
// because if distance is longer than the total length of the path we return
// the index of the final segment anyway.
while (distCovered < distance && i + 1 < numSegments) {
nsCOMPtr<nsIDOMSVGPathSeg> segment;
mSegments->GetItem(i, getter_AddRefs(segment));
nsSVGPathSeg* curSeg = static_cast<nsSVGPathSeg*>(segment.get());
if (i == 0) {
curSeg->GetLength(&ts);
} else {
distCovered += curSeg->GetLength(&ts);
}
if (distCovered >= distance) {
break;
}
++i;
}
*_retval = i;
*_retval = mD.GetAnimValue().GetPathSegAtLength(distance);
return NS_OK;
}
@ -367,26 +332,6 @@ nsSVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y, ns
return CallQueryInterface(seg, _retval);
}
nsresult
nsSVGPathElement::CreatePathSegList()
{
if (mSegments)
return NS_OK;
nsresult rv = NS_NewSVGPathSegList(getter_AddRefs(mSegments));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISVGValue> value = do_QueryInterface(mSegments);
nsAutoString d;
if (NS_SUCCEEDED(GetAttr(kNameSpaceID_None, nsGkAtoms::d, d)))
value->SetValueString(d);
NS_ADD_SVGVALUE_OBSERVER(mSegments);
return NS_OK;
}
//----------------------------------------------------------------------
// nsSVGElement methods
@ -402,12 +347,9 @@ nsSVGPathElement::GetNumberInfo()
/* readonly attribute nsIDOMSVGPathSegList pathSegList; */
NS_IMETHODIMP nsSVGPathElement::GetPathSegList(nsIDOMSVGPathSegList * *aPathSegList)
{
nsresult rv = CreatePathSegList();
NS_ENSURE_SUCCESS(rv, rv);
*aPathSegList = mSegments;
NS_ADDREF(*aPathSegList);
return NS_OK;
void *key = mD.GetBaseValKey();
*aPathSegList = DOMSVGPathSegList::GetDOMWrapper(key, this, PR_FALSE).get();
return *aPathSegList ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
/* readonly attribute nsIDOMSVGPathSegList normalizedPathSegList; */
@ -419,12 +361,10 @@ NS_IMETHODIMP nsSVGPathElement::GetNormalizedPathSegList(nsIDOMSVGPathSegList *
/* readonly attribute nsIDOMSVGPathSegList animatedPathSegList; */
NS_IMETHODIMP nsSVGPathElement::GetAnimatedPathSegList(nsIDOMSVGPathSegList * *aAnimatedPathSegList)
{
nsresult rv = CreatePathSegList();
NS_ENSURE_SUCCESS(rv, rv);
*aAnimatedPathSegList = mSegments;
NS_ADDREF(*aAnimatedPathSegList);
return NS_OK;
void *key = mD.GetAnimValKey();
*aAnimatedPathSegList =
DOMSVGPathSegList::GetDOMWrapper(key, this, PR_TRUE).get();
return *aAnimatedPathSegList ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
/* readonly attribute nsIDOMSVGPathSegList animatedNormalizedPathSegList; */
@ -447,75 +387,10 @@ nsSVGPathElement::IsAttributeMapped(const nsIAtom* name) const
nsSVGPathElementBase::IsAttributeMapped(name);
}
nsresult
nsSVGPathElement::BeforeSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify)
{
if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::d) {
if (mSegments) {
NS_REMOVE_SVGVALUE_OBSERVER(mSegments);
mSegments = nsnull;
}
if (aValue) {
nsSVGPathDataParserToInternal parser(&mPathData);
nsresult rv = parser.Parse(*aValue);
if (NS_FAILED(rv)) {
ReportAttributeParseFailure(GetOwnerDoc(), aName, *aValue);
}
} else {
mPathData.Clear();
}
}
return nsSVGPathElementBase::BeforeSetAttr(aNamespaceID, aName,
aValue, aNotify);
}
NS_IMETHODIMP
nsSVGPathElement::WillModifySVGObservable(nsISVGValue* observable,
nsISVGValue::modificationType aModType)
{
nsCOMPtr<nsIDOMSVGPathSegList> list = do_QueryInterface(observable);
if (list && mSegments == list) {
return NS_OK;
}
return nsSVGPathElementBase::WillModifySVGObservable(observable, aModType);
}
NS_IMETHODIMP
nsSVGPathElement::DidModifySVGObservable(nsISVGValue* observable,
nsISVGValue::modificationType aModType)
{
nsCOMPtr<nsIDOMSVGPathSegList> list = do_QueryInterface(observable);
if (list && mSegments == list) {
nsCOMPtr<nsISVGValue> value = do_QueryInterface(mSegments);
nsAutoString d;
nsresult rv = value->GetValueString(d);
NS_ENSURE_SUCCESS(rv, rv);
// Want to keep the seglist alive - SetAttr normally invalidates it
nsCOMPtr<nsIDOMSVGPathSegList> deathGrip = mSegments;
mSegments = nsnull;
rv = SetAttr(kNameSpaceID_None, nsGkAtoms::d, d, PR_TRUE);
// Restore seglist
mSegments = deathGrip;
return rv;
}
return nsSVGPathElementBase::DidModifySVGObservable(observable, aModType);
}
already_AddRefed<gfxFlattenedPath>
nsSVGPathElement::GetFlattenedPath(const gfxMatrix &aMatrix)
{
return mPathData.GetFlattenedPath(aMatrix);
return mD.GetAnimValue().ToFlattenedPath(aMatrix);
}
//----------------------------------------------------------------------
@ -537,517 +412,15 @@ nsSVGPathElement::IsMarkable()
return PR_TRUE;
}
static double
CalcVectorAngle(double ux, double uy, double vx, double vy)
{
double ta = atan2(uy, ux);
double tb = atan2(vy, vx);
if (tb >= ta)
return tb-ta;
return 2 * M_PI - (ta-tb);
}
void
nsSVGPathElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks)
{
if (NS_FAILED(CreatePathSegList()))
return;
PRUint32 count;
mSegments->GetNumberOfItems(&count);
nsCOMPtr<nsIDOMSVGPathSeg> segment;
float cx = 0.0f; // current point
float cy = 0.0f;
float cx1 = 0.0f; // last controlpoint (for s,S,t,T)
float cy1 = 0.0f;
PRUint16 lastSegmentType = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
float px = 0, py = 0; // subpath initial point
float pathAngle = 0;
PRUint32 pathIndex = 0;
float prevAngle = 0, startAngle = 0, endAngle = 0;
PRBool newSegment = PR_FALSE;
PRUint32 i;
for (i = 0; i < count; ++i) {
nsCOMPtr<nsIDOMSVGPathSeg> segment;
mSegments->GetItem(i, getter_AddRefs(segment));
PRUint16 type = nsIDOMSVGPathSeg::PATHSEG_UNKNOWN;
segment->GetPathSegType(&type);
float x, y;
PRBool absCoords = PR_FALSE;
switch (type) {
case nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH:
{
x = px;
y = py;
startAngle = endAngle = atan2(y - cy, x - cx);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL:
{
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegMovetoRel> moveseg = do_QueryInterface(segment);
NS_ASSERTION(moveseg, "interface not implemented");
moveseg->GetX(&x);
moveseg->GetY(&y);
x += cx;
y += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegMovetoAbs> moveseg = do_QueryInterface(segment);
NS_ASSERTION(moveseg, "interface not implemented");
moveseg->GetX(&x);
moveseg->GetY(&y);
}
px = x;
py = y;
startAngle = endAngle = prevAngle;
newSegment = PR_TRUE;
}
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL:
{
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegLinetoRel> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetX(&x);
lineseg->GetY(&y);
x += cx;
y += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegLinetoAbs> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetX(&x);
lineseg->GetY(&y);
}
startAngle = endAngle = atan2(y - cy, x - cx);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL:
{
float x1, y1, x2, y2;
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegCurvetoCubicRel> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX1(&x1);
curveseg->GetY1(&y1);
curveseg->GetX2(&x2);
curveseg->GetY2(&y2);
x += cx;
y += cy;
x1 += cx;
y1 += cy;
x2 += cx;
y2 += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegCurvetoCubicAbs> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX1(&x1);
curveseg->GetY1(&y1);
curveseg->GetX2(&x2);
curveseg->GetY2(&y2);
}
cx1 = x2;
cy1 = y2;
if (x1 == cx && y1 == cy) {
x1 = x2;
y1 = y2;
}
if (x2 == x && y2 == y) {
x2 = x1;
y2 = y1;
}
startAngle = atan2(y1 - cy, x1 - cx);
endAngle = atan2(y - y2, x - x2);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL:
{
float x1, y1;
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegCurvetoQuadraticRel> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX1(&x1);
curveseg->GetY1(&y1);
x += cx;
y += cy;
x1 += cx;
y1 += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegCurvetoQuadraticAbs> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX1(&x1);
curveseg->GetY1(&y1);
}
cx1 = x1;
cy1 = y1;
startAngle = atan2(y1 - cy, x1 - cx);
endAngle = atan2(y - y1, x - x1);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_ARC_REL:
{
float r1, r2, angle;
PRBool largeArcFlag, sweepFlag;
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegArcRel> arcseg = do_QueryInterface(segment);
NS_ASSERTION(arcseg, "interface not implemented");
arcseg->GetX(&x);
arcseg->GetY(&y);
arcseg->GetR1(&r1);
arcseg->GetR2(&r2);
arcseg->GetAngle(&angle);
arcseg->GetLargeArcFlag(&largeArcFlag);
arcseg->GetSweepFlag(&sweepFlag);
x += cx;
y += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegArcAbs> arcseg = do_QueryInterface(segment);
NS_ASSERTION(arcseg, "interface not implemented");
arcseg->GetX(&x);
arcseg->GetY(&y);
arcseg->GetR1(&r1);
arcseg->GetR2(&r2);
arcseg->GetAngle(&angle);
arcseg->GetLargeArcFlag(&largeArcFlag);
arcseg->GetSweepFlag(&sweepFlag);
}
/* check for degenerate ellipse */
if (r1 == 0.0 || r2 == 0.0) {
startAngle = endAngle = atan2(y - cy, x - cx);
break;
}
r1 = fabs(r1); r2 = fabs(r2);
float xp, yp, cxp, cyp;
/* slope fun&games ... see SVG spec, section F.6 */
angle = angle*M_PI/180.0;
xp = cos(angle)*(cx-x)/2.0 + sin(angle)*(cy-y)/2.0;
yp = -sin(angle)*(cx-x)/2.0 + cos(angle)*(cy-y)/2.0;
/* make sure radii are large enough */
float root, numerator = r1*r1*r2*r2 - r1*r1*yp*yp - r2*r2*xp*xp;
if (numerator < 0.0) {
float s = sqrt(1.0 - numerator/(r1*r1*r2*r2));
r1 *= s;
r2 *= s;
root = 0.0;
} else {
root = sqrt(numerator/(r1*r1*yp*yp + r2*r2*xp*xp));
if (largeArcFlag == sweepFlag)
root = -root;
}
cxp = root*r1*yp/r2;
cyp = -root*r2*xp/r1;
float theta, delta;
theta = CalcVectorAngle(1.0, 0.0, (xp-cxp)/r1, (yp-cyp)/r2);
delta = CalcVectorAngle((xp-cxp)/r1, (yp-cyp)/r2,
(-xp-cxp)/r1, (-yp-cyp)/r2);
if (!sweepFlag && delta > 0)
delta -= 2.0*M_PI;
else if (sweepFlag && delta < 0)
delta += 2.0*M_PI;
float tx1, ty1, tx2, ty2;
tx1 = -cos(angle)*r1*sin(theta) - sin(angle)*r2*cos(theta);
ty1 = -sin(angle)*r1*sin(theta) + cos(angle)*r2*cos(theta);
tx2 = -cos(angle)*r1*sin(theta+delta) - sin(angle)*r2*cos(theta+delta);
ty2 = -sin(angle)*r1*sin(theta+delta) + cos(angle)*r2*cos(theta+delta);
if (delta < 0.0f) {
tx1 = -tx1;
ty1 = -ty1;
tx2 = -tx2;
ty2 = -ty2;
}
startAngle = atan2(ty1, tx1);
endAngle = atan2(ty2, tx2);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL:
{
y = cy;
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegLinetoHorizontalRel> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetX(&x);
x += cx;
} else {
nsCOMPtr<nsIDOMSVGPathSegLinetoHorizontalAbs> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetX(&x);
}
startAngle = endAngle = atan2(0, x - cx);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL:
{
x = cx;
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegLinetoVerticalRel> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetY(&y);
y += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegLinetoVerticalAbs> lineseg = do_QueryInterface(segment);
NS_ASSERTION(lineseg, "interface not implemented");
lineseg->GetY(&y);
}
startAngle = endAngle = atan2(y - cy, 0);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
{
float x1, y1, x2, y2;
if (lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS ) {
// the first controlpoint is the reflection of the last one about the current point:
x1 = 2*cx - cx1;
y1 = 2*cy - cy1;
}
else {
// the first controlpoint is equal to the current point:
x1 = cx;
y1 = cy;
}
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegCurvetoCubicSmoothRel> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX2(&x2);
curveseg->GetY2(&y2);
x += cx;
y += cy;
x2 += cx;
y2 += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegCurvetoCubicSmoothAbs> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
curveseg->GetX2(&x2);
curveseg->GetY2(&y2);
}
cx1 = x2;
cy1 = y2;
if (x1 == cx && y1 == cy) {
x1 = x2;
y1 = y2;
}
if (x2 == x && y2 == y) {
x2 = x1;
y2 = y1;
}
startAngle = atan2(y1 - cy, x1 - cx);
endAngle = atan2(y - y2, x - x2);
}
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
absCoords = PR_TRUE;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
{
float x1, y1;
if (lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
lastSegmentType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS ) {
// the first controlpoint is the reflection of the last one about the current point:
x1 = 2*cx - cx1;
y1 = 2*cy - cy1;
}
else {
// the first controlpoint is equal to the current point:
x1 = cx;
y1 = cy;
}
if (!absCoords) {
nsCOMPtr<nsIDOMSVGPathSegCurvetoQuadraticSmoothRel> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
x += cx;
y += cy;
} else {
nsCOMPtr<nsIDOMSVGPathSegCurvetoQuadraticSmoothAbs> curveseg = do_QueryInterface(segment);
NS_ASSERTION(curveseg, "interface not implemented");
curveseg->GetX(&x);
curveseg->GetY(&y);
}
cx1 = x1;
cy1 = y1;
startAngle = atan2(y1 - cy, x1 - cx);
endAngle = atan2(y - y1, x - x1);
}
break;
default:
NS_ASSERTION(1==0, "unknown path segment");
break;
}
lastSegmentType = type;
if (newSegment &&
type != nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS &&
type != nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL) {
pathIndex = aMarks->Length() - 1;
pathAngle = startAngle;
aMarks->ElementAt(pathIndex).angle = pathAngle;
newSegment = PR_FALSE;
prevAngle = endAngle;
} else if (type == nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL) {
if (aMarks->Length())
aMarks->ElementAt(aMarks->Length() - 1).angle = prevAngle;
} else {
aMarks->ElementAt(aMarks->Length() - 1).angle =
nsSVGUtils::AngleBisect(prevAngle, startAngle);
prevAngle = endAngle;
}
aMarks->AppendElement(nsSVGMark(x, y, 0));
if (type == nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH) {
prevAngle = nsSVGUtils::AngleBisect(endAngle, pathAngle);
aMarks->ElementAt(pathIndex).angle = prevAngle;
}
cx = x;
cy = y;
}
if (aMarks->Length())
aMarks->ElementAt(aMarks->Length() - 1).angle = prevAngle;
mD.GetAnimValue().GetMarkerPositioningData(aMarks);
}
void
nsSVGPathElement::ConstructPath(gfxContext *aCtx)
{
mPathData.Playback(aCtx);
mD.GetAnimValue().ConstructPath(aCtx);
}
//==================================================================
// nsSVGPathList
void
nsSVGPathList::Clear()
{
if (mArguments) {
free(mArguments);
mArguments = nsnull;
}
mNumCommands = 0;
mNumArguments = 0;
}
void
nsSVGPathList::Playback(gfxContext *aCtx)
{
float *args = mArguments;
for (PRUint32 i = 0; i < mNumCommands; i++) {
PRUint8 command =
reinterpret_cast<PRUint8*>(mArguments + mNumArguments)[i / 4];
command = (command >> (2 * (i % 4))) & 0x3;
switch (command) {
case MOVETO:
aCtx->MoveTo(gfxPoint(args[0], args[1]));
args += 2;
break;
case LINETO:
aCtx->LineTo(gfxPoint(args[0], args[1]));
args += 2;
break;
case CURVETO:
aCtx->CurveTo(gfxPoint(args[0], args[1]),
gfxPoint(args[2], args[3]),
gfxPoint(args[4], args[5]));
args += 6;
break;
case CLOSEPATH:
aCtx->ClosePath();
break;
default:
break;
}
}
}
already_AddRefed<gfxFlattenedPath>
nsSVGPathList::GetFlattenedPath(const gfxMatrix& aMatrix)
{
nsRefPtr<gfxContext> ctx =
new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
ctx->SetMatrix(aMatrix);
Playback(ctx);
ctx->IdentityMatrix();
return ctx->GetFlattenedPath();
}

View File

@ -43,28 +43,11 @@
#include "nsIDOMSVGPathElement.h"
#include "nsIDOMSVGAnimatedPathData.h"
#include "nsSVGNumber2.h"
#include "SVGAnimatedPathSegList.h"
#include "gfxPath.h"
class gfxContext;
class nsSVGPathList
{
friend class nsSVGPathDataParserToInternal;
public:
enum { MOVETO, LINETO, CURVETO, CLOSEPATH };
nsSVGPathList() : mArguments(nsnull), mNumCommands(0), mNumArguments(0) {}
~nsSVGPathList() { Clear(); }
void Playback(gfxContext *aCtx);
already_AddRefed<gfxFlattenedPath> GetFlattenedPath(const gfxMatrix &aMatrix);
void Clear();
protected:
float *mArguments;
PRUint32 mNumCommands;
PRUint32 mNumArguments;
};
typedef nsSVGPathGeometryElement nsSVGPathElementBase;
class nsSVGPathElement : public nsSVGPathElementBase,
@ -78,9 +61,9 @@ protected:
friend nsresult NS_NewSVGPathElement(nsIContent **aResult,
already_AddRefed<nsINodeInfo> aNodeInfo);
nsSVGPathElement(already_AddRefed<nsINodeInfo> aNodeInfo);
virtual ~nsSVGPathElement();
public:
typedef mozilla::SVGAnimatedPathSegList SVGAnimatedPathSegList;
// interfaces:
NS_DECL_ISUPPORTS_INHERITED
@ -95,12 +78,6 @@ public:
// nsIContent interface
NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* name) const;
// nsISVGValueObserver
NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
nsISVGValue::modificationType aModType);
NS_IMETHOD DidModifySVGObservable(nsISVGValue* observable,
nsISVGValue::modificationType aModType);
// nsSVGPathGeometryElement methods:
virtual PRBool AttributeDefinesGeometry(const nsIAtom *aName);
virtual PRBool IsMarkable();
@ -111,22 +88,25 @@ public:
// nsIContent interface
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual nsresult BeforeSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
virtual nsXPCClassInfo* GetClassInfo();
virtual SVGAnimatedPathSegList* GetAnimPathSegList() {
return &mD;
}
virtual nsIAtom* GetPathDataAttrName() const {
return nsGkAtoms::d;
}
protected:
// nsSVGElement method
virtual NumberAttributesInfo GetNumberInfo();
// Helper for lazily creating pathseg list
nsresult CreatePathSegList();
nsCOMPtr<nsIDOMSVGPathSegList> mSegments;
SVGAnimatedPathSegList mD;
nsSVGNumber2 mPathLength;
static NumberInfo sNumberInfo;
nsSVGPathList mPathData;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,173 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 the Mozilla SVG project.
*
* The Initial Developer of the Original Code is
* Crocodile Clips Ltd..
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 ***** */
#ifndef __NS_SVGPATHSEG_H__
#define __NS_SVGPATHSEG_H__
#include "nsIDOMSVGPathSeg.h"
#include "nsIWeakReference.h"
#include "nsSVGUtils.h"
#include "nsSVGPathDataParser.h"
// 0dfd1b3c-5638-4813-a6f8-5a325a35d06e
#define NS_SVGPATHSEG_IID \
{ 0x0dfd1b3c, 0x5638, 0x4813, \
{ 0xa6, 0xf8, 0x5a, 0x32, 0x5a, 0x35, 0xd0, 0x6e } }
#define NS_ENSURE_NATIVE_PATH_SEG(obj, retval) \
{ \
nsCOMPtr<nsSVGPathSeg> path = do_QueryInterface(obj); \
if (!path) { \
*retval = nsnull; \
return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR; \
} \
}
class nsSVGPathSegList;
class nsISVGValue;
struct nsSVGPathSegTraversalState {
float curPosX, startPosX, quadCPX, cubicCPX;
float curPosY, startPosY, quadCPY, cubicCPY;
nsSVGPathSegTraversalState()
{
curPosX = startPosX = quadCPX = cubicCPX = 0;
curPosY = startPosY = quadCPY = cubicCPY = 0;
}
};
class nsSVGPathSeg : public nsIDOMSVGPathSeg
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_SVGPATHSEG_IID)
nsSVGPathSeg() : mCurrentList(nsnull) {}
nsresult SetCurrentList(nsISVGValue* aList);
nsQueryReferent GetCurrentList() const;
// nsISupports interface:
NS_DECL_ISUPPORTS
// nsIDOMSVGPathSeg interface:
NS_IMETHOD GetPathSegType(PRUint16 *aPathSegType) = 0;
NS_IMETHOD GetPathSegTypeAsLetter(nsAString & aPathSegTypeAsLetter) = 0;
// nsSVGPathSeg methods:
NS_IMETHOD GetValueString(nsAString& aValue) = 0;
virtual float GetLength(nsSVGPathSegTraversalState *ts) = 0;
protected:
void DidModify();
private:
nsCOMPtr<nsIWeakReference> mCurrentList;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsSVGPathSeg, NS_SVGPATHSEG_IID)
nsIDOMSVGPathSeg*
NS_NewSVGPathSegClosePath();
nsIDOMSVGPathSeg*
NS_NewSVGPathSegMovetoAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegMovetoRel(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoRel(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicAbs(float x, float y,
float x1, float y1,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicRel(float x, float y,
float x1, float y1,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticAbs(float x, float y,
float x1, float y1);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticRel(float x, float y,
float x1, float y1);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegArcAbs(float x, float y,
float r1, float r2, float angle,
PRBool largeArcFlag, PRBool sweepFlag);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegArcRel(float x, float y,
float r1, float r2, float angle,
PRBool largeArcFlag, PRBool sweepFlag);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoHorizontalAbs(float x);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoHorizontalRel(float x);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoVerticalAbs(float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegLinetoVerticalRel(float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicSmoothAbs(float x, float y,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoCubicSmoothRel(float x, float y,
float x2, float y2);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y);
nsIDOMSVGPathSeg*
NS_NewSVGPathSegCurvetoQuadraticSmoothRel(float x, float y);
#endif //__NS_SVGPATHSEG_H__

View File

@ -1,400 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 the Mozilla SVG project.
*
* The Initial Developer of the Original Code is
* Crocodile Clips Ltd..
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 "nsSVGPathSegList.h"
#include "nsSVGPathSeg.h"
#include "nsSVGValue.h"
#include "nsWeakReference.h"
#include "nsCOMArray.h"
#include "nsDOMError.h"
#include "nsSVGPathDataParser.h"
#include "nsReadableUtils.h"
#include "nsContentUtils.h"
////////////////////////////////////////////////////////////////////////
// nsSVGPathSegList
class nsSVGPathSegList : public nsIDOMSVGPathSegList,
public nsSVGValue,
public nsISVGValueObserver
{
protected:
friend nsresult NS_NewSVGPathSegList(nsIDOMSVGPathSegList** result);
nsSVGPathSegList();
~nsSVGPathSegList();
// void Init();
public:
// nsISupports interface:
NS_DECL_ISUPPORTS
// nsIDOMSVGPathSegList interface:
NS_DECL_NSIDOMSVGPATHSEGLIST
// remainder of nsISVGValue interface:
NS_IMETHOD SetValueString(const nsAString& aValue);
NS_IMETHOD GetValueString(nsAString& aValue);
// nsISVGValueObserver
NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
modificationType aModType);
NS_IMETHOD DidModifySVGObservable (nsISVGValue* observable,
modificationType aModType);
// nsISupportsWeakReference
// implementation inherited from nsSupportsWeakReference
protected:
// implementation helpers:
void AppendElement(nsSVGPathSeg* aElement);
void RemoveElementAt(PRInt32 index);
void InsertElementAt(nsSVGPathSeg* aElement, PRInt32 index);
void RemoveFromCurrentList(nsSVGPathSeg*);
void ReleaseSegments(PRBool aModify = PR_TRUE);
nsCOMArray<nsIDOMSVGPathSeg> mSegments;
};
//----------------------------------------------------------------------
// Implementation
nsSVGPathSegList::nsSVGPathSegList()
{
}
nsSVGPathSegList::~nsSVGPathSegList()
{
PRInt32 count = mSegments.Count();
for (PRInt32 i = 0; i < count; ++i) {
nsSVGPathSeg* seg = static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(i));
seg->SetCurrentList(nsnull);
}
}
//----------------------------------------------------------------------
// nsISupports methods:
NS_IMPL_ADDREF(nsSVGPathSegList)
NS_IMPL_RELEASE(nsSVGPathSegList)
DOMCI_DATA(SVGPathSegList, nsSVGPathSegList)
NS_INTERFACE_MAP_BEGIN(nsSVGPathSegList)
NS_INTERFACE_MAP_ENTRY(nsISVGValue)
NS_INTERFACE_MAP_ENTRY(nsIDOMSVGPathSegList)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsISVGValueObserver)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGPathSegList)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISVGValue)
NS_INTERFACE_MAP_END
//----------------------------------------------------------------------
// nsISVGValue methods:
NS_IMETHODIMP
nsSVGPathSegList::SetValueString(const nsAString& aValue)
{
WillModify();
ReleaseSegments(PR_FALSE);
nsSVGPathDataParserToDOM parser(&mSegments);
nsresult rv = parser.Parse(aValue);
PRInt32 count = mSegments.Count();
for (PRInt32 i=0; i<count; ++i) {
nsSVGPathSeg* seg = static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(i));
seg->SetCurrentList(this);
}
if (NS_FAILED(rv)) {
NS_ERROR("path data parse error!");
ReleaseSegments(PR_FALSE);
}
DidModify();
return rv;
}
NS_IMETHODIMP
nsSVGPathSegList::GetValueString(nsAString& aValue)
{
aValue.Truncate();
PRInt32 count = mSegments.Count();
if (count<=0) return NS_OK;
PRInt32 i = 0;
while (1) {
nsSVGPathSeg* seg = static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(i));
nsAutoString str;
seg->GetValueString(str);
aValue.Append(str);
if (++i >= count) break;
aValue.AppendLiteral(" ");
}
return NS_OK;
}
//----------------------------------------------------------------------
// nsIDOMSVGPathSegList methods:
/* readonly attribute unsigned long numberOfItems; */
NS_IMETHODIMP nsSVGPathSegList::GetNumberOfItems(PRUint32 *aNumberOfItems)
{
*aNumberOfItems = mSegments.Count();
return NS_OK;
}
/* void clear (); */
NS_IMETHODIMP nsSVGPathSegList::Clear()
{
WillModify();
ReleaseSegments();
DidModify();
return NS_OK;
}
/* nsIDOMSVGPathSeg initialize (in nsIDOMSVGPathSeg newItem); */
NS_IMETHODIMP nsSVGPathSegList::Initialize(nsIDOMSVGPathSeg *newItem,
nsIDOMSVGPathSeg **_retval)
{
NS_ENSURE_NATIVE_PATH_SEG(newItem, _retval);
Clear();
return AppendItem(newItem, _retval);
}
/* nsIDOMSVGPathSeg getItem (in unsigned long index); */
NS_IMETHODIMP nsSVGPathSegList::GetItem(PRUint32 index, nsIDOMSVGPathSeg **_retval)
{
if (index >= static_cast<PRUint32>(mSegments.Count())) {
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
*_retval = mSegments.ObjectAt(index);
NS_ADDREF(*_retval);
return NS_OK;
}
/* nsIDOMSVGPathSeg insertItemBefore (in nsIDOMSVGPathSeg newItem, in unsigned long index); */
NS_IMETHODIMP nsSVGPathSegList::InsertItemBefore(nsIDOMSVGPathSeg *newItem,
PRUint32 index,
nsIDOMSVGPathSeg **_retval)
{
NS_ENSURE_NATIVE_PATH_SEG(newItem, _retval);
if (index >= static_cast<PRUint32>(mSegments.Count())) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
InsertElementAt(static_cast<nsSVGPathSeg*>(newItem), index);
NS_ADDREF(*_retval = newItem);
return NS_OK;
}
/* nsIDOMSVGPathSeg replaceItem (in nsIDOMSVGPathSeg newItem, in unsigned long index); */
NS_IMETHODIMP nsSVGPathSegList::ReplaceItem(nsIDOMSVGPathSeg *newItem,
PRUint32 index,
nsIDOMSVGPathSeg **_retval)
{
NS_ENSURE_NATIVE_PATH_SEG(newItem, _retval);
// immediately remove the new item from its current list
nsSVGPathSeg* newItemSeg = static_cast<nsSVGPathSeg*>(newItem);
RemoveFromCurrentList(newItemSeg);
if (index >= static_cast<PRUint32>(mSegments.Count())) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
// NOTE: the new item can never be the item we will be replacing now that we removed it from its current list beforehand
InsertElementAt(newItemSeg, index);
RemoveFromCurrentList(static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(index+1)));
NS_ADDREF(*_retval = newItem);
return NS_OK;
}
/* nsIDOMSVGPathSeg removeItem (in unsigned long index); */
NS_IMETHODIMP nsSVGPathSegList::RemoveItem(PRUint32 index, nsIDOMSVGPathSeg **_retval)
{
if (index >= static_cast<PRUint32>(mSegments.Count())) {
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
*_retval = mSegments.ObjectAt(index);
NS_ADDREF(*_retval);
WillModify();
RemoveElementAt(index);
DidModify();
return NS_OK;
}
/* nsIDOMSVGPathSeg appendItem (in nsIDOMSVGPathSeg newItem); */
NS_IMETHODIMP nsSVGPathSegList::AppendItem(nsIDOMSVGPathSeg *newItem,
nsIDOMSVGPathSeg **_retval)
{
NS_ENSURE_NATIVE_PATH_SEG(newItem, _retval);
NS_ADDREF(*_retval = newItem);
AppendElement(static_cast<nsSVGPathSeg*>(newItem));
return NS_OK;
}
//----------------------------------------------------------------------
// nsISVGValueObserver methods
NS_IMETHODIMP
nsSVGPathSegList::WillModifySVGObservable(nsISVGValue* observable,
modificationType aModType)
{
// This method is not yet used
NS_NOTYETIMPLEMENTED("nsSVGPathSegList::WillModifySVGObservable");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsSVGPathSegList::DidModifySVGObservable (nsISVGValue* observable,
modificationType aModType)
{
// This method is not yet used
NS_NOTYETIMPLEMENTED("nsSVGPathSegList::DidModifySVGObservable");
return NS_ERROR_NOT_IMPLEMENTED;
}
//----------------------------------------------------------------------
// Implementation helpers
void
nsSVGPathSegList::ReleaseSegments(PRBool aModify)
{
if (aModify) {
WillModify();
}
PRInt32 count = mSegments.Count();
for (PRInt32 i = 0; i < count; ++i) {
nsSVGPathSeg* seg = static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(i));
seg->SetCurrentList(nsnull);
}
mSegments.Clear();
if (aModify) {
DidModify();
}
}
void
nsSVGPathSegList::AppendElement(nsSVGPathSeg* aElement)
{
WillModify();
// XXX: we should only remove an item from its current list if we
// successfully added it to this list
RemoveFromCurrentList(aElement);
mSegments.AppendObject(aElement);
aElement->SetCurrentList(this);
DidModify();
}
void
nsSVGPathSegList::RemoveElementAt(PRInt32 index)
{
WillModify();
nsSVGPathSeg* seg = static_cast<nsSVGPathSeg*>(mSegments.ObjectAt(index));
seg->SetCurrentList(nsnull);
mSegments.RemoveObjectAt(index);
DidModify();
}
void
nsSVGPathSegList::InsertElementAt(nsSVGPathSeg* aElement, PRInt32 index)
{
WillModify();
// XXX: we should only remove an item from its current list if we
// successfully added it to this list
RemoveFromCurrentList(aElement);
mSegments.InsertObjectAt(aElement, index);
aElement->SetCurrentList(this);
DidModify();
}
// The SVG specs state that 'if newItem is already in a list, it
// is removed from its previous list before it is inserted into this
// list'. This is a helper function to do that.
void
nsSVGPathSegList::RemoveFromCurrentList(nsSVGPathSeg* aSeg)
{
nsCOMPtr<nsISVGValue> currentList = aSeg->GetCurrentList();
if (currentList) {
// aSeg's current list must be cast back to a nsSVGPathSegList*
nsSVGPathSegList* otherSegList = static_cast<nsSVGPathSegList*>(currentList.get());
PRInt32 ix = otherSegList->mSegments.IndexOfObject(aSeg);
if (ix != -1) {
otherSegList->RemoveElementAt(ix);
}
}
}
////////////////////////////////////////////////////////////////////////
// Exported creation functions:
nsresult
NS_NewSVGPathSegList(nsIDOMSVGPathSegList** result)
{
*result = nsnull;
nsSVGPathSegList* pathSegList = new nsSVGPathSegList();
if (!pathSegList) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(pathSegList);
// pathSegList->Init();
*result = (nsIDOMSVGPathSegList*) pathSegList;
return NS_OK;
}

View File

@ -1035,6 +1035,7 @@ nsSVGSVGElement::BindToTree(nsIDocument* aDocument,
rv = mTimedDocumentRoot->SetParent(smilController);
if (mStartAnimationOnBindToTree) {
mTimedDocumentRoot->Begin();
mStartAnimationOnBindToTree = PR_FALSE;
}
}

View File

@ -107,9 +107,11 @@ nsSVGTransformSMILAttr::GetBaseValue() const
void
nsSVGTransformSMILAttr::ClearAnimValue()
{
mVal->WillModify(nsISVGValue::mod_other);
PRBool animValSet = !!mVal->mAnimVal;
mVal->mAnimVal = nsnull;
mVal->DidModify(nsISVGValue::mod_other);
if (animValSet) {
mSVGElement->DidAnimateTransform();
}
}
nsresult

View File

@ -14,6 +14,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=515116
<div id="content" style="display:none;">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text id="text">text</text>
<path id="path"/>
</svg>
</div>
<pre id="test">
@ -121,6 +122,7 @@ var tests = [
prop_name: 'tableValues',
bv_name: 'baseVal',
av_name: 'animVal',
el_type: 'xxx',
prop_type: 'SVGAnimatedNumberList',
list_type: 'SVGNumberList',
item_type: 'SVGNumber',
@ -138,6 +140,7 @@ var tests = [
prop_name: null, // SVGAnimatedPoints is an inherited interface!
bv_name: 'points',
av_name: 'animatedPoints',
el_type: 'SVGPolylineElement',
prop_type: null,
list_type: 'SVGPointList',
item_type: 'SVGPoint',
@ -148,6 +151,7 @@ var tests = [
attr_val_5b: '',
item_constructor: document.getElementById('svg').createSVGPoint
},
*/
{
// SVGPathSegList test:
target_element_id: 'path',
@ -155,19 +159,21 @@ var tests = [
prop_name: null, // SVGAnimatedPathData is an inherited interface!
bv_name: 'pathSegList',
av_name: 'animatedPathSegList',
el_type: 'SVGPathElement',
prop_type: null,
list_type: 'SVGPathSegList',
item_type: 'SVGPathSeg',
attr_val_3a: '',
attr_val_3b: '',
attr_val_4 : '',
attr_val_5a: '',
attr_val_5b: '',
attr_val_3a: 'M 10,10 L 50,50 L 90,10',
attr_val_3b: 'M 10,50 L 50,10 L 90,50',
attr_val_4 : 'M 10,10 L 50,50 L 90,10 M 200,100',
attr_val_5a: 'M 10,10 L 50,50 L 90,10 L 130,50 L 170,10',
attr_val_5b: 'M 50,10 L 50,10 L 90,50 L 130,10 L 170,50',
item_constructor: function() {
// XXX return different values each time
return SVGPathElement.createSVGPathSegLinetoAbs(1, 1);
return document.getElementById('path').createSVGPathSegMovetoAbs(1, 1);
}
},
/*
{
// SVGPathSegList test:
target_element_id: 'path',
@ -175,6 +181,7 @@ var tests = [
prop_name: null, // SVGAnimatedPathData is an inherited interface!
bv_name: 'normalizedPathSegList',
av_name: 'animatedNormalizedPathSegList',
el_type: 'SVGPathElement',
prop_type: null,
list_type: 'SVGPathSegList',
item_type: 'SVGPathSeg',
@ -195,6 +202,7 @@ var tests = [
prop_name: null, // SVGStringList attributes are not animatable
bv_name: 'requiredFeatures',
av_name: null,
el_type: 'SVGGElement',
prop_type: null,
list_type: 'SVGStringList',
item_type: 'DOMString',
@ -219,6 +227,7 @@ var tests = [
prop_name: 'transform',
bv_name: 'baseVal',
av_name: 'animVal',
el_type: 'SVGGElement',
prop_type: 'SVGAnimatedTransformList',
list_type: 'SVGTransformList',
item_type: 'SVGTransform',
@ -878,12 +887,15 @@ function run_animation_timeline_tests()
'The start of the animation should have changed the number of items '+
'in the '+t.list_type+' for '+t.bv_path+' to 5.');
// TODO
if (t.list_type != 'SVGPathSegList') {
ok(t.animVal.getItem(3) === t.old_animVal_items[3],
'When affected by SMIL animation, list items in the '+t.list_type+
' for '+t.bv_path+' that are at indexes that existed prior to the '+
'start of the animation should be the exact same objects as the '+
'objects that were at those indexes prior to the start of the '+
'animation.');
}
t.old_animVal_items = get_array_of_list_items(t.animVal);
@ -1042,11 +1054,13 @@ function run_animation_timeline_tests()
} else {
/* TODO
ok(false,
'Decide what to do here - see ' +
'https://bugzilla.mozilla.org/show_bug.cgi?id=573716 - we ' +
'probably should be discarding any animation sandwich layers from ' +
'a layer that fails to add, on up.');
*/
// In other words, we wouldn't need the if-else check here.

View File

@ -51,10 +51,11 @@ interface nsIDocShell;
#define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
template<class E> class nsTArray;
template<class E, class A> class nsTArray;
struct nsTArrayDefaultAllocator;
%}
[ref] native nsDocshellIDArray(nsTArray<PRUint64>);
[ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>);
[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)]
interface nsISHistoryInternal: nsISupports

View File

@ -321,9 +321,9 @@ ContentChild::DeallocPExternalHelperApp(PExternalHelperAppChild* aService)
}
bool
ContentChild::RecvRegisterChrome(const nsTArray<ChromePackage>& packages,
const nsTArray<ResourceMapping>& resources,
const nsTArray<OverrideMapping>& overrides)
ContentChild::RecvRegisterChrome(const InfallibleTArray<ChromePackage>& packages,
const InfallibleTArray<ResourceMapping>& resources,
const InfallibleTArray<OverrideMapping>& overrides)
{
nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
nsChromeRegistryContent* chromeRegistry =

View File

@ -94,9 +94,9 @@ public:
const PRInt64& aContentLength);
virtual bool DeallocPExternalHelperApp(PExternalHelperAppChild *aService);
virtual bool RecvRegisterChrome(const nsTArray<ChromePackage>& packages,
const nsTArray<ResourceMapping>& resources,
const nsTArray<OverrideMapping>& overrides);
virtual bool RecvRegisterChrome(const InfallibleTArray<ChromePackage>& packages,
const InfallibleTArray<ResourceMapping>& resources,
const InfallibleTArray<OverrideMapping>& overrides);
virtual bool RecvSetOffline(const PRBool& offline);
@ -130,7 +130,7 @@ private:
*/
NS_NORETURN void QuickExit();
nsTArray<nsAutoPtr<AlertObserver> > mAlertObservers;
InfallibleTArray<nsAutoPtr<AlertObserver> > mAlertObservers;
nsRefPtr<ConsoleListener> mConsoleListener;
static ContentChild* sSingleton;

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