author | Benjamin Smedberg <benjamin@smedbergs.us> |
Wed, 10 Nov 2010 10:29:09 -0500 | |
changeset 57236 | bdbef533364f1cdecbbefe16d8d8d9f5e0fd8100 |
parent 57235 | 30b383d73524f23ce5ea32b176c580077e21c0e9 (current diff) |
parent 57201 | 212a391d3b79caa2e953bb70e4a00460089e844e (diff) |
child 57237 | 68dcdaf45a0f759ad51f8978a1c8d578c6c695df |
push id | 16846 |
push user | bsmedberg@mozilla.com |
push date | Wed, 10 Nov 2010 15:29:47 +0000 |
treeherder | mozilla-central@bdbef533364f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | blocking2.0, josh, romaxa, karlt, jmathies, roc |
bugs | 596451, 583109, 606285 |
milestone | 2.0b8pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
content/svg/content/src/nsSVGPathSeg.cpp | file | annotate | diff | comparison | revisions | |
content/svg/content/src/nsSVGPathSeg.h | file | annotate | diff | comparison | revisions | |
content/svg/content/src/nsSVGPathSegList.cpp | file | annotate | diff | comparison | revisions | |
content/svg/content/src/nsSVGPathSegList.h | file | annotate | diff | comparison | revisions | |
dom/plugins/PluginInstanceChild.cpp | file | annotate | diff | comparison | revisions | |
dom/plugins/PluginModuleParent.cpp | file | annotate | diff | comparison | revisions | |
dom/plugins/PluginModuleParent.h | file | annotate | diff | comparison | revisions | |
ipc/ipdl/ipdl/lower.py | file | annotate | diff | comparison | revisions | |
modules/plugin/test/crashtests/crashtests.list | file | annotate | diff | comparison | revisions | |
modules/plugin/test/testplugin/nptest.cpp | file | annotate | diff | comparison | revisions |
--- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -2725,64 +2725,77 @@ nsAccessible::InvalidateChildren() mEmbeddedObjCollector = nsnull; mChildren.Clear(); mChildrenFlags = eChildrenUninitialized; } PRBool nsAccessible::AppendChild(nsAccessible* aChild) { + if (!aChild) + return PR_FALSE; + if (!mChildren.AppendElement(aChild)) return PR_FALSE; if (!nsAccUtils::IsEmbeddedObject(aChild)) mChildrenFlags = eMixedChildren; aChild->BindToParent(this, mChildren.Length() - 1); return PR_TRUE; } 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; mEmbeddedObjCollector = nsnull; aChild->BindToParent(this, aIndex); return PR_TRUE; } 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; } nsAccessible* nsAccessible::GetParent() { if (mParent) return mParent;
--- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -674,18 +674,20 @@ nsDocAccessible::Shutdown() if (mParent) { nsDocAccessible* parentDocument = mParent->GetDocAccessible(); if (parentDocument) parentDocument->RemoveChildDocument(this); 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(); mWeakShell = nsnull; // Avoid reentrancy mNodeToAccessibleMap.Clear(); ClearCache(mAccessibleCache);
--- a/accessible/src/base/nsOuterDocAccessible.cpp +++ b/accessible/src/base/nsOuterDocAccessible.cpp @@ -191,18 +191,23 @@ nsOuterDocAccessible::InvalidateChildren // accessible is created and appended as a child when it's requested. mChildrenFlags = eChildrenUninitialized; } 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; NS_LOG_ACCDOCCREATE("append document to outerdoc", aAccessible->GetDocumentNode()) NS_LOG_ACCDOCCREATE_ACCADDRESS("outerdoc", this)
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -46,17 +46,17 @@ tabbrowser { .tab-throbber, .tab-label, .tab-icon-image, .tab-close-button { -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 */ } #alltabs-popup { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); }
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -827,41 +827,58 @@ true, false); } // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code // that might rely upon the other changes suppressed. // 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") { - - var fm = Components.classes["@mozilla.org/focus-manager;1"]. - getService(Components.interfaces.nsIFocusManager); - var newFocusedElement = fm.getFocusedElementForWindow(window.content, 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; + } + } + + // 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> </method> <method name="_tabAttrModified"> <parameter name="aTab"/> <body><![CDATA[ @@ -1897,25 +1914,21 @@ </property> <!-- Moves a tab to a new browser window, unless it's already the only tab in the current window, in which case this will do nothing. --> <method name="replaceTabWithWindow"> <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> <method name="moveTabTo"> <parameter name="aTab"/> <parameter name="aIndex"/> <body> @@ -2703,21 +2716,28 @@ tabStrip.scrollByPixels(-1); } catch (e) {} ]]></body> </method> <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"; this.mTabstrip.ensureElementIsVisible(this.selectedItem, false); ]]></body> </method> <method name="handleEvent">
--- a/browser/base/content/tabview/groupitems.js +++ b/browser/base/content/tabview/groupitems.js @@ -1015,74 +1015,72 @@ GroupItem.prototype = Utils.extend(new I opacity: .2, top: childBB.top + childBB.height - parentBB.top + padding, left: parentBB.width/2 - this.$expander.width()/2 }); }, // ---------- // 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; }, // ---------- // Function: arrange // Lays out all of the children. // // Parameters: // options - passed to <Items.arrange> or <_stackArrange> arrange: function GroupItem_arrange(options) { if (this.expanded) { this.topChild = null; var box = new Rect(this.expanded.bounds); box.inset(8, 8); 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) // / (the total available content height) this.yDensity = (rects[rects.length - 1].bottom - bb.top) / (bb.height); // xDensity = (the distance from the left of the content area to the right of the rightmost // tab) / (the total available content width) @@ -1093,25 +1091,16 @@ GroupItem.prototype = Utils.extend(new I for each (var rect in rects) { if (rect.right > rightMostRight) rightMostRight = rect.right; else break; } 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); } if (this._isStacked && !this.expanded) this.showExpandControl(); else this.hideExpandControl(); },
--- a/browser/base/content/tabview/items.js +++ b/browser/base/content/tabview/items.js @@ -596,17 +596,17 @@ Item.prototype = { var startSent; var startEvent; var droppables; var dropTarget; // ___ 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) { if (typeof self.dragOptions.start == "function") self.dragOptions.start.apply(self, [startEvent, {position: {left: startPos.x, top: startPos.y}}]); startSent = true; } @@ -887,49 +887,51 @@ let Items = { }, // ---------- // Function: arrange // Arranges the given items in a grid within the given bounds, // 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 ? parseInt(iQ(items[0].container).css('margin-left')) : 0; var padding = itemMargin * 2; var yScale = 1.1; // to allow for titles var rows; var tabWidth; @@ -949,45 +951,41 @@ let Items = { columns++; figure(); } if (rows == 1) { 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); if (options.z) item.setZ(options.z); } } box.left += box.width + padding; column++; if (column == columns) { box.left = bounds.left; box.top += (box.height * yScale) + padding; column = 0; - row++; } } return rects; }, // ---------- // Function: unsquish
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -140,30 +140,32 @@ endif browser_bug555224.js \ browser_bug555767.js \ browser_bug556061.js \ browser_bug559991.js \ browser_bug561623.js \ browser_bug561636.js \ browser_bug562649.js \ browser_bug563588.js \ + browser_bug565575.js \ browser_bug575561.js \ browser_bug577121.js \ browser_bug579872.js \ browser_bug580956.js \ browser_bug581242.js \ browser_bug581253.js \ browser_bug581947.js \ browser_bug585785.js \ browser_bug585830.js \ browser_bug592338.js \ browser_bug594131.js \ browser_bug595507.js \ browser_bug596687.js \ browser_bug597218.js \ + browser_bug609700.js \ browser_contextSearchTabPosition.js \ browser_ctrlTab.js \ browser_discovery.js \ browser_duplicateIDs.js \ browser_gestureSupport.js \ browser_getshortcutoruri.js \ browser_hide_removing.js \ browser_inspector_initialization.js \
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_bug565575.js @@ -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(); +}
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_bug609700.js @@ -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"); +}
--- a/browser/base/content/test/browser_tabfocus.js +++ b/browser/base/content/test/browser_tabfocus.js @@ -39,21 +39,18 @@ function test() { is(focusedWindow.value, browser1.contentWindow, "initial frame focus in tab 1"); is(fm.getFocusedElementForWindow(browser2.contentWindow, false, focusedWindow), null, "initial focus in tab 2"); is(focusedWindow.value, browser2.contentWindow, "initial frame focus in tab 2"); expectFocusShift(function () gBrowser.selectedTab = tab2, 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"); // focusing a button in the current tab should focus it var button1 = browser1.contentDocument.getElementById("button1"); expectFocusShift(function () button1.focus(), browser1.contentWindow, button1, true, @@ -84,16 +81,19 @@ function test() { // focusing the url field should switch active focus away from the tab but // not clear what would be the focus in the tab button1.focus(); expectFocusShift(function () gURLBar.focus(), 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 expectFocusShift(function () gBrowser.selectedTab = tab1, browser1.contentWindow, button1, true, "focusedWindow after tab change, focus in url field, button focused in new tab"); is(fm.getFocusedElementForWindow(browser2.contentWindow, false, {}), button2, "after switch tab, focus in unfocused tab");
--- a/browser/components/migration/content/migration.js +++ b/browser/components/migration/content/migration.js @@ -32,16 +32,18 @@ # 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 ***** 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 _itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield) _selectedProfile: null, // Selected Profile name to import from _wiz: null, _migrator: null, _autoMigrate: null, @@ -49,16 +51,17 @@ var MigrationWizard = { init: function () { var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); 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; if ("arguments" in window && window.arguments.length > 1) { this._source = window.arguments[0]; this._migrator = window.arguments[1].QueryInterface(kIMig); this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup); @@ -76,16 +79,17 @@ var MigrationWizard = { uninit: function () { var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); 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"); }, // 1 - Import Source onImportSourcePageShow: function () { // Reference to the "From File" radio button var fromfile = null; @@ -484,16 +488,45 @@ var MigrationWizard = { setTimeout(close, 5000); } else { this._wiz.canAdvance = true; var nextButton = this._wiz.getButton("next"); 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; } }, onDonePageShow: function () { this._wiz.getButton("cancel").disabled = true; this._wiz.canRewind = false; this._listItems("doneItems");
--- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.h +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.h @@ -34,29 +34,31 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef browserprofilemigratorutils___h___ #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" #define NOTIFY_OBSERVERS(message, item) \ 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()); \ } #define NC_URI(property) \ NS_LITERAL_CSTRING("http://home.netscape.com/NC-rdf#"#property) #define BATCH_ACTION_HISTORY 0 #define BATCH_ACTION_HISTORY_REPLACE 1
--- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -65,16 +65,18 @@ #include "nsISimpleEnumerator.h" #include "nsISupportsArray.h" #include "nsIProfileMigrator.h" #include "nsIBrowserProfileMigrator.h" #include "nsIObserverService.h" #include "nsILocalFileWin.h" #include "nsAutoPtr.h" +#include "prnetdb.h" + #include <objbase.h> #include <shlguid.h> #include <urlhist.h> #include <comdef.h> #include <shlobj.h> #include <intshcut.h> #include "nsIBrowserHistory.h" @@ -1914,24 +1916,30 @@ nsIEProfileMigrator::CopyCookiesFromBuff PL_strncpy(hostCopyConstructor, host, hostLength); hostCopyConstructor += hostLength; *hostCopyConstructor = '\0'; 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), flagsValue & 0x1, // isSecure PR_FALSE, // isHttpOnly PR_FALSE, // isSession PRInt64(expirationDate)); if (NS_FAILED(onerv)) {
--- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -740,34 +740,69 @@ SessionStoreService.prototype = { let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1; this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp); } else if (this._restoreLastWindow && aWindow.toolbar.visible && this._closedWindows.length && !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)); } } // we actually restored the session just now. this._prefBranch.setBoolPref("sessionstore.resume_session_once", false); } if (this._restoreLastWindow && aWindow.toolbar.visible) { // always reset (if not a popup window)
--- a/browser/components/sessionstore/test/browser/Makefile.in +++ b/browser/components/sessionstore/test/browser/Makefile.in @@ -115,16 +115,17 @@ include $(topsrcdir)/config/rules.mk browser_524745.js \ browser_528776.js \ browser_579868.js \ browser_579879.js \ browser_580512.js \ browser_581593.js \ browser_586147.js \ browser_586068-cascaded_restore.js \ + browser_589246.js \ browser_590268.js \ browser_600545.js \ $(NULL) ifneq ($(OS_ARCH),Darwin) _BROWSER_TEST_FILES += \ browser_597071.js \ $(NULL)
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_589246.js @@ -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 O’Shannessy <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(); + }); +} +
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -380,18 +380,16 @@ #endif @BINPATH@/components/nsINIProcessor.manifest @BINPATH@/components/nsINIProcessor.js @BINPATH@/components/nsPrompter.manifest @BINPATH@/components/nsPrompter.js #ifdef MOZ_SERVICES_SYNC @BINPATH@/components/SyncComponents.manifest @BINPATH@/components/Weave.js -@BINPATH@/components/WeaveCrypto.manifest -@BINPATH@/components/WeaveCrypto.js #endif ; Modules @BINPATH@/modules/* ; Safe Browsing @BINPATH@/components/nsSafebrowsingApplication.manifest @BINPATH@/components/nsSafebrowsingApplication.js
--- a/browser/installer/removed-files.in +++ b/browser/installer/removed-files.in @@ -62,16 +62,18 @@ components/nsCloseAllWindows.js components/nsDictionary.js components/nsExtensionManager.js components/nsInterfaceInfoToIDL.js components/nsScriptableIO.js 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 components/xptitemp.dat defaults/pref/all.js defaults/pref/bug259708.js defaults/pref/bug307259.js defaults/pref/reporter.js @@ -616,17 +618,16 @@ xpicleanup@BIN_SUFFIX@ components/nsUrlClassifierLib.js components/nsUrlClassifierListManager.js components/nsURLFormatter.js components/nsWebHandlerApp.js components/PlacesProtocolHandler.js components/storage-Legacy.js components/storage-mozStorage.js components/txEXSLTRegExFunctions.js - components/WeaveCrypto.js components/Weave.js components/WebContentConverter.js defaults/autoconfig/platform.js defaults/autoconfig/prefcalls.js defaults/pref/firefox-branding.js defaults/pref/firefox.js defaults/pref/firefox-l10n.js defaults/pref/services-sync.js
--- a/browser/themes/winstripe/browser/browser-aero.css +++ b/browser/themes/winstripe/browser/browser-aero.css @@ -28,29 +28,22 @@ border-top: none; -moz-border-left-colors: rgba(255,255,255,.5) rgba(83,42,6,.9); -moz-border-bottom-colors: rgba(255,255,255,.5) rgba(83,42,6,.9); -moz-border-right-colors: rgba(255,255,255,.5) rgba(83,42,6,.9); box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 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, #editBookmarkPanel { -moz-appearance: tooltip; color: InfoText; }
--- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -121,26 +121,21 @@ color: white; text-shadow: 0 0 1px rgba(0,0,0,.7), 0 1px 1.5px rgba(0,0,0,.5); font-weight: bold; padding: .1em 1.5em .15em; 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)), -moz-linear-gradient(rgb(246,170,69), rgb(209,74,0) 95%); border-color: rgba(83,42,6,.9); box-shadow: 0 1px 0 rgba(255,255,255,.1) inset, 0 0 1.5px 1px rgba(250,234,169,.7) inset, 0 -1px 0 rgba(250,234,169,.5) inset;
--- a/chrome/src/nsChromeRegistryChrome.cpp +++ b/chrome/src/nsChromeRegistryChrome.cpp @@ -452,28 +452,28 @@ EnumerateOverride(nsIURI* aURIKey, chromeURI, overrideURI }; overrides->AppendElement(override); return (PLDHashOperator)PL_DHASH_NEXT; } struct EnumerationArgs { - nsTArray<ChromePackage>& packages; + InfallibleTArray<ChromePackage>& packages; const nsCString& selectedLocale; const nsCString& selectedSkin; }; 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 }; PL_DHashTableEnumerate(&mPackagesHash, CollectPackages, &args); nsCOMPtr<nsIIOService> io (do_GetIOService()); NS_ENSURE_TRUE(io, );
--- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -607,17 +607,16 @@ OS_TEST=@OS_TEST@ SOLARIS_SUNPRO_CC = @SOLARIS_SUNPRO_CC@ SOLARIS_SUNPRO_CXX = @SOLARIS_SUNPRO_CXX@ # For AIX build 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@ MOZ_PSM=@MOZ_PSM@ # Gssapi (krb5) libraries and headers for the Negotiate auth method GSSAPI_INCLUDES = @GSSAPI_INCLUDES@
--- a/config/rules.mk +++ b/config/rules.mk @@ -1244,36 +1244,20 @@ endif ifeq ($(OS_ARCH),OS2) $(DEF_FILE): $(OBJS) $(SHARED_LIBRARY_LIBS) rm -f $@ echo LIBRARY $(SHARED_LIBRARY_NAME) INITINSTANCE TERMINSTANCE > $@ echo PROTMODE >> $@ 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) $@ endif # OS/2 $(HOST_LIBRARY): $(HOST_OBJS) Makefile rm -f $@ $(HOST_AR) $(HOST_AR_FLAGS) $(HOST_OBJS)
--- a/configure.in +++ b/configure.in @@ -2637,17 +2637,17 @@ ia64*-hpux*) DSO_PIC_CFLAGS= AR=emxomfar AR_FLAGS='r $@' CFLAGS="$CFLAGS -Zomf" CXXFLAGS="$CXXFLAGS -Zomf" 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" MOZ_OPTIMIZE_FLAGS="-O2" MOZ_OPTIMIZE_LDFLAGS="-s -Zlinker /EXEPACK:2 -Zlinker /PACKCODE -Zlinker /PACKDATA" DYNAMIC_XPCOM_LIBS='-L$(LIBXUL_DIST)/lib $(LIBXUL_DIST)/lib/xpcom.lib $(LIBXUL_DIST)/lib/xpcom_core.lib $(LIBXUL_DIST)/lib/mozalloc.lib' LIBXUL_LIBS='-L$(LIBXUL_DIST)/lib $(LIBXUL_DIST)/lib/xpcom.lib $(LIBXUL_DIST)/lib/xul.lib $(LIBXUL_DIST)/lib/mozalloc.lib' TARGET_MD_ARCH=os2 @@ -2673,19 +2673,18 @@ ia64*-hpux*) _DEFINES_CXXFLAGS="$_DEFINES_CXXFLAGS -Uunix -U__unix -U__unix__" AC_CACHE_CHECK(for __declspec(dllexport), ac_os2_declspec, [AC_TRY_COMPILE([__declspec(dllexport) void ac_os2_declspec(void) {}], [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 ;; alpha*-*-osf*) if test "$GNU_CC"; then MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-soname,$@ -o $@' MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-soname,$@ -o $@' @@ -8929,17 +8928,16 @@ AC_SUBST(XPCOM_LIBS) AC_SUBST(XPCOM_FROZEN_LDOPTS) AC_SUBST(XPCOM_GLUE_LDOPTS) AC_SUBST(XPCOM_STANDALONE_GLUE_LDOPTS) 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) AC_SUBST(MOZ_TIMELINE) AC_SUBST(OGLES_SDK_DIR) AC_SUBST(MOZ_APP_NAME) AC_SUBST(MOZ_APP_DISPLAYNAME)
--- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1695,16 +1695,24 @@ public: * A sub document isn't tabbable when it's a zombie document. * * @param aElement element to test. * * @return Whether the subdocument is tabbable. */ 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(); static nsresult EnsureStringBundle(PropertiesFile aFile); static nsIDOMScriptObjectFactory *GetDOMScriptObjectFactory();
--- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -733,17 +733,17 @@ public: * observer that is already observing the node must not be added without * being removed first. */ void AddMutationObserver(nsIMutationObserver* aMutationObserver) { 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); } } /** * Same as above, but only adds the observer if its not observing * the node already.
--- a/content/base/src/nsAttrValue.h +++ b/content/base/src/nsAttrValue.h @@ -53,18 +53,19 @@ #include "nsCOMPtr.h" typedef PRUptrdiff PtrBits; class nsAString; 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 #define NS_ATTRVALUE_BASETYPE_MASK (PtrBits(3)) #define NS_ATTRVALUE_POINTERVALUE_MASK (~NS_ATTRVALUE_BASETYPE_MASK) #define NS_ATTRVALUE_INTEGERTYPE_BITS 4 #define NS_ATTRVALUE_INTEGERTYPE_MASK (PtrBits((1 << NS_ATTRVALUE_INTEGERTYPE_BITS) - 1)) @@ -376,17 +377,17 @@ private: // aStrict is set PR_TRUE if stringifying the return value equals with // aValue. PRInt32 StringToInteger(const nsAString& aValue, PRBool* aStrict, PRInt32* aErrorCode, PRBool aCanBePercent = PR_FALSE, PRBool* aIsPercent = nsnull) const; - static nsTPtrArray<const EnumTable>* sEnumTableArray; + static nsTPtrArray<const EnumTable, nsTArrayDefaultAllocator>* sEnumTableArray; PtrBits mBits; }; /** * Implementation of inline methods */
--- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -6298,16 +6298,50 @@ nsContentUtils::IsSubDocumentTabbable(ns contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer)); // If there are 2 viewers for the current docshell, that // means the current document is a zombie document. // Only navigate into the subdocument if it's not a zombie. 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 static const char badChars[] = {'\r', '\n', 0}; aString.StripChars(badChars); } void
--- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -200,16 +200,18 @@ static NS_DEFINE_CID(kDOMEventGroupCID, #include "nsHTMLStyleSheet.h" #include "nsHTMLCSSStyleSheet.h" #include "mozilla/dom/Link.h" #include "nsIHTMLDocument.h" using namespace mozilla::dom; +typedef nsTArray<Link*> LinkArray; + #ifdef PR_LOGGING static PRLogModuleInfo* gDocumentLeakPRLog; static PRLogModuleInfo* gCspPRLog; #endif #define NAME_NOT_VALID ((nsBaseContentList*)1) @@ -3897,17 +3899,17 @@ nsDocument::InternalAllowXULXBL() return PR_FALSE; } // Note: We don't hold a reference to the document observer; we assume // that it has a live reference to the document. 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); } PRBool nsDocument::RemoveObserver(nsIDocumentObserver* aObserver) { @@ -5538,18 +5540,20 @@ nsDocument::GetAnimationController() if (mAnimationController && shell) { nsPresContext *context = shell->GetPresContext(); if (context && context->ImageAnimationMode() == imgIContainer::kDontAnimMode) { mAnimationController->Pause(nsSMILTimeContainer::PAUSE_USERPREF); } } - // 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(); } return mAnimationController; } #endif // MOZ_SMIL struct DirTable { @@ -7541,33 +7545,33 @@ nsDocument::DestroyElementMaps() mStyledLinks.Clear(); mIdentifierMap.Clear(); } 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; } void 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); } } NS_IMETHODIMP nsDocument::GetScriptTypeID(PRUint32 *aScriptType) { NS_ERROR("No default script type here - ask some element");
--- a/content/base/src/nsFrameLoader.cpp +++ b/content/base/src/nsFrameLoader.cpp @@ -718,18 +718,17 @@ nsFrameLoader::ShowRemoteFrame(const nsI // FIXME/bug 589337: Show()/Hide() is pretty expensive for // cross-process layers; need to figure out what behavior we really // want here. For now, hack. if (!mRemoteBrowserShown) { mRemoteBrowser->Show(size); mRemoteBrowserShown = PR_TRUE; - nsCOMPtr<nsIChromeFrameMessageManager> dummy; - GetMessageManager(getter_AddRefs(dummy)); // Initialize message manager. + EnsureMessageManager(); } else { mRemoteBrowser->Move(size); } return true; } #endif
--- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -229,17 +229,17 @@ nsFrameMessageManager::SendSyncMessage() NS_ASSERTION(!IsWindowLevel(), "Should not call SendSyncMessage in chrome"); NS_ASSERTION(!mParentManager, "Should not have parent manager in content!"); if (mSyncCallback) { NS_ENSURE_TRUE(mCallbackData, NS_ERROR_NOT_INITIALIZED); nsString messageName; 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); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(ncc); JSContext* ctx = nsnull; rv = ncc->GetJSContext(&ctx); @@ -321,17 +321,17 @@ nsFrameMessageManager::GetDocShell(nsIDo return NS_OK; } nsresult 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; if (!ctx) { nsContentUtils::ThreadJSContextStack()->GetSafeJSContext(&ctx); } if (mListeners.Length()) { nsCOMPtr<nsIAtom> name = do_GetAtom(aMessage); @@ -737,17 +737,17 @@ bool SendAsyncMessageToChildProcess(void return cp->SendAsyncMessage(nsString(aMessage), nsString(aJSON)); } return true; } bool SendSyncMessageToParentProcess(void* aCallbackData, const nsAString& aMessage, const nsAString& aJSON, - nsTArray<nsString>* aJSONRetVal) + InfallibleTArray<nsString>* aJSONRetVal) { mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); if (cc) { return cc->SendSyncMessage(nsString(aMessage), nsString(aJSON), aJSONRetVal); } return true;
--- a/content/base/src/nsFrameMessageManager.h +++ b/content/base/src/nsFrameMessageManager.h @@ -61,17 +61,17 @@ struct nsMessageListenerInfo nsCOMPtr<nsIFrameMessageListener> mListener; nsCOMPtr<nsIAtom> mMessage; }; 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); class nsFrameMessageManager : public nsIContentFrameMessageManager, public nsIChromeFrameMessageManager { public: @@ -124,17 +124,17 @@ public: NS_DECL_NSIFRAMEMESSAGEMANAGER NS_DECL_NSISYNCMESSAGESENDER NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER NS_DECL_NSICHROMEFRAMEMESSAGEMANAGER 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); void RemoveChildManager(nsFrameMessageManager* aManager) { mChildManagers.RemoveObject(aManager); }
--- a/content/base/src/nsInProcessTabChildGlobal.cpp +++ b/content/base/src/nsInProcessTabChildGlobal.cpp @@ -49,17 +49,17 @@ #include "nsScriptLoader.h" #include "nsIJSContextStack.h" #include "nsFrameLoader.h" #include "nsIPrivateDOMEvent.h" bool SendSyncMessageToParent(void* aCallbackData, const nsAString& aMessage, const nsAString& aJSON, - nsTArray<nsString>* aJSONRetVal) + InfallibleTArray<nsString>* aJSONRetVal) { nsInProcessTabChildGlobal* tabChild = static_cast<nsInProcessTabChildGlobal*>(aCallbackData); nsCOMPtr<nsIContent> owner = tabChild->mOwner; nsTArray<nsCOMPtr<nsIRunnable> > asyncMessages; asyncMessages.SwapElements(tabChild->mASyncMessages); PRUint32 len = asyncMessages.Length(); for (PRUint32 i = 0; i < len; ++i) {
--- a/content/base/src/nsScriptElement.cpp +++ b/content/base/src/nsScriptElement.cpp @@ -10,17 +10,17 @@ * 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 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. * * Contributor(s): * Jonas Sicking <jonas@sicking.cc> (original developer) * * 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 @@ -141,65 +141,35 @@ void nsScriptElement::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInContainer) { 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() { nsCOMPtr<nsIContent> cont = do_QueryInterface((nsIScriptElement*) this); NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this), "You forgot to add self as observer"); if (mAlreadyStarted || !mDoneAddingChildren || !cont->IsInDoc() || mMalformed || !HasScriptContent()) { return NS_OK; } 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 // (such as NS_CONTENT_SCRIPT_IS_EVENTHANDLER) if (NS_FAILED(scriptresult) && scriptresult != NS_ERROR_HTMLPARSER_BLOCK) { scriptresult = NS_OK; }
--- a/content/base/test/test_bug453736.html +++ b/content/base/test/test_bug453736.html @@ -8,62 +8,28 @@ https://bugzilla.mozilla.org/show_bug.cg <script type="application/javascript" src="/MochiKit/packed.js"></script> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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() { const scriptCreationFuncs = [ function() { return document.createElement("script"); }, 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]; 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')); @@ -82,16 +48,12 @@ addLoadEvent(function() { document.body.appendChild(s.cloneNode(true)); is(window[cont+"ScriptRan"], false, "Clone of script inside <" + cont + "> created with " + func + " shouldn't run"); } } SimpleTest.finish(); }); - - - - </script> </pre> </body> </html>
--- a/content/canvas/src/DocumentRendererChild.cpp +++ b/content/canvas/src/DocumentRendererChild.cpp @@ -59,62 +59,28 @@ using namespace mozilla::ipc; 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, const gfxMatrix& transform, const nsString& bgcolor, PRUint32 renderFlags, PRBool flushLayout, const nsIntSize& renderSize, nsCString& data) { if (flushLayout) - FlushLayoutForTree(window); + nsContentUtils::FlushLayoutForTree(window); nsCOMPtr<nsPresContext> presContext; nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(window); if (win) { nsIDocShell* docshell = win->GetDocShell(); if (docshell) { docshell->GetPresContext(getter_AddRefs(presContext)); }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp @@ -3557,51 +3557,16 @@ nsCanvasRenderingContext2D::GetGlobalCom else CANVAS_OP_TO_THEBES_OP("xor", XOR) else return NS_ERROR_FAILURE; #undef CANVAS_OP_TO_THEBES_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, const nsAString& aBGColor, PRUint32 flags) { NS_ENSURE_ARG(aWindow != nsnull); @@ -3620,17 +3585,17 @@ nsCanvasRenderingContext2D::DrawWindow(n if (!nsContentUtils::IsCallerTrustedForRead()) { // not permitted to use DrawWindow // XXX ERRMSG we need to report an error to developers here! (bug 329026) return NS_ERROR_DOM_SECURITY_ERR; } // Flush layout updates if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) - FlushLayoutForTree(aWindow); + nsContentUtils::FlushLayoutForTree(aWindow); nsRefPtr<nsPresContext> presContext; nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow); if (win) { nsIDocShell* docshell = win->GetDocShell(); if (docshell) { docshell->GetPresContext(getter_AddRefs(presContext)); }
--- a/content/html/content/src/nsHTMLCanvasElement.cpp +++ b/content/html/content/src/nsHTMLCanvasElement.cpp @@ -368,18 +368,17 @@ nsHTMLCanvasElement::MozGetAsFileImpl(co } nsresult nsHTMLCanvasElement::GetContextHelper(const nsAString& aContextId, nsICanvasRenderingContextInternal **aContext) { 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++) { if ((ctxId[i] < 'A' || ctxId[i] > 'Z') && (ctxId[i] < 'a' || ctxId[i] > 'z') && (ctxId[i] < '0' || ctxId[i] > '9') && (ctxId[i] != '-') && (ctxId[i] != '_'))
--- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -16,16 +16,17 @@ * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * 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"), * 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 @@ -535,16 +536,19 @@ UploadLastDir::FetchLastUsedDirectory(ns nsresult UploadLastDir::StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile) { NS_PRECONDITION(aURI, "aURI is null"); 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 if (mInPrivateBrowsing) { nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService = do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID); if (!hostnameGrouperService) return NS_ERROR_NOT_AVAILABLE;
--- a/content/html/content/src/nsHTMLSelectElement.cpp +++ b/content/html/content/src/nsHTMLSelectElement.cpp @@ -1212,18 +1212,17 @@ nsHTMLSelectElement::SetValue(const nsAS return rv; } 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 nsHTMLSelectElement::Blur() { return nsGenericHTMLElement::Blur(); }
--- a/content/html/content/src/nsHTMLSelectElement.h +++ b/content/html/content/src/nsHTMLSelectElement.h @@ -490,24 +490,16 @@ protected: void VerifyOptionsArray(); #endif virtual PRBool AcceptAutofocus() const { 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. */ PRPackedBool mIsDoneAddingChildren; /** true if our disabled state has changed from the default **/ PRPackedBool mDisabledChanged; /** true if child nodes are being added or removed. * Used by nsSafeOptionListMutation.
--- a/content/html/content/test/Makefile.in +++ b/content/html/content/test/Makefile.in @@ -235,12 +235,13 @@ include $(topsrcdir)/config/rules.mk test_bug596350.html \ test_bug600155.html \ test_bug556007.html \ test_bug606817.html \ test_bug297761.html \ file_bug297761.html \ test_bug607145.html \ test_bug601061.html \ + reflect.js \ $(NULL) libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
copy from content/html/content/test/test_bug601061.html copy to content/html/content/test/reflect.js --- a/content/html/content/test/test_bug601061.html +++ b/content/html/content/test/reflect.js @@ -1,27 +1,12 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=601061 ---> -<head> - <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> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=601061">Mozilla Bug 601061</a> -<p id="display"></p> -<pre id="test"> -<script type="application/javascript"> - -/** Test for Bug 601061 **/ - +/** + * 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); } @@ -103,16 +88,8 @@ function reflectUnsignedInt(aElement, aA 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); - -</script> -</pre> -</body> -</html>
--- a/content/html/content/test/test_bug551846.html +++ b/content/html/content/test/test_bug551846.html @@ -117,43 +117,43 @@ function checkSetSizeException(element) caught = true; } ok(!caught, "Setting an invalid size in the content attribute shouldn't throw an exception"); // reverting to defalut 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> </body> </html>
--- a/content/html/content/test/test_bug601061.html +++ b/content/html/content/test/test_bug601061.html @@ -2,117 +2,25 @@ <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=601061 --> <head> <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> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=601061">Mozilla Bug 601061</a> <p id="display"></p> <pre id="test"> <script type="application/javascript"> /** 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> </body> </html>
--- a/content/media/test/test_timeupdate_seek.html +++ b/content/media/test/test_timeupdate_seek.html @@ -15,59 +15,64 @@ // WHATWG spec. // // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#seeking 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; } function do_seeking(e) { var v = e.target; if (v._finished) return false; 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; } function do_timeupdate(e) { var v = e.target; if (v._finished) 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; } function startTest(test, token) { var type = /^video/.test(test.type) ? "video" : "audio"; var v = document.createElement(type); 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; v.addEventListener("loadedmetadata", do_loadedmetadata, false); v.addEventListener("seeking", do_seeking, false); v.addEventListener("timeupdate", do_timeupdate, false); document.body.appendChild(v); }
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/572938-1.svg @@ -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>
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/572938-2.svg @@ -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>
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/572938-3.svg @@ -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>
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/572938-4.svg @@ -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>
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/605345-1.svg @@ -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>
new file mode 100644 --- /dev/null +++ b/content/smil/crashtests/608549-1.svg @@ -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>
--- a/content/smil/crashtests/crashtests.list +++ b/content/smil/crashtests/crashtests.list @@ -11,16 +11,22 @@ load 541297-1.svg load 547333-1.svg load 548899-1.svg load 551620-1.svg load 554202-1.svg 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
--- a/content/smil/nsSMILAnimationController.cpp +++ b/content/smil/nsSMILAnimationController.cpp @@ -69,19 +69,17 @@ GetRefreshDriverForDoc(nsIDocument* aDoc } //---------------------------------------------------------------------- // ctors, dtors, factory methods nsSMILAnimationController::nsSMILAnimationController() : mResampleNeeded(PR_FALSE), mDeferredStartSampling(PR_FALSE), -#ifdef DEBUG mRunningSample(PR_FALSE), -#endif mDocument(nsnull) { mAnimationElementTable.Init(); mChildContainerTable.Init(); } nsSMILAnimationController::~nsSMILAnimationController() { @@ -184,17 +182,16 @@ nsSMILAnimationController::WillRefresh(m //---------------------------------------------------------------------- // Animation element registration methods: void nsSMILAnimationController::RegisterAnimationElement( nsISMILAnimationElement* aAnimationElement) { - NS_ASSERTION(!mRunningSample, "Registering content during sample."); mAnimationElementTable.PutEntry(aAnimationElement); if (mDeferredStartSampling) { mDeferredStartSampling = PR_FALSE; if (mChildContainerTable.Count()) { // mAnimationElementTable was empty, but now we've added its 1st element NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1, "we shouldn't have deferred sampling if we already had " "animations registered"); @@ -202,17 +199,16 @@ nsSMILAnimationController::RegisterAnima } // else, don't sample until a time container is registered (via AddChild) } } void nsSMILAnimationController::UnregisterAnimationElement( nsISMILAnimationElement* aAnimationElement) { - NS_ASSERTION(!mRunningSample, "Unregistering content during sample."); mAnimationElementTable.RemoveEntry(aAnimationElement); } //---------------------------------------------------------------------- // Page show/hide void nsSMILAnimationController::OnPageShow() @@ -357,26 +353,21 @@ void nsSMILAnimationController::DoSample() { DoSample(PR_TRUE); // Skip unchanged time containers } 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; + // 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; mDocument->FlushPendingNotifications(Flush_Style); -#ifdef DEBUG - 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; // STEP 1: Bring model up to date // (i) Rewind elements where necessary // (ii) Run milestone samples RewindElements(); DoMilestoneSamples(); // STEP 2: Sample the child time containers @@ -440,19 +431,17 @@ nsSMILAnimationController::DoSample(PRBo } // STEP 5: Compose currently-animated attributes. // XXXdholbert: This step traverses our animation targets in an effectively // random order. For animation from/to 'inherit' values to work correctly // 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(); NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!"); } void
--- a/content/smil/nsSMILAnimationController.h +++ b/content/smil/nsSMILAnimationController.h @@ -89,17 +89,22 @@ public: // Methods for registering and enumerating animation elements void RegisterAnimationElement(nsISMILAnimationElement* aAnimationElement); void UnregisterAnimationElement(nsISMILAnimationElement* aAnimationElement); // Methods for resampling all animations // (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) return; Resample(); } @@ -198,19 +203,17 @@ protected: TimeContainerHashtable mChildContainerTable; mozilla::TimeStamp mCurrentSampleTime; mozilla::TimeStamp mStartTime; PRPackedBool mResampleNeeded; // If we're told to start sampling but there are no animation elements we just // 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 nsIDocument* mDocument; // Contains compositors used in our last sample. We keep this around // so we can detect when an element/attribute used to be animated, // but isn't anymore for some reason. (e.g. if its <animate> element is
--- a/content/smil/nsSMILAnimationFunction.cpp +++ b/content/smil/nsSMILAnimationFunction.cpp @@ -241,17 +241,17 @@ nsSMILAnimationFunction::ComposeResult(c // If this interval is active, we must have a non-negative mSampleTime NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive, "Negative sample time for active animation"); NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mSimpleDuration.IsIndefinite() || mLastValue, "Unresolved simple duration for active or frozen animation"); - nsSMILValue result(aResult.mType); + nsSMILValue result; if (mSimpleDuration.IsIndefinite() || (values.Length() == 1 && TreatSingleValueAsStatic())) { // Indefinite duration or only one value set: Always set the first value result = values[0]; } else if (mLastValue) { @@ -397,25 +397,29 @@ nsSMILAnimationFunction::InterpolateResu if (calcMode != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nsnull; const nsSMILValue* to = nsnull; // Init to -1 to make sure that if we ever forget to set this, the // 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) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was
--- a/content/smil/nsSMILAnimationFunction.h +++ b/content/smil/nsSMILAnimationFunction.h @@ -196,17 +196,17 @@ public: /** * Indicates if this animation will replace the passed in result rather than * adding to it. Animations that replace the underlying value may be called * without first calling lower priority animations. * * @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 * time it was composited. This allows rendering to be performed only when * necessary, particularly when no animations are active. * * Note that the caller is responsible for determining if the animation * target has changed (with help from my UpdateCachedTarget() method).
--- a/content/smil/nsSMILCSSProperty.cpp +++ b/content/smil/nsSMILCSSProperty.cpp @@ -97,28 +97,31 @@ nsSMILCSSProperty::nsSMILCSSProperty(nsC nsSMILValue nsSMILCSSProperty::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 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; } // GENERAL CASE: Non-Shorthands // (1) Put empty string in override style for property mPropID // (saving old override style value, so we can set it again when we're done) nsCOMPtr<nsIDOMCSSStyleDeclaration> overrideStyle; mElement->GetSMILOverrideStyle(getter_AddRefs(overrideStyle));
--- a/content/smil/nsSMILCompositor.cpp +++ b/content/smil/nsSMILCompositor.cpp @@ -103,32 +103,35 @@ nsSMILCompositor::ComposeAttribute() nsSMILAnimationFunction::Comparator comparator; mAnimationFunctions.Sort(comparator); // THIRD: Step backwards through animation functions to find out // which ones we actually care about. 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); if (!mForceCompositing) { return; } // FIFTH: Compose animation functions PRUint32 length = mAnimationFunctions.Length(); 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); if (NS_FAILED(rv)) { NS_WARNING("nsISMILAttr::SetAnimValue failed"); } }
--- a/content/smil/nsSMILSetAnimationFunction.cpp +++ b/content/smil/nsSMILSetAnimationFunction.cpp @@ -119,8 +119,14 @@ PRBool nsSMILSetAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const { if (IsDisallowedAttribute(aAttName)) return nsnull; return nsSMILAnimationFunction::GetAttr(aAttName, aResult); } + +PRBool +nsSMILSetAnimationFunction::WillReplace() const +{ + return PR_TRUE; +}
--- a/content/smil/nsSMILSetAnimationFunction.h +++ b/content/smil/nsSMILSetAnimationFunction.h @@ -79,13 +79,14 @@ protected: // that value whenever the animation is active (no interpolation or anything). NS_OVERRIDE virtual PRBool TreatSingleValueAsStatic() const { return PR_TRUE; } NS_OVERRIDE virtual PRBool HasAttr(nsIAtom* aAttName) const; 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; }; #endif // NS_SMILSETANIMATIONFUNCTION_H_
--- a/content/smil/nsSMILTimeContainer.cpp +++ b/content/smil/nsSMILTimeContainer.cpp @@ -210,16 +210,24 @@ nsSMILTimeContainer::Sample() mNeedsPauseSample = PR_FALSE; } nsresult 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; nsresult rv = NS_OK; if (mParent) { rv = mParent->AddChild(*this); } @@ -309,16 +317,17 @@ nsSMILTimeContainer::Unlink() mMilestoneEntries.Clear(); } void nsSMILTimeContainer::UpdateCurrentTime() { nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime(); mCurrentTime = now - mParentOffset; + NS_ABORT_IF_FALSE(mCurrentTime >= 0, "Container has negative time"); } void nsSMILTimeContainer::NotifyTimeChange() { // Called when the container time is changed with respect to the document // time. When this happens time dependencies in other time containers need to // re-resolve their times because begin and end times are stored in container
--- a/content/smil/nsSMILTimedElement.cpp +++ b/content/smil/nsSMILTimedElement.cpp @@ -1099,16 +1099,20 @@ nsSMILTimedElement::IsTimeDependent(cons return PR_FALSE; return thisBegin->IsDependentOn(*otherBegin); } 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) { mSeekState = SEEK_NOT_SEEKING; Rewind(); } // Resolve references to other parts of the tree @@ -1117,18 +1121,16 @@ nsSMILTimedElement::BindToTree(nsIConten mBeginSpecs[i]->ResolveReferences(aContextNode); } count = mEndSpecs.Length(); for (PRUint32 j = 0; j < count; ++j) { mEndSpecs[j]->ResolveReferences(aContextNode); } - // Register new milestone - mPrevRegisteredMilestone = sMaxMilestone; RegisterMilestone(); } void nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget) { PRUint32 count = mBeginSpecs.Length(); for (PRUint32 i = 0; i < count; ++i) {
--- a/content/smil/test/smilAnimateMotionValueLists.js +++ b/content/smil/test/smilAnimateMotionValueLists.js @@ -108,17 +108,23 @@ const gInvalidToBy = [ const gValidPath = [ "m0 0 L30 30", "M20,20L10 10", "M20,20 L30, 30h20", "m50 50", "M50 50", "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", ];
--- a/content/smil/test/test_smilAnimateMotionInvalidValues.xhtml +++ b/content/smil/test/test_smilAnimateMotionInvalidValues.xhtml @@ -152,16 +152,17 @@ function main() testAttr("to", gValidToBy, true, false); testAttr("to", gInvalidToBy, false, false); testAttr("by", gValidToBy, true, false); testAttr("by", gInvalidToBy, false, false); testAttr("path", gValidPath, true, false); testAttr("path", gInvalidPath, false, false); + testAttr("path", gValidPathWithErrors, true, false); testMpathElem(gValidPath, true, false); testMpathElem(gInvalidPath, false, false); SimpleTest.finish(); } window.addEventListener("load", main, false);
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/DOMSVGPathSeg.cpp @@ -0,0 +1,1056 @@ +/* -*- 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 "DOMSVGPathSeg.h" +#include "DOMSVGPathSegList.h" +#include "SVGPathSegUtils.h" +#include "SVGAnimatedPathSegList.h" +#include "nsSVGElement.h" +#include "nsIDOMSVGPathSeg.h" +#include "nsDOMError.h" + +// See the architecture comment in DOMSVGPathSegList.h. + +using namespace mozilla; + +// We could use NS_IMPL_CYCLE_COLLECTION_1, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSeg) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSeg) + // We may not belong to a list, so we must null check tmp->mList. + if (tmp->mList) { + tmp->mList->ItemAt(tmp->mListIndex) = nsnull; + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mList) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPathSeg) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPathSeg) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPathSeg) + +DOMCI_DATA(SVGPathSeg, DOMSVGPathSeg) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPathSeg) + NS_INTERFACE_MAP_ENTRY(DOMSVGPathSeg) // pseudo-interface + NS_INTERFACE_MAP_ENTRY(nsIDOMSVGPathSeg) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + + +DOMSVGPathSeg::DOMSVGPathSeg(DOMSVGPathSegList *aList, + PRUint32 aListIndex, + PRBool aIsAnimValItem) + : mList(aList) + , mListIndex(aListIndex) + , mIsAnimValItem(aIsAnimValItem) +{ + // These shifts are in sync with the members in the header. + NS_ABORT_IF_FALSE(aList && + aListIndex < (1U << 31), "bad arg"); + + NS_ABORT_IF_FALSE(IndexIsValid(), "Bad index for DOMSVGPathSeg!"); +} + +DOMSVGPathSeg::DOMSVGPathSeg() + : mList(nsnull) + , mListIndex(0) + , mIsAnimValItem(PR_FALSE) +{ +} + +NS_IMETHODIMP +DOMSVGPathSeg::GetPathSegType(PRUint16 *aPathSegType) +{ + *aPathSegType = PRUint16(Type()); + return NS_OK; +} + +NS_IMETHODIMP +DOMSVGPathSeg::GetPathSegTypeAsLetter(nsAString &aPathSegTypeAsLetter) +{ + aPathSegTypeAsLetter = SVGPathSegUtils::GetPathSegTypeAsLetter(Type()); + return NS_OK; +} + +void +DOMSVGPathSeg::InsertingIntoList(DOMSVGPathSegList *aList, + PRUint32 aListIndex, + PRBool aIsAnimValItem) +{ + NS_ABORT_IF_FALSE(!HasOwner(), "Inserting item that is already in a list"); + + mList = aList; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + + NS_ABORT_IF_FALSE(IndexIsValid(), "Bad index for DOMSVGPathSeg!"); +} + +void +DOMSVGPathSeg::RemovingFromList() +{ + PRUint32 argCount = SVGPathSegUtils::ArgCountForType(Type()); + // InternalItem() + 1, because the args come after the encoded seg type + memcpy(PtrToMemberArgs(), InternalItem() + 1, argCount * sizeof(float)); + mList = nsnull; + mIsAnimValItem = PR_FALSE; +} + +void +DOMSVGPathSeg::ToSVGPathSegEncodedData(float* aRaw) +{ + NS_ABORT_IF_FALSE(aRaw, "null pointer"); + PRUint32 argCount = SVGPathSegUtils::ArgCountForType(Type()); + if (IsInList()) { + // 1 + argCount, because we're copying the encoded seg type and args + memcpy(aRaw, InternalItem(), (1 + argCount) * sizeof(float)); + } else { + aRaw[0] = SVGPathSegUtils::EncodeType(Type()); + // aRaw + 1, because the args go after the encoded seg type + memcpy(aRaw + 1, PtrToMemberArgs(), argCount * sizeof(float)); + } +} + +float* +DOMSVGPathSeg::InternalItem() +{ + PRUint32 dataIndex = mList->mItems[mListIndex].mInternalDataIndex; + return &(mList->InternalList().mData[dataIndex]); +} + +#ifdef DEBUG +PRBool +DOMSVGPathSeg::IndexIsValid() +{ + SVGAnimatedPathSegList *alist = Element()->GetAnimPathSegList(); + return (mIsAnimValItem && + mListIndex < alist->GetAnimValue().CountItems()) || + (!mIsAnimValItem && + mListIndex < alist->GetBaseValue().CountItems()); +} +#endif + + +//////////////////////////////////////////////////////////////////////// +// Implementation of DOMSVGPathSeg sub-classes below this point + +#define CHECK_ARG_COUNT_IN_SYNC(segType) \ + NS_ABORT_IF_FALSE(NS_ARRAY_LENGTH(mArgs) == \ + SVGPathSegUtils::ArgCountForType(PRUint32(segType)) || \ + PRUint32(segType) == nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH, \ + "Arg count/array size out of sync") + +#define IMPL_SVGPATHSEG_SUBCLASS_COMMON(segName, segType) \ + DOMSVGPathSeg##segName(const float *aArgs) \ + : DOMSVGPathSeg() \ + { \ + CHECK_ARG_COUNT_IN_SYNC(segType); \ + memcpy(mArgs, aArgs, \ + SVGPathSegUtils::ArgCountForType(PRUint32(segType)) * sizeof(float)); \ + } \ + DOMSVGPathSeg##segName(DOMSVGPathSegList *aList, \ + PRUint32 aListIndex, \ + PRBool aIsAnimValItem) \ + : DOMSVGPathSeg(aList, aListIndex, aIsAnimValItem) \ + { \ + CHECK_ARG_COUNT_IN_SYNC(segType); \ + } \ + /* From DOMSVGPathSeg: */ \ + virtual PRUint32 \ + Type() const \ + { \ + return segType; \ + } \ + virtual DOMSVGPathSeg* \ + Clone() \ + { \ + /* InternalItem() + 1, because we're skipping the encoded seg type */ \ + float *args = IsInList() ? InternalItem() + 1 : mArgs; \ + return new DOMSVGPathSeg##segName(args); \ + } \ + virtual float* \ + PtrToMemberArgs() \ + { \ + return mArgs; \ + } + +#define IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(segName) \ + /* Forward to the CYCLE_COLLECTING_ADDREF on our base class */ \ + NS_IMPL_ADDREF_INHERITED(DOMSVGPathSeg##segName, DOMSVGPathSeg) \ + NS_IMPL_RELEASE_INHERITED(DOMSVGPathSeg##segName, DOMSVGPathSeg) \ + \ + DOMCI_DATA(SVGPathSeg##segName, DOMSVGPathSeg##segName) \ + \ + NS_INTERFACE_MAP_BEGIN(DOMSVGPathSeg##segName) \ + NS_INTERFACE_MAP_ENTRY(nsIDOMSVGPathSeg##segName) \ + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGPathSeg##segName) \ + NS_INTERFACE_MAP_END_INHERITING(DOMSVGPathSeg) + +#define IMPL_PROP_WITH_TYPE(segName, propName, index, type) \ + /* attribute type propName; */ \ + NS_IMETHODIMP \ + DOMSVGPathSeg##segName::Get##propName(type *a##propName) \ + { \ + if (mIsAnimValItem && HasOwner()) { \ + Element()->FlushAnimations(); /* May make HasOwner() == PR_FALSE */ \ + } \ + *a##propName = type(HasOwner() ? InternalItem()[1+index] : mArgs[index]); \ + return NS_OK; \ + } \ + NS_IMETHODIMP \ + DOMSVGPathSeg##segName::Set##propName(type a##propName) \ + { \ + if (mIsAnimValItem) { \ + return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; \ + } \ + NS_ENSURE_FINITE(float(a##propName), NS_ERROR_ILLEGAL_VALUE); \ + if (HasOwner()) { \ + InternalItem()[1+index] = float(a##propName); \ + NS_ABORT_IF_FALSE(IsInList(), "DidChangePathSegList() is wrong"); \ + Element()->DidChangePathSegList(PR_TRUE); \ + if (mList->AttrIsAnimating()) { \ + Element()->AnimationNeedsResample(); \ + } \ + } else { \ + mArgs[index] = float(a##propName); \ + } \ + return NS_OK; \ + } + +// For float, the normal type of arguments +#define IMPL_FLOAT_PROP(segName, propName, index) \ + IMPL_PROP_WITH_TYPE(segName, propName, index, float) + +// For the boolean flags in arc commands +#define IMPL_BOOL_PROP(segName, propName, index) \ + IMPL_PROP_WITH_TYPE(segName, propName, index, PRBool) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegClosePath + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegClosePath +{ +public: + DOMSVGPathSegClosePath() + : DOMSVGPathSeg() + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCLOSEPATH + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ClosePath, PATHSEG_CLOSEPATH) + +protected: + // To allow IMPL_SVGPATHSEG_SUBCLASS_COMMON above to compile we need an + // mArgs, but since C++ doesn't allow zero-sized arrays we need to give it + // one (unused) element. + float mArgs[1]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(ClosePath) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegMovetoAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegMovetoAbs +{ +public: + DOMSVGPathSegMovetoAbs(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGMOVETOABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(MovetoAbs, PATHSEG_MOVETO_ABS) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(MovetoAbs) + +IMPL_FLOAT_PROP(MovetoAbs, X, 0) +IMPL_FLOAT_PROP(MovetoAbs, Y, 1) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegMovetoRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegMovetoRel +{ +public: + DOMSVGPathSegMovetoRel(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGMOVETOREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(MovetoRel, PATHSEG_MOVETO_REL) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(MovetoRel) + +IMPL_FLOAT_PROP(MovetoRel, X, 0) +IMPL_FLOAT_PROP(MovetoRel, Y, 1) + + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoAbs +{ +public: + DOMSVGPathSegLinetoAbs(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoAbs, PATHSEG_LINETO_ABS) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoAbs) + +IMPL_FLOAT_PROP(LinetoAbs, X, 0) +IMPL_FLOAT_PROP(LinetoAbs, Y, 1) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoRel +{ +public: + DOMSVGPathSegLinetoRel(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoRel, PATHSEG_LINETO_REL) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoRel) + +IMPL_FLOAT_PROP(LinetoRel, X, 0) +IMPL_FLOAT_PROP(LinetoRel, Y, 1) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoCubicAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoCubicAbs +{ +public: + DOMSVGPathSegCurvetoCubicAbs(float x1, float y1, + float x2, float y2, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x2; + mArgs[3] = y2; + mArgs[4] = x; + mArgs[5] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOCUBICABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoCubicAbs, PATHSEG_CURVETO_CUBIC_ABS) + +protected: + float mArgs[6]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoCubicAbs) + +IMPL_FLOAT_PROP(CurvetoCubicAbs, X1, 0) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y1, 1) +IMPL_FLOAT_PROP(CurvetoCubicAbs, X2, 2) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y2, 3) +IMPL_FLOAT_PROP(CurvetoCubicAbs, X, 4) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y, 5) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoCubicRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoCubicRel +{ +public: + DOMSVGPathSegCurvetoCubicRel(float x1, float y1, + float x2, float y2, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x2; + mArgs[3] = y2; + mArgs[4] = x; + mArgs[5] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOCUBICREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoCubicRel, PATHSEG_CURVETO_CUBIC_REL) + +protected: + float mArgs[6]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoCubicRel) + +IMPL_FLOAT_PROP(CurvetoCubicRel, X1, 0) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y1, 1) +IMPL_FLOAT_PROP(CurvetoCubicRel, X2, 2) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y2, 3) +IMPL_FLOAT_PROP(CurvetoCubicRel, X, 4) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y, 5) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoQuadraticAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoQuadraticAbs +{ +public: + DOMSVGPathSegCurvetoQuadraticAbs(float x1, float y1, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x; + mArgs[3] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOQUADRATICABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoQuadraticAbs, PATHSEG_CURVETO_QUADRATIC_ABS) + +protected: + float mArgs[4]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoQuadraticAbs) + +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, X1, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, Y1, 1) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, X, 2) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, Y, 3) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoQuadraticRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoQuadraticRel +{ +public: + DOMSVGPathSegCurvetoQuadraticRel(float x1, float y1, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x; + mArgs[3] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOQUADRATICREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoQuadraticRel, PATHSEG_CURVETO_QUADRATIC_REL) + +protected: + float mArgs[4]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoQuadraticRel) + +IMPL_FLOAT_PROP(CurvetoQuadraticRel, X1, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, Y1, 1) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, X, 2) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, Y, 3) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegArcAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegArcAbs +{ +public: + DOMSVGPathSegArcAbs(float r1, float r2, float angle, + PRBool largeArcFlag, PRBool sweepFlag, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = r1; + mArgs[1] = r2; + mArgs[2] = angle; + mArgs[3] = largeArcFlag; + mArgs[4] = sweepFlag; + mArgs[5] = x; + mArgs[6] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGARCABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ArcAbs, PATHSEG_ARC_ABS) + +protected: + float mArgs[7]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(ArcAbs) + +IMPL_FLOAT_PROP(ArcAbs, R1, 0) +IMPL_FLOAT_PROP(ArcAbs, R2, 1) +IMPL_FLOAT_PROP(ArcAbs, Angle, 2) +IMPL_BOOL_PROP(ArcAbs, LargeArcFlag, 3) +IMPL_BOOL_PROP(ArcAbs, SweepFlag, 4) +IMPL_FLOAT_PROP(ArcAbs, X, 5) +IMPL_FLOAT_PROP(ArcAbs, Y, 6) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegArcRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegArcRel +{ +public: + DOMSVGPathSegArcRel(float r1, float r2, float angle, + PRBool largeArcFlag, PRBool sweepFlag, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = r1; + mArgs[1] = r2; + mArgs[2] = angle; + mArgs[3] = largeArcFlag; + mArgs[4] = sweepFlag; + mArgs[5] = x; + mArgs[6] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGARCREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ArcRel, PATHSEG_ARC_REL) + +protected: + float mArgs[7]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(ArcRel) + +IMPL_FLOAT_PROP(ArcRel, R1, 0) +IMPL_FLOAT_PROP(ArcRel, R2, 1) +IMPL_FLOAT_PROP(ArcRel, Angle, 2) +IMPL_BOOL_PROP(ArcRel, LargeArcFlag, 3) +IMPL_BOOL_PROP(ArcRel, SweepFlag, 4) +IMPL_FLOAT_PROP(ArcRel, X, 5) +IMPL_FLOAT_PROP(ArcRel, Y, 6) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoHorizontalAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoHorizontalAbs +{ +public: + DOMSVGPathSegLinetoHorizontalAbs(float x) + : DOMSVGPathSeg() + { + mArgs[0] = x; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOHORIZONTALABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoHorizontalAbs, PATHSEG_LINETO_HORIZONTAL_ABS) + +protected: + float mArgs[1]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoHorizontalAbs) + +IMPL_FLOAT_PROP(LinetoHorizontalAbs, X, 0) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoHorizontalRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoHorizontalRel +{ +public: + DOMSVGPathSegLinetoHorizontalRel(float x) + : DOMSVGPathSeg() + { + mArgs[0] = x; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOHORIZONTALREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoHorizontalRel, PATHSEG_LINETO_HORIZONTAL_REL) + +protected: + float mArgs[1]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoHorizontalRel) + +IMPL_FLOAT_PROP(LinetoHorizontalRel, X, 0) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoVerticalAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoVerticalAbs +{ +public: + DOMSVGPathSegLinetoVerticalAbs(float y) + : DOMSVGPathSeg() + { + mArgs[0] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOVERTICALABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoVerticalAbs, PATHSEG_LINETO_VERTICAL_ABS) + +protected: + float mArgs[1]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoVerticalAbs) + +IMPL_FLOAT_PROP(LinetoVerticalAbs, Y, 0) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegLinetoVerticalRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegLinetoVerticalRel +{ +public: + DOMSVGPathSegLinetoVerticalRel(float y) + : DOMSVGPathSeg() + { + mArgs[0] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGLINETOVERTICALREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoVerticalRel, PATHSEG_LINETO_VERTICAL_REL) + +protected: + float mArgs[1]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(LinetoVerticalRel) + +IMPL_FLOAT_PROP(LinetoVerticalRel, Y, 0) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoCubicSmoothAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoCubicSmoothAbs +{ +public: + DOMSVGPathSegCurvetoCubicSmoothAbs(float x2, float y2, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x2; + mArgs[1] = y2; + mArgs[2] = x; + mArgs[3] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOCUBICSMOOTHABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoCubicSmoothAbs, PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) + +protected: + float mArgs[4]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoCubicSmoothAbs) + +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, X2, 0) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, Y2, 1) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, X, 2) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, Y, 3) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoCubicSmoothRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoCubicSmoothRel +{ +public: + DOMSVGPathSegCurvetoCubicSmoothRel(float x2, float y2, + float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x2; + mArgs[1] = y2; + mArgs[2] = x; + mArgs[3] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOCUBICSMOOTHREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoCubicSmoothRel, PATHSEG_CURVETO_CUBIC_SMOOTH_REL) + +protected: + float mArgs[4]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoCubicSmoothRel) + +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, X2, 0) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, Y2, 1) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, X, 2) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, Y, 3) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoQuadraticSmoothAbs + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoQuadraticSmoothAbs +{ +public: + DOMSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOQUADRATICSMOOTHABS + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoQuadraticSmoothAbs, PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoQuadraticSmoothAbs) + +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothAbs, X, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothAbs, Y, 1) + + +//////////////////////////////////////////////////////////////////////// + +class DOMSVGPathSegCurvetoQuadraticSmoothRel + : public DOMSVGPathSeg + , public nsIDOMSVGPathSegCurvetoQuadraticSmoothRel +{ +public: + DOMSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) + : DOMSVGPathSeg() + { + mArgs[0] = x; + mArgs[1] = y; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMSVGPATHSEGCURVETOQUADRATICSMOOTHREL + IMPL_SVGPATHSEG_SUBCLASS_COMMON(CurvetoQuadraticSmoothRel, PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL) + +protected: + float mArgs[2]; +}; + +IMPL_NSISUPPORTS_SVGPATHSEG_SUBCLASS(CurvetoQuadraticSmoothRel) + +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothRel, X, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothRel, Y, 1) + + + +// This must come after DOMSVGPathSegClosePath et. al. have been declared. +/* static */ DOMSVGPathSeg* +DOMSVGPathSeg::CreateFor(DOMSVGPathSegList *aList, + PRUint32 aListIndex, + PRBool aIsAnimValItem) +{ + PRUint32 dataIndex = aList->mItems[aListIndex].mInternalDataIndex; + float *data = &aList->InternalList().mData[dataIndex]; + PRUint32 type = SVGPathSegUtils::DecodeType(data[0]); + + switch (type) + { + case nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH: + return new DOMSVGPathSegClosePath(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS: + return new DOMSVGPathSegMovetoAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL: + return new DOMSVGPathSegMovetoRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS: + return new DOMSVGPathSegLinetoAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL: + return new DOMSVGPathSegLinetoRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS: + return new DOMSVGPathSegCurvetoCubicAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL: + return new DOMSVGPathSegCurvetoCubicRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS: + return new DOMSVGPathSegCurvetoQuadraticAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL: + return new DOMSVGPathSegCurvetoQuadraticRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS: + return new DOMSVGPathSegArcAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_ARC_REL: + return new DOMSVGPathSegArcRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS: + return new DOMSVGPathSegLinetoHorizontalAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL: + return new DOMSVGPathSegLinetoHorizontalRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS: + return new DOMSVGPathSegLinetoVerticalAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL: + return new DOMSVGPathSegLinetoVerticalRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + return new DOMSVGPathSegCurvetoCubicSmoothAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + return new DOMSVGPathSegCurvetoCubicSmoothRel(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + return new DOMSVGPathSegCurvetoQuadraticSmoothAbs(aList, aListIndex, aIsAnimValItem); + case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + return new DOMSVGPathSegCurvetoQuadraticSmoothRel(aList, aListIndex, aIsAnimValItem); + default: + NS_NOTREACHED("Invalid path segment type"); + return nsnull; + } +} + + + + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegClosePath() +{ + return new DOMSVGPathSegClosePath(); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegMovetoAbs(float x, float y) +{ + return new DOMSVGPathSegMovetoAbs(x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegMovetoRel(float x, float y) +{ + return new DOMSVGPathSegMovetoRel(x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoAbs(float x, float y) +{ + return new DOMSVGPathSegLinetoAbs(x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoRel(float x, float y) +{ + return new DOMSVGPathSegLinetoRel(x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoCubicAbs(float x, float y, + float x1, float y1, + float x2, float y2) +{ + // Note that we swap from DOM API argument order to the argument order used + // in the <path> element's 'd' attribute (i.e. we put the arguments for the + // end point of the segment last instead of first). + + return new DOMSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoCubicRel(float x, float y, + float x1, float y1, + float x2, float y2) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoQuadraticAbs(float x, float y, + float x1, float y1) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegCurvetoQuadraticAbs(x1, y1, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoQuadraticRel(float x, float y, + float x1, float y1) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegCurvetoQuadraticRel(x1, y1, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegArcAbs(float x, float y, + float r1, float r2, float angle, + PRBool largeArcFlag, PRBool sweepFlag) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegArcAbs(r1, r2, angle, largeArcFlag, sweepFlag, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegArcRel(float x, float y, + float r1, float r2, float angle, + PRBool largeArcFlag, PRBool sweepFlag) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegArcRel(r1, r2, angle, largeArcFlag, sweepFlag, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoHorizontalAbs(float x) +{ + return new DOMSVGPathSegLinetoHorizontalAbs(x); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoHorizontalRel(float x) +{ + return new DOMSVGPathSegLinetoHorizontalRel(x); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoVerticalAbs(float y) +{ + return new DOMSVGPathSegLinetoVerticalAbs(y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegLinetoVerticalRel(float y) +{ + return new DOMSVGPathSegLinetoVerticalRel(y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoCubicSmoothAbs(float x, float y, + float x2, float y2) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoCubicSmoothRel(float x, float y, + float x2, float y2) +{ + // See comment in NS_NewSVGPathSegCurvetoCubicAbs! + + return new DOMSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) +{ + return new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x, y); +} + +nsIDOMSVGPathSeg* +NS_NewSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) +{ + return new DOMSVGPathSegCurvetoQuadraticSmoothRel(x, y); +} +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/DOMSVGPathSeg.h @@ -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__
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/DOMSVGPathSegList.cpp @@ -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()); + } +} +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/DOMSVGPathSegList.h @@ -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__
--- a/content/svg/content/src/Makefile.in +++ b/content/svg/content/src/Makefile.in @@ -47,16 +47,18 @@ include $(DEPTH)/config/autoconf.mk MODULE = content LIBRARY_NAME = gkcontentsvg_s LIBXUL_LIBRARY = 1 CPPSRCS = \ DOMSVGAnimatedLengthList.cpp \ DOMSVGLength.cpp \ DOMSVGLengthList.cpp \ + DOMSVGPathSeg.cpp \ + DOMSVGPathSegList.cpp \ nsDOMSVGZoomEvent.cpp \ nsDOMSVGEvent.cpp \ nsSVGAElement.cpp \ nsSVGAltGlyphElement.cpp \ nsSVGAngle.cpp \ nsSVGAnimatedNumberList.cpp \ nsSVGAnimatedTransformList.cpp \ nsSVGBoolean.cpp \ @@ -85,18 +87,16 @@ CPPSRCS = \ nsSVGMatrix.cpp \ nsSVGMetadataElement.cpp \ nsSVGNumber.cpp \ nsSVGNumber2.cpp \ nsSVGNumberList.cpp \ nsSVGPathDataParser.cpp \ nsSVGPathElement.cpp \ nsSVGPathGeometryElement.cpp \ - nsSVGPathSeg.cpp \ - nsSVGPathSegList.cpp \ nsSVGPatternElement.cpp \ nsSVGPoint.cpp \ nsSVGPointList.cpp \ nsSVGPolyElement.cpp \ nsSVGPolygonElement.cpp \ nsSVGPolylineElement.cpp \ nsSVGPreserveAspectRatio.cpp \ nsSVGScriptElement.cpp \ @@ -118,18 +118,21 @@ CPPSRCS = \ nsSVGTitleElement.cpp \ nsSVGTransform.cpp \ nsSVGTransformList.cpp \ nsSVGTransformListParser.cpp \ nsSVGUseElement.cpp \ nsSVGValue.cpp \ nsSVGViewBox.cpp \ SVGAnimatedLengthList.cpp \ + SVGAnimatedPathSegList.cpp \ SVGLength.cpp \ SVGLengthList.cpp \ + SVGPathData.cpp \ + SVGPathSegUtils.cpp \ $(NULL) ifdef MOZ_SMIL CPPSRCS += nsSVGAnimateElement.cpp \ nsSVGAnimateTransformElement.cpp \ nsSVGAnimateMotionElement.cpp \ nsSVGAnimationElement.cpp \ nsSVGMpathElement.cpp \ @@ -137,16 +140,17 @@ CPPSRCS += nsSVGAnimateElement.cpp \ nsSVGTransformSMILType.cpp \ nsSVGTransformSMILAttr.cpp \ SVGLengthListSMILType.cpp \ SVGMotionSMILType.cpp \ SVGMotionSMILAttr.cpp \ SVGMotionSMILAnimationFunction.cpp \ SVGMotionSMILPathUtils.cpp \ SVGOrientSMILType.cpp \ + SVGPathSegListSMILType.cpp \ SVGViewBoxSMILType.cpp \ $(NULL) endif include $(topsrcdir)/config/config.mk # we don't want the shared lib, but we want to force the creation of a static lib. FORCE_STATIC_LIB = 1
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGAnimatedPathSegList.cpp @@ -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 +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGAnimatedPathSegList.h @@ -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__
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp +++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp @@ -36,17 +36,16 @@ * ***** END LICENSE BLOCK ***** */ #include "SVGMotionSMILAnimationFunction.h" #include "nsSMILParserUtils.h" #include "nsSVGAngle.h" #include "SVGMotionSMILType.h" #include "SVGMotionSMILPathUtils.h" #include "nsSVGPathDataParser.h" -#include "nsSVGPathSeg.h" #include "nsSVGPathElement.h" // for nsSVGPathList #include "nsSVGMpathElement.h" namespace mozilla { SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction() : mRotateType(eRotateType_Explicit), mRotateAngle(0.0f), @@ -248,92 +247,54 @@ void SVGMotionSMILAnimationFunction:: RebuildPathAndVerticesFromMpathElem(nsSVGMpathElement* aMpathElem) { mPathSourceType = ePathSourceType_Mpath; // 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())); } } } } void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() { const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue(); 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:: RebuildPathAndVertices(const nsIContent* aTargetElement) { NS_ABORT_IF_FALSE(mIsPathStale, "rebuilding path when it isn't stale");
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.h +++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.h @@ -90,17 +90,16 @@ protected: void CheckKeyPoints(); nsresult SetKeyPoints(const nsAString& aKeyPoints, nsAttrValue& aResult); void UnsetKeyPoints(); nsresult SetRotate(const nsAString& aRotate, nsAttrValue& aResult); void UnsetRotate(); // Helpers for GetValues void MarkStaleIfAttributeAffectsPath(nsIAtom* aAttribute); - nsresult SetPathVerticesFromPathString(const nsAString& aPathSpec); void RebuildPathAndVertices(const nsIContent* aContextElem); void RebuildPathAndVerticesFromMpathElem(nsSVGMpathElement* aMpathElem); void RebuildPathAndVerticesFromPathAttr(); void RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem); PRBool GenerateValuesForPathAndPoints(gfxFlattenedPath* aPath, PRBool aIsKeyPoints, nsTArray<double>& aPointDistances, nsTArray<nsSMILValue>& aResult);
--- a/content/svg/content/src/SVGMotionSMILType.cpp +++ b/content/svg/content/src/SVGMotionSMILType.cpp @@ -39,17 +39,16 @@ #include "SVGMotionSMILType.h" #include "nsSMILValue.h" #include "nsDebug.h" #include "nsSVGTransform.h" #include "nsSVGAngle.h" #include "nsIDOMSVGAngle.h" #include "nsSVGPathElement.h" -#include "nsSVGPathSeg.h" #include "nsIDOMSVGPathSeg.h" #include "nsIDOMSVGPathSegList.h" #include "nsMathUtils.h" #include <math.h> namespace mozilla { /*static*/ SVGMotionSMILType SVGMotionSMILType::sSingleton;
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathData.cpp @@ -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; +} +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathData.h @@ -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__
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathSegListSMILType.cpp @@ -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; +} +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathSegListSMILType.h @@ -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_
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathSegUtils.cpp @@ -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); +} +
new file mode 100644 --- /dev/null +++ b/content/svg/content/src/SVGPathSegUtils.h @@ -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__
--- a/content/svg/content/src/crashtests/crashtests.list +++ b/content/svg/content/src/crashtests/crashtests.list @@ -41,17 +41,17 @@ load 414188-1.svg load 427325-1.svg load 428228-1.svg load 428841-1.svg load 435209-1.svg 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 load 579356-2.svg load 595608-1.svg load 601251-1.html load 601406-1.svg load 603145-1.svg
--- a/content/svg/content/src/nsSVGAnimateMotionElement.h +++ b/content/svg/content/src/nsSVGAnimateMotionElement.h @@ -69,16 +69,21 @@ public: virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const; // nsISMILAnimationElement virtual nsSMILAnimationFunction& AnimationFunction(); virtual PRBool GetTargetAttributeName(PRInt32 *aNamespaceID, 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(); } virtual nsXPCClassInfo* GetClassInfo(); }; #endif // NS_SVGANIMATEMOTIONELEMENT_H_
--- a/content/svg/content/src/nsSVGAnimationElement.cpp +++ b/content/svg/content/src/nsSVGAnimationElement.cpp @@ -453,16 +453,18 @@ nsSVGAnimationElement::BeginElement(void { return BeginElementAt(0.f); } /* void beginElementAt (in float offset); */ 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); if (NS_FAILED(rv)) return rv; AnimationNeedsResample(); @@ -475,16 +477,18 @@ nsSVGAnimationElement::EndElement(void) { return EndElementAt(0.f); } /* void endElementAt (in float offset); */ 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; AnimationNeedsResample(); return NS_OK; }
--- a/content/svg/content/src/nsSVGElement.cpp +++ b/content/svg/content/src/nsSVGElement.cpp @@ -70,16 +70,17 @@ #include "nsSVGNumber2.h" #include "nsSVGInteger.h" #include "nsSVGAngle.h" #include "nsSVGBoolean.h" #include "nsSVGEnum.h" #include "nsSVGViewBox.h" #include "nsSVGString.h" #include "SVGAnimatedLengthList.h" +#include "SVGAnimatedPathSegList.h" #include "nsIDOMSVGUnitTypes.h" #include "nsIDOMSVGNumberList.h" #include "nsIDOMSVGAnimatedNumberList.h" #include "nsIDOMSVGPointList.h" #include "nsIDOMSVGAnimatedPoints.h" #include "nsIDOMSVGTransformList.h" #include "nsIDOMSVGAnimTransformList.h" #include "nsIDOMSVGAnimatedRect.h" @@ -174,16 +175,19 @@ nsSVGElement::Init() } LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); for (i = 0; i < lengthListInfo.mLengthListCount; i++) { 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++) { stringInfo.Reset(i); } return NS_OK; } @@ -369,16 +373,32 @@ nsSVGElement::ParseAttribute(PRInt32 aNa } foundMatch = PR_TRUE; break; } } } 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(); for (i = 0; i < numberInfo.mNumberCount; i++) { if (aAttribute == *numberInfo.mNumberInfo[i].mName) { if (i + 1 < numberInfo.mNumberCount && aAttribute == *numberInfo.mNumberInfo[i + 1].mName) { rv = ParseNumberOptionalNumber(aValue, i, i + 1); if (NS_FAILED(rv)) { @@ -564,16 +584,28 @@ nsSVGElement::UnsetAttr(PRInt32 aNamespa DidChangeLengthList(i, PR_FALSE); foundMatch = PR_TRUE; break; } } } 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(); for (PRUint32 i = 0; i < numInfo.mNumberCount; i++) { if (aName == *numInfo.mNumberInfo[i].mName) { if (i + 1 < numInfo.mNumberCount && aName == *numInfo.mNumberInfo[i + 1].mName) { // found a number-optional-number @@ -1541,16 +1573,46 @@ nsSVGElement::GetAnimatedLengthList(PRUi LengthListAttributesInfo info = GetLengthListInfo(); if (aAttrEnum < info.mLengthListCount) { return &(info.mLengthLists[aAttrEnum]); } NS_NOTREACHED("Bad attrEnum"); 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() { return NumberAttributesInfo(nsnull, nsnull, 0); } void nsSVGElement::NumberAttributesInfo::Reset(PRUint8 aAttrEnum) { @@ -2210,16 +2272,26 @@ nsSVGElement::GetAnimatedAttr(PRInt32 aN for (PRUint32 i = 0; i < info.mStringCount; i++) { if (aNamespaceID == info.mStringInfo[i].mNamespaceID && aName == *info.mStringInfo[i].mName) { return info.mStrings[i].ToSMILAttr(this); } } } + // PathSegLists: + { + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList *segList = GetAnimPathSegList(); + if (segList) { + return segList->ToSMILAttr(this); + } + } + } + // Mapped attributes: if (IsAttributeMapped(aName)) { nsCSSProperty prop = nsCSSProps::LookupProperty(nsDependentAtomString(aName)); // Check IsPropertyAnimatable to avoid attributes that... // - map to explicitly unanimatable properties (e.g. 'direction') // - map to unsupported attributes (e.g. 'glyph-orientation-horizontal') if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
--- a/content/svg/content/src/nsSVGElement.h +++ b/content/svg/content/src/nsSVGElement.h @@ -69,29 +69,34 @@ class nsSVGEnum; struct nsSVGEnumMapping; class nsSVGViewBox; class nsSVGPreserveAspectRatio; class nsSVGString; struct gfxMatrix; namespace mozilla { class SVGAnimatedLengthList; class SVGUserUnitList; +class SVGAnimatedPathSegList; } typedef nsStyledElement nsSVGElementBase; class nsSVGElement : public nsSVGElementBase, // nsIContent public nsISVGValueObserver // :nsISupportsWeakReference { protected: nsSVGElement(already_AddRefed<nsINodeInfo> aNodeInfo); nsresult Init(); virtual ~nsSVGElement(); public: + typedef mozilla::SVGUserUnitList SVGUserUnitList; + typedef mozilla::SVGAnimatedLengthList SVGAnimatedLengthList; + typedef mozilla::SVGAnimatedPathSegList SVGAnimatedPathSegList; + // nsISupports NS_DECL_ISUPPORTS_INHERITED // nsIContent interface methods virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, PRBool aCompileEventHandlers); @@ -162,47 +167,63 @@ public: virtual void DidChangeNumber(PRUint8 aAttrEnum, PRBool aDoSetAttr); virtual void DidChangeInteger(PRUint8 aAttrEnum, PRBool aDoSetAttr); virtual void DidChangeAngle(PRUint8 aAttrEnum, PRBool aDoSetAttr); virtual void DidChangeBoolean(PRUint8 aAttrEnum, PRBool aDoSetAttr); virtual void DidChangeEnum(PRUint8 aAttrEnum, PRBool aDoSetAttr); 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); virtual void DidAnimateNumber(PRUint8 aAttrEnum); virtual void DidAnimateInteger(PRUint8 aAttrEnum); virtual void DidAnimateAngle(PRUint8 aAttrEnum); virtual void DidAnimateBoolean(PRUint8 aAttrEnum); virtual void DidAnimateEnum(PRUint8 aAttrEnum); 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(); 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); virtual PRBool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute, const nsAString& aValue, nsAttrValue& aResult); static nsresult ReportAttributeParseFailure(nsIDocument* aDocument, nsIAtom* aAttribute, const nsAString& aValue); @@ -352,21 +373,21 @@ protected: * is false. This flag is fed down to SVGLengthListSMILType so it can * determine if it can sensibly animate from-to lists of different lengths, * which is desirable in the case of dx and dy. */ PRPackedBool mCouldZeroPadList; }; struct LengthListAttributesInfo { - mozilla::SVGAnimatedLengthList* mLengthLists; + SVGAnimatedLengthList* mLengthLists; LengthListInfo* mLengthListInfo; PRUint32 mLengthListCount; - LengthListAttributesInfo(mozilla::SVGAnimatedLengthList *aLengthLists, + LengthListAttributesInfo(SVGAnimatedLengthList *aLengthLists, LengthListInfo *aLengthListInfo, PRUint32 aLengthListCount) : mLengthLists(aLengthLists) , mLengthListInfo(aLengthListInfo) , mLengthListCount(aLengthListCount) {} void Reset(PRUint8 aAttrEnum);
--- a/content/svg/content/src/nsSVGPathDataParser.cpp +++ b/content/svg/content/src/nsSVGPathDataParser.cpp @@ -33,23 +33,24 @@ * 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 "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(); } //---------------------------------------------------------------------- nsresult nsSVGPathDataParser::MatchCoordPair(float* aX, float* aY) @@ -861,585 +862,259 @@ PRBool nsSVGPathDataParser::IsTokenEllip { return IsTokenNonNegativeNumberStarter(); } //----------------------------------------------------------------------- -// --------------------------------------------------------------- -// 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) { double ta = atan2(uy, ux); double tb = atan2(vy, vx); if (tb >= ta) return tb-ta; 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 - mRy*mRy*x1dash*x1dash; 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; root = 0.0; } else { root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) * sqrt( numerator/(mRx*mRx*y1dash*y1dash + mRy*mRy*x1dash*x1dash) ); } 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); if (!sweepFlag && dtheta>0) dtheta -= 2.0*M_PI; else if (sweepFlag && dtheta<0) dtheta += 2.0*M_PI; // Convert into cubic bezier segments <= 90deg mNumSegs = static_cast<int>(ceil(fabs(dtheta/(M_PI/2.0)))); 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); +} +
--- a/content/svg/content/src/nsSVGPathDataParser.h +++ b/content/svg/content/src/nsSVGPathDataParser.h @@ -39,19 +39,24 @@ #ifndef __NS_SVGPATHDATAPARSER_H__ #define __NS_SVGPATHDATAPARSER_H__ #include "nsSVGDataParser.h" #include "nsCOMPtr.h" #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 // can be found in SVG CR 20001102, chapter 8. class nsSVGPathDataParser : public nsSVGDataParser { protected: @@ -135,20 +140,40 @@ protected: nsresult MatchEllipticalArcArgSeq(PRBool absCoords); nsresult MatchEllipticalArcArg(float* x, float* y, float* r1, float* r2, float* angle, PRBool* largeArcFlag, PRBool* sweepFlag); PRBool IsTokenEllipticalArcArgStarter(); }; +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: 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); @@ -160,83 +185,12 @@ protected: 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: - 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__
--- a/content/svg/content/src/nsSVGPathElement.cpp +++ b/content/svg/content/src/nsSVGPathElement.cpp @@ -32,29 +32,31 @@ * 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 "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" #include "nsSVGPathElement.h" #include "nsISVGValueUtils.h" #include "nsSVGUtils.h" #include "nsSVGPoint.h" #include "gfxContext.h" #include "gfxPlatform.h" +using namespace mozilla; + nsSVGElement::NumberInfo nsSVGPathElement::sNumberInfo = { &nsGkAtoms::pathLength, 0 }; NS_IMPL_NS_NEW_SVG_ELEMENT(Path) //---------------------------------------------------------------------- // nsISupports methods @@ -73,22 +75,16 @@ NS_INTERFACE_MAP_END_INHERITING(nsSVGPat //---------------------------------------------------------------------- // Implementation nsSVGPathElement::nsSVGPathElement(already_AddRefed<nsINodeInfo> aNodeInfo) : nsSVGPathElementBase(aNodeInfo) { } -nsSVGPathElement::~nsSVGPathElement() -{ - if (mSegments) - NS_REMOVE_SVGVALUE_OBSERVER(mSegments); -} - //---------------------------------------------------------------------- // nsIDOMNode methods NS_IMPL_ELEMENT_CLONE_WITH_INIT(nsSVGPathElement) //---------------------------------------------------------------------- // nsIDOMSVGPathElement methods: @@ -136,48 +132,17 @@ nsSVGPathElement::GetPointAtLength(float return NS_NewSVGPoint(_retval, flat->FindPoint(gfxPoint(distance, 0))); } /* unsigned long getPathSegAtLength (in float distance); */ 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; } /* nsIDOMSVGPathSegClosePath createSVGPathSegClosePath (); */ NS_IMETHODIMP nsSVGPathElement::CreateSVGPathSegClosePath(nsIDOMSVGPathSegClosePath **_retval) { nsIDOMSVGPathSeg* seg = NS_NewSVGPathSegClosePath(); @@ -362,74 +327,49 @@ NS_IMETHODIMP nsSVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y, nsIDOMSVGPathSegCurvetoQuadraticSmoothRel **_retval) { NS_ENSURE_FINITE2(x, y, NS_ERROR_ILLEGAL_VALUE); nsIDOMSVGPathSeg* seg = NS_NewSVGPathSegCurvetoQuadraticSmoothRel(x, y); NS_ENSURE_TRUE(seg, NS_ERROR_OUT_OF_MEMORY); 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 nsSVGElement::NumberAttributesInfo nsSVGPathElement::GetNumberInfo() { return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); } //---------------------------------------------------------------------- // nsIDOMSVGAnimatedPathData methods: /* 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; */ NS_IMETHODIMP nsSVGPathElement::GetNormalizedPathSegList(nsIDOMSVGPathSegList * *aNormalizedPathSegList) { return NS_ERROR_NOT_IMPLEMENTED; } /* 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; */ NS_IMETHODIMP nsSVGPathElement::GetAnimatedNormalizedPathSegList(nsIDOMSVGPathSegList * *aAnimatedNormalizedPathSegList) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -442,85 +382,20 @@ nsSVGPathElement::IsAttributeMapped(cons static const MappedAttributeEntry* const map[] = { sMarkersMap }; return FindAttributeDependence(name, map, NS_ARRAY_LENGTH(map)) || 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); } //---------------------------------------------------------------------- // nsSVGPathGeometryElement methods PRBool nsSVGPathElement::AttributeDefinesGeometry(const nsIAtom *aName) { @@ -532,522 +407,20 @@ nsSVGPathElement::AttributeDefinesGeomet } PRBool 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); -} - -//================================================================== -// nsSVGPathList - -void -nsSVGPathList::Clear() -{ - if (mArguments) { - free(mArguments); - mArguments = nsnull; - } - mNumCommands = 0; - mNumArguments = 0; + mD.GetAnimValue().ConstructPath(aCtx); } -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(); -}
--- a/content/svg/content/src/nsSVGPathElement.h +++ b/content/svg/content/src/nsSVGPathElement.h @@ -38,95 +38,75 @@ #ifndef __NS_SVGPATHELEMENT_H__ #define __NS_SVGPATHELEMENT_H__ #include "nsSVGPathGeometryElement.h" #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, public nsIDOMSVGPathElement, public nsIDOMSVGAnimatedPathData { friend class nsSVGPathFrame; friend class nsSVGTextPathFrame; 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 NS_DECL_NSIDOMSVGPATHELEMENT NS_DECL_NSIDOMSVGANIMATEDPATHDATA // xxx I wish we could use virtual inheritance NS_FORWARD_NSIDOMNODE(nsSVGPathElementBase::) NS_FORWARD_