From 54b8f7f089a191f86718605a627f2acdda07facb Mon Sep 17 00:00:00 2001
From: Christopher Butler
' . Piwik::translate('CustomVariables_CustomVariablesSubcategoryHelp2') . '
' - ; - } -} diff --git a/files/plugin-CustomVariables-5.0.2/Columns/Base.php b/files/plugin-CustomVariables-5.0.2/Columns/Base.php deleted file mode 100644 index bcb0527..0000000 --- a/files/plugin-CustomVariables-5.0.2/Columns/Base.php +++ /dev/null @@ -1,68 +0,0 @@ -setSegment('customVariable' . $segmentNameSuffix . $i); - $segment->setSqlSegment('log_visit.custom_var_' . $segmentSuffix . $i); - $segment->setName(Piwik::translate('CustomVariables_ColumnCustomVariable' . $segmentNameSuffix) . ' ' . $i - . ' (' . Piwik::translate('CustomVariables_ScopeVisit') . ')'); - $segmentsList->addSegment($dimensionSegmentFactory->createSegment($segment)); - - $segment = new Segment(); - $segment->setSegment('customVariablePage' . $segmentNameSuffix . $i); - $segment->setSqlSegment('log_link_visit_action.custom_var_' . $segmentSuffix . $i); - $segment->setName(Piwik::translate('CustomVariables_ColumnCustomVariable' . $segmentNameSuffix) . ' ' . $i - . ' (' . Piwik::translate('CustomVariables_ScopePage') . ')'); - $segmentsList->addSegment($dimensionSegmentFactory->createSegment($segment)); - } - - $segment = new Segment(); - $segment->setType('dimension'); - $segment->setSegment('customVariable' . $segmentNameSuffix); - $segment->setName($this->getName() . ' (' . Piwik::translate('CustomVariables_ScopeVisit') . ')'); - $segment->setUnionOfSegments($this->getSegmentColumns('customVariable' . $segmentNameSuffix, $numCustomVariables)); - $segmentsList->addSegment($dimensionSegmentFactory->createSegment($segment)); - - $segment = new Segment(); - $segment->setType('dimension'); - $segment->setSegment('customVariablePage' . $segmentNameSuffix); - $segment->setName($this->getName() . ' (' . Piwik::translate('CustomVariables_ScopePage') . ')'); - $segment->setUnionOfSegments($this->getSegmentColumns('customVariablePage' . $segmentNameSuffix, $numCustomVariables)); - $segmentsList->addSegment($dimensionSegmentFactory->createSegment($segment)); - } - - private function getSegmentColumns($column, $numCustomVariables) - { - $columns = array(); - for ($i = 1; $i <= $numCustomVariables; ++$i) { - $columns[] = $column . $i; - } - return $columns; - } -} \ No newline at end of file diff --git a/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableName.php b/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableName.php deleted file mode 100644 index cf8e807..0000000 --- a/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableName.php +++ /dev/null @@ -1,27 +0,0 @@ -configureSegmentsFor('Name', $segmentsList, $dimensionSegmentFactory); - } - - public function getName() - { - return Piwik::translate('CustomVariables_ColumnCustomVariableName'); - } - -} \ No newline at end of file diff --git a/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableValue.php b/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableValue.php deleted file mode 100644 index aaf0299..0000000 --- a/files/plugin-CustomVariables-5.0.2/Columns/CustomVariableValue.php +++ /dev/null @@ -1,27 +0,0 @@ -configureSegmentsFor('Value', $segmentsList, $dimensionSegmentFactory); - } - - public function getName() - { - return Piwik::translate('CustomVariables_ColumnCustomVariableValue'); - } - -} \ No newline at end of file diff --git a/files/plugin-CustomVariables-5.0.2/Commands/Info.php b/files/plugin-CustomVariables-5.0.2/Commands/Info.php deleted file mode 100644 index fb5a6db..0000000 --- a/files/plugin-CustomVariables-5.0.2/Commands/Info.php +++ /dev/null @@ -1,70 +0,0 @@ -setName('customvariables:info'); - $this->setDescription('Get info about configured custom variables'); - } - - protected function doExecute(): int - { - $output = $this->getOutput(); - $maxVars = CustomVariables::getNumUsableCustomVariables(); - - if ($this->hasEverywhereSameAmountOfVariables()) { - $this->writeSuccessMessage([ - 'Your Matomo is configured for ' . $maxVars . ' custom variables.' - ]); - return self::SUCCESS; - } - - $output->writeln('-_paq.push([setCustomVariable(index, name, value, scope = "visit"]); -- -It is important that you use a unique index for each value as the index will only ever store the most recent value. However, you can use the same name against multiple indexes so that you can store multiple values for a metric. You can find examples of this for both scopes formatted for the [HTTP tracking API](https://developer.matomo.org/api-reference/tracking-api) below. - -**Visit Scoped Custom Variable Examples** - -In this example we are tracking a Role variable where website users can hold multiple roles at the same time. In this case it is associating both a “Staff” and “Moderator” role with the visitor that will persist for all actions within their entire visit. - -
-_paq.push([setCustomVariable(1, "Role", "Staff", scope = "visit"]); -_paq.push([setCustomVariable(2, "Role", "Moderator", scope = "visit"]); -- -**Page Scoped Custom Variable Examples** - -In this example of a Page scoped custom variable multiple tags are associated with a specific page view. In this case both the “Guide” and “Videos” tag will be associated with the pageview, however this won’t be associated with other page views unless the same code is also present on those pages. - -
-_paq.push([setCustomVariable, 1, "Tag", "Guides", scope = "page"]); -_paq.push([setCustomVariable, 2, "Tag", "Videos", scope = "page"]); -- -When using the [HTTP tracking API](https://developer.matomo.org/api-reference/tracking-api) you must make sure that you set your custom variables before `trackPageview` is called. You can find more information on doing this via Matomo Tag Manager further down the page. - -##### Tracking/Capturing Custom Variables via Matomo Tag Manager - -While it may not always be the easiest way to pull data from your existing content management system, it is also possible to set up Custom Variables with [Matomo Tag Manager](https://matomo.org/docs/tag-manager/). The specific method for collecting data will vary depending on what you would like to collect but the general configuration is detailed below. - -
-<script> - var _paq = window._paq = window._paq || []; - _paq.push(['trackPageView']); -</script> --You will also want to ensure that this tag fires after your existing Priority for this tag so make sure you click Show advanced settings and update the Priority text field to 999 so that it always fires after the initial tag.
-<script>
- var _paq = window._paq = window._paq || [];
-_paq.push(['setCustomVariable', 1, 'User Role', 'Guest', scope = 'visit']);
-_paq.push(['setCustomVariable', 1, 'Tag', 'Marketing', scope = 'page']);
-_paq.push(['setCustomVariable', 2, 'Tag', {{dataLayer - Tags}}, scope = 'page']);
-</script>
-
-You can combine several variables in a single tag if it makes sense, but you will need to ensure that the tag only fires when relevant. For example, if you create a Tags variable for tracking tags associated with pageviews, then you will need to create a Trigger that only fires when a page is loaded with the relevant tags. You can learn more about triggers here. Any tags that you create containing variables will need to be set to a Priority number somewhere between your first tag 1 and your pageview tag 999. This is so the tags all load in the correct sequence. For example:
-
-
-
-#### Hierarchical vs Flat Analysis
-
-By default variable data is displayed in hierarchical format. This means you can click on the plus icon for any of the top level names to reveal the values stored against that specific Name for easy comparison. Below is an example comparing different page type on a dive review website:
-
-
-
-Alternatively you can flatten the table to compare disparate metrics against each other. For example comparing the success of certain page types against content from certain authors. Not all comparisons will make sense so it is up to you to consider whether this view is relevant for the custom variables tracked on your site.
-
-
-
-You can switch between these modes by hovering your mouse over the table to reveal a green icon menu in the bottom left of the screen. You can then click on the cog icon which provides several options including switching between hierarchical and flat data views, exclude rows with low data or [pivot your data](https://matomo.org/faq/custom-reports/faq_25253/).
-
-
-
-### Resources
-* [Developer JavaScript Tracking Guide for Custom Variables](https://developer.matomo.org/guides/tracking-javascript-guide#custom-variables)
-* [Developer Hooks for Custom Variables in the Reporting API](https://developer.matomo.org/api-reference/reporting-api#CustomVariables)
-* [How to Extend Custom Variable Limits for Matomo On-Premise](https://matomo.org/faq/how-to/faq_17931/)
-* [Advantages of Custom Dimensions over Custom Variables](https://matomo.org/faq/general/faq_21117/)
-* [Developer Documentation of REST API for Custom Variables](http://developer.matomo.org/api-reference/reporting-api#CustomVariables)
diff --git a/files/plugin-CustomVariables-5.0.2/RecordBuilders/CustomVariables.php b/files/plugin-CustomVariables-5.0.2/RecordBuilders/CustomVariables.php
deleted file mode 100644
index 58640bd..0000000
--- a/files/plugin-CustomVariables-5.0.2/RecordBuilders/CustomVariables.php
+++ /dev/null
@@ -1,329 +0,0 @@
-maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_custom_variables']
- ?? Config::getInstance()->General['datatable_archiving_maximum_rows_custom_dimensions'];
- $this->maxRowsInSubtable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_custom_variables']
- ?? Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_custom_dimensions'];
- $this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
- $this->columnAggregationOps = ['slots' => 'uniquearraymerge'];
- }
-
- public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
- {
- return [
- Record::make(Record::TYPE_BLOB, Archiver::CUSTOM_VARIABLE_RECORD_NAME),
- ];
- }
-
- protected function aggregate(ArchiveProcessor $archiveProcessor): array
- {
- $record = new DataTable();
- $metadata = [];
- $metadataFlat = [];
-
- $logAggregator = $archiveProcessor->getLogAggregator();
-
- $maxCustomVariables = \Piwik\Plugins\CustomVariables\CustomVariables::getNumUsableCustomVariables();
- for ($i = 1; $i <= $maxCustomVariables; $i++) {
- $this->aggregateCustomVariable($record, $metadata, $metadataFlat, $logAggregator, $i);
- }
-
- $this->removeVisitsMetricsFromActionsAggregate($record);
- $record->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class);
-
- foreach ($record->getRows() as $row) {
- $label = $row->getColumn('label');
- if (!empty($metadata[$label])) {
- foreach ($metadata[$label] as $name => $value) {
- $row->addMetadata($name, $value);
- }
- }
- }
-
- return [Archiver::CUSTOM_VARIABLE_RECORD_NAME => $record];
- }
-
-
- protected function aggregateCustomVariable(DataTable $record, array &$metadata, array &$metadataFlat, LogAggregator $logAggregator, string $slot): void
- {
- $keyField = "custom_var_k" . $slot;
- $valueField = "custom_var_v" . $slot;
- $where = "%s.$keyField != ''";
- $dimensions = array($keyField, $valueField);
-
- $query = $logAggregator->queryVisitsByDimension($dimensions, $where);
- $this->aggregateFromVisits($record, $metadata, $metadataFlat, $query, $keyField, $valueField);
-
- // IF we query Custom Variables scope "page" either: Product SKU, Product Name,
- // then we also query the "Product page view" price which was possibly recorded.
- $additionalSelects = false;
-
- // Before Matomo 4.0.0 ecommerce views were tracked in custom variables
- // So if Matomo was installed before still try to archive it the old way, as old data might be archived
- if (version_compare(DbHelper::getInstallVersion(),'4.0.0-b2', '<') && in_array($slot, array(3, 4, 5))) {
- $additionalSelects = array($this->getSelectAveragePrice());
- }
- $query = $logAggregator->queryActionsByDimension($dimensions, $where, $additionalSelects);
- $this->aggregateFromActions($record, $metadata, $metadataFlat, $query, $keyField, $valueField);
-
- $query = $logAggregator->queryConversionsByDimension($dimensions, $where);
- $this->aggregateFromConversions($record, $query, $keyField, $valueField);
- }
-
- protected function getSelectAveragePrice()
- {
- $field = "custom_var_v2";
- return LogAggregator::getSqlRevenue("AVG(log_link_visit_action." . $field . ")") . " as `" . Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED . "`";
- }
-
- protected function aggregateFromVisits(DataTable $record, array &$metadata, array &$metadataFlat, $query,
- string $keyField, string $valueField): void
- {
- while ($row = $query->fetch()) {
- $key = $row[$keyField];
- $value = $this->cleanCustomVarValue($row[$valueField]);
-
- $this->addMetadata($metadata, $metadataFlat, $keyField, $key, Model::SCOPE_VISIT);
-
- $columns = [
- Metrics::INDEX_NB_UNIQ_VISITORS => $row[Metrics::INDEX_NB_UNIQ_VISITORS],
- Metrics::INDEX_NB_VISITS => $row[Metrics::INDEX_NB_VISITS],
- Metrics::INDEX_NB_ACTIONS => $row[Metrics::INDEX_NB_ACTIONS],
- Metrics::INDEX_NB_USERS => $row[Metrics::INDEX_NB_USERS],
- Metrics::INDEX_MAX_ACTIONS => $row[Metrics::INDEX_MAX_ACTIONS],
- Metrics::INDEX_SUM_VISIT_LENGTH => $row[Metrics::INDEX_SUM_VISIT_LENGTH],
- Metrics::INDEX_BOUNCE_COUNT => $row[Metrics::INDEX_BOUNCE_COUNT],
- Metrics::INDEX_NB_VISITS_CONVERTED => $row[Metrics::INDEX_NB_VISITS_CONVERTED],
- ];
-
- $existingRow = $record->getRowFromLabel($key);
-
- // Edge case fail safe
- if (!empty($existingRow)
- && !$existingRow->hasColumn(Metrics::INDEX_NB_VISITS)
- ) {
- continue;
- }
-
- // In case the existing Row had no action metrics (eg. Custom Variable XYZ with "visit" scope)
- // but the new Row has action metrics (eg. same Custom Variable XYZ this time with a "page" scope)
- if (!empty($existingRow)
- && !$existingRow->hasColumn(Metrics::INDEX_MAX_ACTIONS)
- ) {
- $toZero = [
- Metrics::INDEX_NB_USERS,
- Metrics::INDEX_MAX_ACTIONS,
- Metrics::INDEX_SUM_VISIT_LENGTH,
- Metrics::INDEX_BOUNCE_COUNT,
- Metrics::INDEX_NB_VISITS_CONVERTED,
- ];
- foreach ($toZero as $metric) {
- $existingRow->setColumn($metric, 0);
- }
- }
-
- $topLevelRow = $record->sumRowWithLabel($key, $columns);
- $topLevelRow->sumRowWithLabelToSubtable($value, $columns);
- }
- }
-
- protected function cleanCustomVarValue($value)
- {
- if ($value !== null && strlen($value)) {
- return $value;
- }
- return Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED;
- }
-
- protected function aggregateFromActions(DataTable $record, array &$metadata, array &$metadataFlat, $query,
- string $keyField, string $valueField): void
- {
- while ($row = $query->fetch()) {
- $key = $row[$keyField];
- $value = $this->cleanCustomVarValue($row[$valueField]);
-
- $this->addMetadata($metadata, $metadataFlat, $keyField, $key, Model::SCOPE_PAGE);
-
- $alreadyAggregated = $this->aggregateEcommerceCategories($record, $key, $value, $row);
- if (!$alreadyAggregated) {
- $this->aggregateActionByKeyAndValue($record, $key, $value, $row);
-
- $columns = [
- Metrics::INDEX_NB_UNIQ_VISITORS => $row[Metrics::INDEX_NB_UNIQ_VISITORS],
- Metrics::INDEX_NB_VISITS => $row[Metrics::INDEX_NB_VISITS],
- Metrics::INDEX_NB_ACTIONS => $row[Metrics::INDEX_NB_ACTIONS],
- ];
- $record->sumRowWithLabel($key, $columns);
- }
- }
- }
-
- private function addMetadata(array &$metadata, array &$metadataFlat, string $keyField, string $label, string $scope): void
- {
- $index = (int) str_replace('custom_var_k', '', $keyField);
-
- if (!array_key_exists($label, $metadata)) {
- $metadata[$label] = array('slots' => array());
- }
-
- $uniqueId = $label . 'scope' . $scope . 'index' . $index;
-
- if (!isset($metadataFlat[$uniqueId])) {
- $metadata[$label]['slots'][] = array('scope' => $scope, 'index' => $index);
- $metadataFlat[$uniqueId] = true;
- }
- }
-
- /**
- * @param string $key
- * @param string $value
- * @param $row
- * @return bool True if the $row metrics were already added to the ->metrics
- */
- protected function aggregateEcommerceCategories(DataTable $record, string $key, string $value, array $row): bool
- {
- $ecommerceCategoriesAggregated = false;
- if ($key == '_pkc'
- && $value[0] == '[' && $value[1] == '"'
- ) {
- // In case categories were truncated, try closing the array
- if (substr($value, -2) != '"]') {
- $value .= '"]';
- }
- $decoded = json_decode($value);
- if (is_array($decoded)) {
- $count = 0;
- foreach ($decoded as $category) {
- if (empty($category)
- || $count >= GoalManager::MAXIMUM_PRODUCT_CATEGORIES
- ) {
- continue;
- }
- $this->aggregateActionByKeyAndValue($record, $key, $category, $row);
- $ecommerceCategoriesAggregated = true;
- $count++;
- }
- }
- }
- return $ecommerceCategoriesAggregated;
- }
-
- protected function aggregateActionByKeyAndValue(DataTable $record, string $key, string $value, array $row): void
- {
- $columns = [
- Metrics::INDEX_NB_UNIQ_VISITORS => $row[Metrics::INDEX_NB_UNIQ_VISITORS],
- Metrics::INDEX_NB_VISITS => $row[Metrics::INDEX_NB_VISITS],
- Metrics::INDEX_NB_ACTIONS => $row[Metrics::INDEX_NB_ACTIONS],
- ];
-
- $toplevelRow = $record->sumRowWithLabel($key, []);
-
- // Edge case fail safe
- $subtable = $toplevelRow->getSubtable();
- $existingRow = !empty($subtable) ? $subtable->getRowFromLabel($value) : null;
- if (!empty($existingRow)
- && !$existingRow->hasColumn(Metrics::INDEX_NB_VISITS)
- ) {
- return;
- }
-
- $subtableRow = $toplevelRow->sumRowWithLabelToSubtable($value, $columns);
-
- if ($this->isReservedKey($key)) {
- // Price tracking on Ecommerce product/category pages:
- // the average is returned from the SQL query so the price is not "summed" like other metrics
- $index = Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED;
- if (!empty($row[$index])) {
- $subtableRow->setColumn($index, (float)$row[$index]);
- }
- }
- }
-
- protected static function isReservedKey($key)
- {
- return in_array($key, API::getReservedCustomVariableKeys());
- }
-
- protected function aggregateFromConversions(DataTable $record, $query, string $keyField, string $valueField): void
- {
- if ($query === false) {
- return;
- }
-
- while ($row = $query->fetch()) {
- $key = $row[$keyField];
-
- $value = $this->cleanCustomVarValue($row[$valueField]);
-
- $idGoal = (int) $row['idgoal'];
- $columns = [
- Metrics::INDEX_GOALS => [
- $idGoal => Metrics::makeGoalColumnsRow($idGoal, $row),
- ],
- ];
-
- $topLevelRow = $record->sumRowWithLabel($key, $columns);
- $topLevelRow->sumRowWithLabelToSubtable($value, $columns);
- }
- }
-
- /**
- * Delete Visit, Unique Visitor and Users metric from 'page' scope custom variables.
- *
- * - Custom variables of 'visit' scope: it is expected that these ones have the "visit" column set.
- * - Custom variables of 'page' scope: we cannot process "Visits" count for these.
- * Why?
- * "Actions" column is processed with a SELECT count(*).
- * A same visit can set the same custom variable of 'page' scope multiple times.
- * We cannot sum the values of count(*) as it would be incorrect.
- * The way we could process "Visits" Metric for 'page' scope variable is to issue a count(Distinct *) or so,
- * but it is no implemented yet (this would likely be very slow for high traffic sites).
- *
- */
- protected function removeVisitsMetricsFromActionsAggregate(DataTable $record): void
- {
- foreach ($record->getRows() as $row) {
- $label = $row->getColumn('label');
- if (!self::isReservedKey($label)
- && $this->isActionsRow($row)
- ) {
- $row->deleteColumn(Metrics::INDEX_NB_UNIQ_VISITORS);
- $row->deleteColumn(Metrics::INDEX_NB_VISITS);
- $row->deleteColumn(Metrics::INDEX_NB_USERS);
- }
- }
- }
-
- private function isActionsRow(DataTable\Row $row): bool
- {
- return count($row->getColumns()) == 4 && $row->hasColumn(Metrics::INDEX_NB_ACTIONS);
- }
-}
diff --git a/files/plugin-CustomVariables-5.0.2/Reports/Base.php b/files/plugin-CustomVariables-5.0.2/Reports/Base.php
deleted file mode 100644
index 65a39bf..0000000
--- a/files/plugin-CustomVariables-5.0.2/Reports/Base.php
+++ /dev/null
@@ -1,21 +0,0 @@
-categoryId = 'General_Visitors';
- $this->onlineGuideUrl = 'https://matomo.org/docs/custom-variables/';
- }
-
-}
diff --git a/files/plugin-CustomVariables-5.0.2/Reports/GetCustomVariables.php b/files/plugin-CustomVariables-5.0.2/Reports/GetCustomVariables.php
deleted file mode 100644
index f2490db..0000000
--- a/files/plugin-CustomVariables-5.0.2/Reports/GetCustomVariables.php
+++ /dev/null
@@ -1,86 +0,0 @@
-dimension = new CustomVariableName();
- $this->name = Piwik::translate('CustomVariables_CustomVariables');
- $this->documentation = Piwik::translate('CustomVariables_CustomVariablesReportDocumentation',
- array('- {{ variable.name }}: - {%- for value in variable.values -%} - {{ value.value }}{% if not loop.last %}, {% endif %} - {%- endfor -%} -
- {%- endfor -%} -- -
-| {{ translate('CustomVariables_Index') }} | -{{ translate('CustomVariables_Usages') }} | -|
|---|---|---|
| {{ translate('General_Loading') }} | -||
| {{ customVariables.index }} | -- {{ translate('CustomVariables_Unused') }} - - {{ cvar.name }} - , - - | -|
- {{ translate('CustomVariables_CreatingCustomVariableTakesTime') }}
-
-
-
-
- {{ translate('CustomVariables_ToCreateCustomVarExecute') }}
-
-
-
+
+# Why do I need it? #
+
+Mutation Summary does five main things for you:
+
+ * **It tells you how the document is different now from how it was.** As its name suggests, it summarizes what’s happened. It’s as if it takes a picture of the document when you first create it, and then again after each time it calls you back. When things have changed, it calls you with a concise description of exactly what’s different now from the last picture it took for you.
+ * **It handles any and all changes, no matter how complex.** All kinds of things can happen to the DOM: values can change and but put back to what they were, large parts can be pulled out, changed, rearranged, put back. Mutation Summary can take any crazy thing you throw at it. Go ahead, tear the document to shreds, Mutation Summary won’t even blink.
+ * **It lets you express what kinds of things you’re interested in.** It presents a query API that lets you tell it exactly what kinds of changes you’re interested in. This includes support for simple CSS-like selector descriptions of elements you care about.
+ * **It’s fast.** The time and memory it takes is dependant on number of changes that occurred (which typically involves only a few nodes) -- not the size of your document (which is commonly thousands of nodes).
+ * **It can automatically ignore changes you make during your callback.** Mutation Summary is going to call you back when changes have occurred. If you need to react to those changes by making more changes -- won’t you hear about those changes the next time it calls you back? Not unless you [ask for that](APIReference.md#configuration-options). By default, it stops watching the document immediately before it calls you back and resumes watching as soon as your callback finishes.
+
+# What is it useful for? #
+
+Lots of things, here are some examples:
+
+ * **Browser extensions.** Want to make a browser extension that creates a link to your mapping application whenever an address appears in a page? You’ll need to know when those addresses appear (and disappear).
+ * **Implement missing HTML capabilities.** Think building web apps is too darn hard and you know what’s missing from HTML that would make it a snap? Writing the code for the desired behavior is only half the battle--you’ll also need to know when those elements and attributes show up and what happens to them. In fact, there’s already two widely used classes of libraries which do exactly this, but don’t currently have a good way to observe changes to the DOM.
+ * **UI Widget** libraries, e.g. Dojo Widgets
+ * **Templating** and/or **Databinding** libraries, e.g. Angular or KnockoutJS
+ * **Text Editors.** HTML Text editors often want to observe what’s being input and “fix it up” so that they can maintain a consistent WYSWIG UI.
+
+# What is this _not_ useful for? #
+
+The intent here isn't to be all things to all use-cases. Mutation Summary is not meant to:
+
+ * **Use the DOM as some sort of state-transition machine.** It won't report transient states that the DOM moved through. It will only tell you what the difference is between the previous state and the present one.
+ * **Observing complex selectors.** It offers support for a simple [subset of CSS selectors](APIReference.md#supported-selector-syntax). Want to observe all elements that match `“div[foo] span.bar > p:first-child”`? Unfortunately, efficiently computing that is much harder and currently outside the scope of this library.
+
+Note that both of the above use cases are possible given the data that the underlying Mutation Observers API provides -- we simply judged them to be outside the "80% use case" that we targeted with this particular library.
+
+# Where can Mutation Summary be used? #
+
+The Mutation Summary library depends on the presence of the Mutation Observer DOM API. Mutation Observers are available in
+
+ * [Google Chrome](https://www.google.com/chrome)
+ * [Firefox](http://www.mozilla.org/en-US/firefox/new/)
+ * [Safari](http://www.apple.com/safari/)
+ * [Opera](http://www.opera.com/)
+ * [IE11](http://www.microsoft.com/ie)
+
+Mutation Observers is the work of the [W3C WebApps working group](http://www.w3.org/2008/webapps/). In the future it will be implemented in other browsers (we’ll keep the above list of supporting browsers as up-to-date as possible).
+
+# Great. I want to get started. What’s next? #
+
+ * Check out the [tutorial](Tutorial.md) and the [API reference](APIReference.md).
+
+# Google groups discussion list #
+
+ * [mutation-summary-discuss@googlegroups.com](https://groups.google.com/group/mutation-summary-discuss)
diff --git a/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/package.json b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/package.json
new file mode 100644
index 0000000..3029ee6
--- /dev/null
+++ b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "mutation-summary",
+ "version": "0.0.0",
+ "description": "Makes observing the DOM fast and easy",
+ "main": "src/mutation-summary.js",
+ "directories": {
+ "example": "examples",
+ "test": "tests"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://code.google.com/p/mutation-summary/"
+ },
+ "author": "",
+ "license": "Apache 2.0",
+ "devDependencies": {
+ "chai": "*",
+ "mocha": "*"
+ }
+}
+
+
+
+
+
+
+
diff --git a/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.js b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.js
new file mode 100644
index 0000000..475fd5d
--- /dev/null
+++ b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.js
@@ -0,0 +1,1398 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var __extends = this.__extends || function (d, b) {
+ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+ function __() { this.constructor = d; }
+ __.prototype = b.prototype;
+ d.prototype = new __();
+};
+
+// Modified by InnoCraft to not fail if MutationObserver is not defined, eg IE 10 and lower
+var MutationObserverCtor;
+if (typeof WebKitMutationObserver !== 'undefined') {
+ MutationObserverCtor = WebKitMutationObserver;
+} else if (typeof MutationObserver !== 'undefined') {
+ MutationObserverCtor = MutationObserver;
+}
+
+if (MutationObserverCtor) {
+
+var NodeMap = (function () {
+ function NodeMap() {
+ this.nodes = [];
+ this.values = [];
+ }
+ NodeMap.prototype.isIndex = function (s) {
+ return +s === s >>> 0;
+ };
+ NodeMap.prototype.nodeId = function (node) {
+ var id = node[NodeMap.ID_PROP];
+ if (!id)
+ id = node[NodeMap.ID_PROP] = NodeMap.nextId_++;
+ return id;
+ };
+ NodeMap.prototype.set = function (node, value) {
+ var id = this.nodeId(node);
+ this.nodes[id] = node;
+ this.values[id] = value;
+ };
+ NodeMap.prototype.get = function (node) {
+ var id = this.nodeId(node);
+ return this.values[id];
+ };
+ NodeMap.prototype.has = function (node) {
+ return this.nodeId(node) in this.nodes;
+ };
+ NodeMap.prototype.delete = function (node) {
+ var id = this.nodeId(node);
+ delete this.nodes[id];
+ this.values[id] = undefined;
+ };
+ NodeMap.prototype.keys = function () {
+ var nodes = [];
+ for (var id in this.nodes) {
+ if (!this.isIndex(id))
+ continue;
+ nodes.push(this.nodes[id]);
+ }
+ return nodes;
+ };
+ NodeMap.ID_PROP = '__mutation_summary_node_map_id__';
+ NodeMap.nextId_ = 1;
+ return NodeMap;
+})();
+/**
+ * var reachableMatchableProduct = [
+ * // STAYED_OUT, ENTERED, STAYED_IN, EXITED
+ * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT
+ * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED
+ * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN
+ * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED
+ * ];
+ */
+var Movement;
+(function (Movement) {
+ Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT";
+ Movement[Movement["ENTERED"] = 1] = "ENTERED";
+ Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN";
+ Movement[Movement["REPARENTED"] = 3] = "REPARENTED";
+ Movement[Movement["REORDERED"] = 4] = "REORDERED";
+ Movement[Movement["EXITED"] = 5] = "EXITED";
+})(Movement || (Movement = {}));
+function enteredOrExited(changeType) {
+ return changeType === Movement.ENTERED || changeType === Movement.EXITED;
+}
+var NodeChange = (function () {
+ function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) {
+ if (childList === void 0) { childList = false; }
+ if (attributes === void 0) { attributes = false; }
+ if (characterData === void 0) { characterData = false; }
+ if (oldParentNode === void 0) { oldParentNode = null; }
+ if (added === void 0) { added = false; }
+ if (attributeOldValues === void 0) { attributeOldValues = null; }
+ if (characterDataOldValue === void 0) { characterDataOldValue = null; }
+ this.node = node;
+ this.childList = childList;
+ this.attributes = attributes;
+ this.characterData = characterData;
+ this.oldParentNode = oldParentNode;
+ this.added = added;
+ this.attributeOldValues = attributeOldValues;
+ this.characterDataOldValue = characterDataOldValue;
+ this.isCaseInsensitive =
+ this.node.nodeType === Node.ELEMENT_NODE &&
+ this.node instanceof HTMLElement &&
+ this.node.ownerDocument instanceof HTMLDocument;
+ }
+ NodeChange.prototype.getAttributeOldValue = function (name) {
+ if (!this.attributeOldValues)
+ return undefined;
+ if (this.isCaseInsensitive)
+ name = name.toLowerCase();
+ return this.attributeOldValues[name];
+ };
+ NodeChange.prototype.getAttributeNamesMutated = function () {
+ var names = [];
+ if (!this.attributeOldValues)
+ return names;
+ for (var name in this.attributeOldValues) {
+ names.push(name);
+ }
+ return names;
+ };
+ NodeChange.prototype.attributeMutated = function (name, oldValue) {
+ this.attributes = true;
+ this.attributeOldValues = this.attributeOldValues || {};
+ if (name in this.attributeOldValues)
+ return;
+ this.attributeOldValues[name] = oldValue;
+ };
+ NodeChange.prototype.characterDataMutated = function (oldValue) {
+ if (this.characterData)
+ return;
+ this.characterData = true;
+ this.characterDataOldValue = oldValue;
+ };
+ // Note: is it possible to receive a removal followed by a removal. This
+ // can occur if the removed node is added to an non-observed node, that
+ // node is added to the observed area, and then the node removed from
+ // it.
+ NodeChange.prototype.removedFromParent = function (parent) {
+ this.childList = true;
+ if (this.added || this.oldParentNode)
+ this.added = false;
+ else
+ this.oldParentNode = parent;
+ };
+ NodeChange.prototype.insertedIntoParent = function () {
+ this.childList = true;
+ this.added = true;
+ };
+ // An node's oldParent is
+ // -its present parent, if its parentNode was not changed.
+ // -null if the first thing that happened to it was an add.
+ // -the node it was removed from if the first thing that happened to it
+ // was a remove.
+ NodeChange.prototype.getOldParent = function () {
+ if (this.childList) {
+ if (this.oldParentNode)
+ return this.oldParentNode;
+ if (this.added)
+ return null;
+ }
+ return this.node.parentNode;
+ };
+ return NodeChange;
+})();
+var ChildListChange = (function () {
+ function ChildListChange() {
+ this.added = new NodeMap();
+ this.removed = new NodeMap();
+ this.maybeMoved = new NodeMap();
+ this.oldPrevious = new NodeMap();
+ this.moved = undefined;
+ }
+ return ChildListChange;
+})();
+var TreeChanges = (function (_super) {
+ __extends(TreeChanges, _super);
+ function TreeChanges(rootNode, mutations) {
+ _super.call(this);
+ this.rootNode = rootNode;
+ this.reachableCache = undefined;
+ this.wasReachableCache = undefined;
+ this.anyParentsChanged = false;
+ this.anyAttributesChanged = false;
+ this.anyCharacterDataChanged = false;
+ for (var m = 0; m < mutations.length; m++) {
+ var mutation = mutations[m];
+ switch (mutation.type) {
+ case 'childList':
+ this.anyParentsChanged = true;
+ for (var i = 0; i < mutation.removedNodes.length; i++) {
+ var node = mutation.removedNodes[i];
+ this.getChange(node).removedFromParent(mutation.target);
+ }
+ for (var i = 0; i < mutation.addedNodes.length; i++) {
+ var node = mutation.addedNodes[i];
+ this.getChange(node).insertedIntoParent();
+ }
+ break;
+ case 'attributes':
+ this.anyAttributesChanged = true;
+ var change = this.getChange(mutation.target);
+ change.attributeMutated(mutation.attributeName, mutation.oldValue);
+ break;
+ case 'characterData':
+ this.anyCharacterDataChanged = true;
+ var change = this.getChange(mutation.target);
+ change.characterDataMutated(mutation.oldValue);
+ break;
+ }
+ }
+ }
+ TreeChanges.prototype.getChange = function (node) {
+ var change = this.get(node);
+ if (!change) {
+ change = new NodeChange(node);
+ this.set(node, change);
+ }
+ return change;
+ };
+ TreeChanges.prototype.getOldParent = function (node) {
+ var change = this.get(node);
+ return change ? change.getOldParent() : node.parentNode;
+ };
+ TreeChanges.prototype.getIsReachable = function (node) {
+ if (node === this.rootNode)
+ return true;
+ if (!node)
+ return false;
+ this.reachableCache = this.reachableCache || new NodeMap();
+ var isReachable = this.reachableCache.get(node);
+ if (isReachable === undefined) {
+ isReachable = this.getIsReachable(node.parentNode);
+ this.reachableCache.set(node, isReachable);
+ }
+ return isReachable;
+ };
+ // A node wasReachable if its oldParent wasReachable.
+ TreeChanges.prototype.getWasReachable = function (node) {
+ if (node === this.rootNode)
+ return true;
+ if (!node)
+ return false;
+ this.wasReachableCache = this.wasReachableCache || new NodeMap();
+ var wasReachable = this.wasReachableCache.get(node);
+ if (wasReachable === undefined) {
+ wasReachable = this.getWasReachable(this.getOldParent(node));
+ this.wasReachableCache.set(node, wasReachable);
+ }
+ return wasReachable;
+ };
+ TreeChanges.prototype.reachabilityChange = function (node) {
+ if (this.getIsReachable(node)) {
+ return this.getWasReachable(node) ?
+ Movement.STAYED_IN : Movement.ENTERED;
+ }
+ return this.getWasReachable(node) ?
+ Movement.EXITED : Movement.STAYED_OUT;
+ };
+ return TreeChanges;
+})(NodeMap);
+var MutationProjection = (function () {
+ // TOOD(any)
+ function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) {
+ this.rootNode = rootNode;
+ this.mutations = mutations;
+ this.selectors = selectors;
+ this.calcReordered = calcReordered;
+ this.calcOldPreviousSibling = calcOldPreviousSibling;
+ this.treeChanges = new TreeChanges(rootNode, mutations);
+ this.entered = [];
+ this.exited = [];
+ this.stayedIn = new NodeMap();
+ this.visited = new NodeMap();
+ this.childListChangeMap = undefined;
+ this.characterDataOnly = undefined;
+ this.matchCache = undefined;
+ this.processMutations();
+ }
+ MutationProjection.prototype.processMutations = function () {
+ if (!this.treeChanges.anyParentsChanged &&
+ !this.treeChanges.anyAttributesChanged)
+ return;
+ var changedNodes = this.treeChanges.keys();
+ for (var i = 0; i < changedNodes.length; i++) {
+ this.visitNode(changedNodes[i], undefined);
+ }
+ };
+ MutationProjection.prototype.visitNode = function (node, parentReachable) {
+ if (this.visited.has(node))
+ return;
+ this.visited.set(node, true);
+ var change = this.treeChanges.get(node);
+ var reachable = parentReachable;
+ // node inherits its parent's reachability change unless
+ // its parentNode was mutated.
+ if ((change && change.childList) || reachable == undefined)
+ reachable = this.treeChanges.reachabilityChange(node);
+ if (reachable === Movement.STAYED_OUT)
+ return;
+ // Cache match results for sub-patterns.
+ this.matchabilityChange(node);
+ if (reachable === Movement.ENTERED) {
+ this.entered.push(node);
+ }
+ else if (reachable === Movement.EXITED) {
+ this.exited.push(node);
+ this.ensureHasOldPreviousSiblingIfNeeded(node);
+ }
+ else if (reachable === Movement.STAYED_IN) {
+ var movement = Movement.STAYED_IN;
+ if (change && change.childList) {
+ if (change.oldParentNode !== node.parentNode) {
+ movement = Movement.REPARENTED;
+ this.ensureHasOldPreviousSiblingIfNeeded(node);
+ }
+ else if (this.calcReordered && this.wasReordered(node)) {
+ movement = Movement.REORDERED;
+ }
+ }
+ this.stayedIn.set(node, movement);
+ }
+ if (reachable === Movement.STAYED_IN)
+ return;
+ // reachable === ENTERED || reachable === EXITED.
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ this.visitNode(child, reachable);
+ }
+ };
+ MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) {
+ if (!this.calcOldPreviousSibling)
+ return;
+ this.processChildlistChanges();
+ var parentNode = node.parentNode;
+ var nodeChange = this.treeChanges.get(node);
+ if (nodeChange && nodeChange.oldParentNode)
+ parentNode = nodeChange.oldParentNode;
+ var change = this.childListChangeMap.get(parentNode);
+ if (!change) {
+ change = new ChildListChange();
+ this.childListChangeMap.set(parentNode, change);
+ }
+ if (!change.oldPrevious.has(node)) {
+ change.oldPrevious.set(node, node.previousSibling);
+ }
+ };
+ MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) {
+ this.selectors = selectors;
+ this.characterDataOnly = characterDataOnly;
+ for (var i = 0; i < this.entered.length; i++) {
+ var node = this.entered[i];
+ var matchable = this.matchabilityChange(node);
+ if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN)
+ summary.added.push(node);
+ }
+ var stayedInNodes = this.stayedIn.keys();
+ for (var i = 0; i < stayedInNodes.length; i++) {
+ var node = stayedInNodes[i];
+ var matchable = this.matchabilityChange(node);
+ if (matchable === Movement.ENTERED) {
+ summary.added.push(node);
+ }
+ else if (matchable === Movement.EXITED) {
+ summary.removed.push(node);
+ }
+ else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
+ var movement = this.stayedIn.get(node);
+ if (summary.reparented && movement === Movement.REPARENTED)
+ summary.reparented.push(node);
+ else if (summary.reordered && movement === Movement.REORDERED)
+ summary.reordered.push(node);
+ }
+ }
+ for (var i = 0; i < this.exited.length; i++) {
+ var node = this.exited[i];
+ var matchable = this.matchabilityChange(node);
+ if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN)
+ summary.removed.push(node);
+ }
+ };
+ MutationProjection.prototype.getOldParentNode = function (node) {
+ var change = this.treeChanges.get(node);
+ if (change && change.childList)
+ return change.oldParentNode ? change.oldParentNode : null;
+ var reachabilityChange = this.treeChanges.reachabilityChange(node);
+ if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED)
+ throw Error('getOldParentNode requested on invalid node.');
+ return node.parentNode;
+ };
+ MutationProjection.prototype.getOldPreviousSibling = function (node) {
+ var parentNode = node.parentNode;
+ var nodeChange = this.treeChanges.get(node);
+ if (nodeChange && nodeChange.oldParentNode)
+ parentNode = nodeChange.oldParentNode;
+ var change = this.childListChangeMap.get(parentNode);
+ if (!change)
+ throw Error('getOldPreviousSibling requested on invalid node.');
+ return change.oldPrevious.get(node);
+ };
+ MutationProjection.prototype.getOldAttribute = function (element, attrName) {
+ var change = this.treeChanges.get(element);
+ if (!change || !change.attributes)
+ throw Error('getOldAttribute requested on invalid node.');
+ var value = change.getAttributeOldValue(attrName);
+ if (value === undefined)
+ throw Error('getOldAttribute requested for unchanged attribute name.');
+ return value;
+ };
+ MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) {
+ if (!this.treeChanges.anyAttributesChanged)
+ return {}; // No attributes mutations occurred.
+ var attributeFilter;
+ var caseInsensitiveFilter;
+ if (includeAttributes) {
+ attributeFilter = {};
+ caseInsensitiveFilter = {};
+ for (var i = 0; i < includeAttributes.length; i++) {
+ var attrName = includeAttributes[i];
+ attributeFilter[attrName] = true;
+ caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
+ }
+ }
+ var result = {};
+ var nodes = this.treeChanges.keys();
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var change = this.treeChanges.get(node);
+ if (!change.attributes)
+ continue;
+ if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
+ Movement.STAYED_IN !== this.matchabilityChange(node)) {
+ continue;
+ }
+ var element = node;
+ var changedAttrNames = change.getAttributeNamesMutated();
+ for (var j = 0; j < changedAttrNames.length; j++) {
+ var attrName = changedAttrNames[j];
+ if (attributeFilter &&
+ !attributeFilter[attrName] &&
+ !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) {
+ continue;
+ }
+ var oldValue = change.getAttributeOldValue(attrName);
+ if (oldValue === element.getAttribute(attrName))
+ continue;
+ if (caseInsensitiveFilter && change.isCaseInsensitive)
+ attrName = caseInsensitiveFilter[attrName];
+ result[attrName] = result[attrName] || [];
+ result[attrName].push(element);
+ }
+ }
+ return result;
+ };
+ MutationProjection.prototype.getOldCharacterData = function (node) {
+ var change = this.treeChanges.get(node);
+ if (!change || !change.characterData)
+ throw Error('getOldCharacterData requested on invalid node.');
+ return change.characterDataOldValue;
+ };
+ MutationProjection.prototype.getCharacterDataChanged = function () {
+ if (!this.treeChanges.anyCharacterDataChanged)
+ return []; // No characterData mutations occurred.
+ var nodes = this.treeChanges.keys();
+ var result = [];
+ for (var i = 0; i < nodes.length; i++) {
+ var target = nodes[i];
+ if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target))
+ continue;
+ var change = this.treeChanges.get(target);
+ if (!change.characterData ||
+ target.textContent == change.characterDataOldValue)
+ continue;
+ result.push(target);
+ }
+ return result;
+ };
+ MutationProjection.prototype.computeMatchabilityChange = function (selector, el) {
+ if (!this.matchCache)
+ this.matchCache = [];
+ if (!this.matchCache[selector.uid])
+ this.matchCache[selector.uid] = new NodeMap();
+ var cache = this.matchCache[selector.uid];
+ var result = cache.get(el);
+ if (result === undefined) {
+ result = selector.matchabilityChange(el, this.treeChanges.get(el));
+ cache.set(el, result);
+ }
+ return result;
+ };
+ MutationProjection.prototype.matchabilityChange = function (node) {
+ var _this = this;
+ // TODO(rafaelw): Include PI, CDATA?
+ // Only include text nodes.
+ if (this.characterDataOnly) {
+ switch (node.nodeType) {
+ case Node.COMMENT_NODE:
+ case Node.TEXT_NODE:
+ return Movement.STAYED_IN;
+ default:
+ return Movement.STAYED_OUT;
+ }
+ }
+ // No element filter. Include all nodes.
+ if (!this.selectors)
+ return Movement.STAYED_IN;
+ // Element filter. Exclude non-elements.
+ if (node.nodeType !== Node.ELEMENT_NODE)
+ return Movement.STAYED_OUT;
+ var el = node;
+ var matchChanges = this.selectors.map(function (selector) {
+ return _this.computeMatchabilityChange(selector, el);
+ });
+ var accum = Movement.STAYED_OUT;
+ var i = 0;
+ while (accum !== Movement.STAYED_IN && i < matchChanges.length) {
+ switch (matchChanges[i]) {
+ case Movement.STAYED_IN:
+ accum = Movement.STAYED_IN;
+ break;
+ case Movement.ENTERED:
+ if (accum === Movement.EXITED)
+ accum = Movement.STAYED_IN;
+ else
+ accum = Movement.ENTERED;
+ break;
+ case Movement.EXITED:
+ if (accum === Movement.ENTERED)
+ accum = Movement.STAYED_IN;
+ else
+ accum = Movement.EXITED;
+ break;
+ }
+ i++;
+ }
+ return accum;
+ };
+ MutationProjection.prototype.getChildlistChange = function (el) {
+ var change = this.childListChangeMap.get(el);
+ if (!change) {
+ change = new ChildListChange();
+ this.childListChangeMap.set(el, change);
+ }
+ return change;
+ };
+ MutationProjection.prototype.processChildlistChanges = function () {
+ if (this.childListChangeMap)
+ return;
+ this.childListChangeMap = new NodeMap();
+ for (var i = 0; i < this.mutations.length; i++) {
+ var mutation = this.mutations[i];
+ if (mutation.type != 'childList')
+ continue;
+ if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN &&
+ !this.calcOldPreviousSibling)
+ continue;
+ var change = this.getChildlistChange(mutation.target);
+ var oldPrevious = mutation.previousSibling;
+ function recordOldPrevious(node, previous) {
+ if (!node ||
+ change.oldPrevious.has(node) ||
+ change.added.has(node) ||
+ change.maybeMoved.has(node))
+ return;
+ if (previous &&
+ (change.added.has(previous) ||
+ change.maybeMoved.has(previous)))
+ return;
+ change.oldPrevious.set(node, previous);
+ }
+ for (var j = 0; j < mutation.removedNodes.length; j++) {
+ var node = mutation.removedNodes[j];
+ recordOldPrevious(node, oldPrevious);
+ if (change.added.has(node)) {
+ change.added.delete(node);
+ }
+ else {
+ change.removed.set(node, true);
+ change.maybeMoved.delete(node);
+ }
+ oldPrevious = node;
+ }
+ recordOldPrevious(mutation.nextSibling, oldPrevious);
+ for (var j = 0; j < mutation.addedNodes.length; j++) {
+ var node = mutation.addedNodes[j];
+ if (change.removed.has(node)) {
+ change.removed.delete(node);
+ change.maybeMoved.set(node, true);
+ }
+ else {
+ change.added.set(node, true);
+ }
+ }
+ }
+ };
+ MutationProjection.prototype.wasReordered = function (node) {
+ if (!this.treeChanges.anyParentsChanged)
+ return false;
+ this.processChildlistChanges();
+ var parentNode = node.parentNode;
+ var nodeChange = this.treeChanges.get(node);
+ if (nodeChange && nodeChange.oldParentNode)
+ parentNode = nodeChange.oldParentNode;
+ var change = this.childListChangeMap.get(parentNode);
+ if (!change)
+ return false;
+ if (change.moved)
+ return change.moved.get(node);
+ change.moved = new NodeMap();
+ var pendingMoveDecision = new NodeMap();
+ function isMoved(node) {
+ if (!node)
+ return false;
+ if (!change.maybeMoved.has(node))
+ return false;
+ var didMove = change.moved.get(node);
+ if (didMove !== undefined)
+ return didMove;
+ if (pendingMoveDecision.has(node)) {
+ didMove = true;
+ }
+ else {
+ pendingMoveDecision.set(node, true);
+ didMove = getPrevious(node) !== getOldPrevious(node);
+ }
+ if (pendingMoveDecision.has(node)) {
+ pendingMoveDecision.delete(node);
+ change.moved.set(node, didMove);
+ }
+ else {
+ didMove = change.moved.get(node);
+ }
+ return didMove;
+ }
+ var oldPreviousCache = new NodeMap();
+ function getOldPrevious(node) {
+ var oldPrevious = oldPreviousCache.get(node);
+ if (oldPrevious !== undefined)
+ return oldPrevious;
+ oldPrevious = change.oldPrevious.get(node);
+ while (oldPrevious &&
+ (change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
+ oldPrevious = getOldPrevious(oldPrevious);
+ }
+ if (oldPrevious === undefined)
+ oldPrevious = node.previousSibling;
+ oldPreviousCache.set(node, oldPrevious);
+ return oldPrevious;
+ }
+ var previousCache = new NodeMap();
+ function getPrevious(node) {
+ if (previousCache.has(node))
+ return previousCache.get(node);
+ var previous = node.previousSibling;
+ while (previous && (change.added.has(previous) || isMoved(previous)))
+ previous = previous.previousSibling;
+ previousCache.set(node, previous);
+ return previous;
+ }
+ change.maybeMoved.keys().forEach(isMoved);
+ return change.moved.get(node);
+ };
+ return MutationProjection;
+})();
+var Summary = (function () {
+ function Summary(projection, query) {
+ var _this = this;
+ this.projection = projection;
+ this.added = [];
+ this.removed = [];
+ this.reparented = query.all || query.element || query.characterData ? [] : undefined;
+ this.reordered = query.all ? [] : undefined;
+ projection.getChanged(this, query.elementFilter, query.characterData);
+ if (query.all || query.attribute || query.attributeList) {
+ var filter = query.attribute ? [query.attribute] : query.attributeList;
+ var attributeChanged = projection.attributeChangedNodes(filter);
+ if (query.attribute) {
+ this.valueChanged = attributeChanged[query.attribute] || [];
+ }
+ else {
+ this.attributeChanged = attributeChanged;
+ if (query.attributeList) {
+ query.attributeList.forEach(function (attrName) {
+ if (!_this.attributeChanged.hasOwnProperty(attrName))
+ _this.attributeChanged[attrName] = [];
+ });
+ }
+ }
+ }
+ if (query.all || query.characterData) {
+ var characterDataChanged = projection.getCharacterDataChanged();
+ if (query.characterData)
+ this.valueChanged = characterDataChanged;
+ else
+ this.characterDataChanged = characterDataChanged;
+ }
+ if (this.reordered)
+ this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
+ }
+ Summary.prototype.getOldParentNode = function (node) {
+ return this.projection.getOldParentNode(node);
+ };
+ Summary.prototype.getOldAttribute = function (node, name) {
+ return this.projection.getOldAttribute(node, name);
+ };
+ Summary.prototype.getOldCharacterData = function (node) {
+ return this.projection.getOldCharacterData(node);
+ };
+ Summary.prototype.getOldPreviousSibling = function (node) {
+ return this.projection.getOldPreviousSibling(node);
+ };
+ return Summary;
+})();
+// TODO(rafaelw): Allow ':' and '.' as valid name characters.
+var validNameInitialChar = /[a-zA-Z_]+/;
+var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
+// TODO(rafaelw): Consider allowing backslash in the attrValue.
+// TODO(rafaelw): There's got a to be way to represent this state machine
+// more compactly???
+function escapeQuotes(value) {
+ return '"' + value.replace(/"/, '\\\"') + '"';
+}
+var Qualifier = (function () {
+ function Qualifier() {
+ }
+ Qualifier.prototype.matches = function (oldValue) {
+ if (oldValue === null)
+ return false;
+ if (this.attrValue === undefined)
+ return true;
+ if (!this.contains)
+ return this.attrValue == oldValue;
+ var tokens = oldValue.split(' ');
+ for (var i = 0; i < tokens.length; i++) {
+ if (this.attrValue === tokens[i])
+ return true;
+ }
+ return false;
+ };
+ Qualifier.prototype.toString = function () {
+ if (this.attrName === 'class' && this.contains)
+ return '.' + this.attrValue;
+ if (this.attrName === 'id' && !this.contains)
+ return '#' + this.attrValue;
+ if (this.contains)
+ return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
+ if ('attrValue' in this)
+ return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
+ return '[' + this.attrName + ']';
+ };
+ return Qualifier;
+})();
+var Selector = (function () {
+ function Selector() {
+ this.uid = Selector.nextUid++;
+ this.qualifiers = [];
+ }
+ Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", {
+ get: function () {
+ return this.tagName.toUpperCase();
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Object.defineProperty(Selector.prototype, "selectorString", {
+ get: function () {
+ return this.tagName + this.qualifiers.join('');
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Selector.prototype.isMatching = function (el) {
+ return el[Selector.matchesSelector](this.selectorString);
+ };
+ Selector.prototype.wasMatching = function (el, change, isMatching) {
+ if (!change || !change.attributes)
+ return isMatching;
+ var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
+ if (tagName !== '*' && tagName !== el.tagName)
+ return false;
+ var attributeOldValues = [];
+ var anyChanged = false;
+ for (var i = 0; i < this.qualifiers.length; i++) {
+ var qualifier = this.qualifiers[i];
+ var oldValue = change.getAttributeOldValue(qualifier.attrName);
+ attributeOldValues.push(oldValue);
+ anyChanged = anyChanged || (oldValue !== undefined);
+ }
+ if (!anyChanged)
+ return isMatching;
+ for (var i = 0; i < this.qualifiers.length; i++) {
+ var qualifier = this.qualifiers[i];
+ var oldValue = attributeOldValues[i];
+ if (oldValue === undefined)
+ oldValue = el.getAttribute(qualifier.attrName);
+ if (!qualifier.matches(oldValue))
+ return false;
+ }
+ return true;
+ };
+ Selector.prototype.matchabilityChange = function (el, change) {
+ var isMatching = this.isMatching(el);
+ if (isMatching)
+ return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED;
+ else
+ return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT;
+ };
+ Selector.parseSelectors = function (input) {
+ var selectors = [];
+ var currentSelector;
+ var currentQualifier;
+ function newSelector() {
+ if (currentSelector) {
+ if (currentQualifier) {
+ currentSelector.qualifiers.push(currentQualifier);
+ currentQualifier = undefined;
+ }
+ selectors.push(currentSelector);
+ }
+ currentSelector = new Selector();
+ }
+ function newQualifier() {
+ if (currentQualifier)
+ currentSelector.qualifiers.push(currentQualifier);
+ currentQualifier = new Qualifier();
+ }
+ var WHITESPACE = /\s/;
+ var valueQuoteChar;
+ var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
+ var SELECTOR = 1;
+ var TAG_NAME = 2;
+ var QUALIFIER = 3;
+ var QUALIFIER_NAME_FIRST_CHAR = 4;
+ var QUALIFIER_NAME = 5;
+ var ATTR_NAME_FIRST_CHAR = 6;
+ var ATTR_NAME = 7;
+ var EQUIV_OR_ATTR_QUAL_END = 8;
+ var EQUAL = 9;
+ var ATTR_QUAL_END = 10;
+ var VALUE_FIRST_CHAR = 11;
+ var VALUE = 12;
+ var QUOTED_VALUE = 13;
+ var SELECTOR_SEPARATOR = 14;
+ var state = SELECTOR;
+ var i = 0;
+ while (i < input.length) {
+ var c = input[i++];
+ switch (state) {
+ case SELECTOR:
+ if (c.match(validNameInitialChar)) {
+ newSelector();
+ currentSelector.tagName = c;
+ state = TAG_NAME;
+ break;
+ }
+ if (c == '*') {
+ newSelector();
+ currentSelector.tagName = '*';
+ state = QUALIFIER;
+ break;
+ }
+ if (c == '.') {
+ newSelector();
+ newQualifier();
+ currentSelector.tagName = '*';
+ currentQualifier.attrName = 'class';
+ currentQualifier.contains = true;
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '#') {
+ newSelector();
+ newQualifier();
+ currentSelector.tagName = '*';
+ currentQualifier.attrName = 'id';
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '[') {
+ newSelector();
+ newQualifier();
+ currentSelector.tagName = '*';
+ currentQualifier.attrName = '';
+ state = ATTR_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c.match(WHITESPACE))
+ break;
+ throw Error(SYNTAX_ERROR);
+ case TAG_NAME:
+ if (c.match(validNameNonInitialChar)) {
+ currentSelector.tagName += c;
+ break;
+ }
+ if (c == '.') {
+ newQualifier();
+ currentQualifier.attrName = 'class';
+ currentQualifier.contains = true;
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '#') {
+ newQualifier();
+ currentQualifier.attrName = 'id';
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '[') {
+ newQualifier();
+ currentQualifier.attrName = '';
+ state = ATTR_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c.match(WHITESPACE)) {
+ state = SELECTOR_SEPARATOR;
+ break;
+ }
+ if (c == ',') {
+ state = SELECTOR;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case QUALIFIER:
+ if (c == '.') {
+ newQualifier();
+ currentQualifier.attrName = 'class';
+ currentQualifier.contains = true;
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '#') {
+ newQualifier();
+ currentQualifier.attrName = 'id';
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '[') {
+ newQualifier();
+ currentQualifier.attrName = '';
+ state = ATTR_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c.match(WHITESPACE)) {
+ state = SELECTOR_SEPARATOR;
+ break;
+ }
+ if (c == ',') {
+ state = SELECTOR;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case QUALIFIER_NAME_FIRST_CHAR:
+ if (c.match(validNameInitialChar)) {
+ currentQualifier.attrValue = c;
+ state = QUALIFIER_NAME;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case QUALIFIER_NAME:
+ if (c.match(validNameNonInitialChar)) {
+ currentQualifier.attrValue += c;
+ break;
+ }
+ if (c == '.') {
+ newQualifier();
+ currentQualifier.attrName = 'class';
+ currentQualifier.contains = true;
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '#') {
+ newQualifier();
+ currentQualifier.attrName = 'id';
+ state = QUALIFIER_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c == '[') {
+ newQualifier();
+ state = ATTR_NAME_FIRST_CHAR;
+ break;
+ }
+ if (c.match(WHITESPACE)) {
+ state = SELECTOR_SEPARATOR;
+ break;
+ }
+ if (c == ',') {
+ state = SELECTOR;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case ATTR_NAME_FIRST_CHAR:
+ if (c.match(validNameInitialChar)) {
+ currentQualifier.attrName = c;
+ state = ATTR_NAME;
+ break;
+ }
+ if (c.match(WHITESPACE))
+ break;
+ throw Error(SYNTAX_ERROR);
+ case ATTR_NAME:
+ if (c.match(validNameNonInitialChar)) {
+ currentQualifier.attrName += c;
+ break;
+ }
+ if (c.match(WHITESPACE)) {
+ state = EQUIV_OR_ATTR_QUAL_END;
+ break;
+ }
+ if (c == '~') {
+ currentQualifier.contains = true;
+ state = EQUAL;
+ break;
+ }
+ if (c == '=') {
+ currentQualifier.attrValue = '';
+ state = VALUE_FIRST_CHAR;
+ break;
+ }
+ if (c == ']') {
+ state = QUALIFIER;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case EQUIV_OR_ATTR_QUAL_END:
+ if (c == '~') {
+ currentQualifier.contains = true;
+ state = EQUAL;
+ break;
+ }
+ if (c == '=') {
+ currentQualifier.attrValue = '';
+ state = VALUE_FIRST_CHAR;
+ break;
+ }
+ if (c == ']') {
+ state = QUALIFIER;
+ break;
+ }
+ if (c.match(WHITESPACE))
+ break;
+ throw Error(SYNTAX_ERROR);
+ case EQUAL:
+ if (c == '=') {
+ currentQualifier.attrValue = '';
+ state = VALUE_FIRST_CHAR;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ case ATTR_QUAL_END:
+ if (c == ']') {
+ state = QUALIFIER;
+ break;
+ }
+ if (c.match(WHITESPACE))
+ break;
+ throw Error(SYNTAX_ERROR);
+ case VALUE_FIRST_CHAR:
+ if (c.match(WHITESPACE))
+ break;
+ if (c == '"' || c == "'") {
+ valueQuoteChar = c;
+ state = QUOTED_VALUE;
+ break;
+ }
+ currentQualifier.attrValue += c;
+ state = VALUE;
+ break;
+ case VALUE:
+ if (c.match(WHITESPACE)) {
+ state = ATTR_QUAL_END;
+ break;
+ }
+ if (c == ']') {
+ state = QUALIFIER;
+ break;
+ }
+ if (c == "'" || c == '"')
+ throw Error(SYNTAX_ERROR);
+ currentQualifier.attrValue += c;
+ break;
+ case QUOTED_VALUE:
+ if (c == valueQuoteChar) {
+ state = ATTR_QUAL_END;
+ break;
+ }
+ currentQualifier.attrValue += c;
+ break;
+ case SELECTOR_SEPARATOR:
+ if (c.match(WHITESPACE))
+ break;
+ if (c == ',') {
+ state = SELECTOR;
+ break;
+ }
+ throw Error(SYNTAX_ERROR);
+ }
+ }
+ switch (state) {
+ case SELECTOR:
+ case TAG_NAME:
+ case QUALIFIER:
+ case QUALIFIER_NAME:
+ case SELECTOR_SEPARATOR:
+ // Valid end states.
+ newSelector();
+ break;
+ default:
+ throw Error(SYNTAX_ERROR);
+ }
+ if (!selectors.length)
+ throw Error(SYNTAX_ERROR);
+ return selectors;
+ };
+ Selector.nextUid = 1;
+ Selector.matchesSelector = (function () {
+ var element = document.createElement('div');
+ if (typeof element['webkitMatchesSelector'] === 'function')
+ return 'webkitMatchesSelector';
+ if (typeof element['mozMatchesSelector'] === 'function')
+ return 'mozMatchesSelector';
+ if (typeof element['msMatchesSelector'] === 'function')
+ return 'msMatchesSelector';
+ return 'matchesSelector';
+ })();
+ return Selector;
+})();
+var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/;
+function validateAttribute(attribute) {
+ if (typeof attribute != 'string')
+ throw Error('Invalid request opion. attribute must be a non-zero length string.');
+ attribute = attribute.trim();
+ if (!attribute)
+ throw Error('Invalid request opion. attribute must be a non-zero length string.');
+ if (!attribute.match(attributeFilterPattern))
+ throw Error('Invalid request option. invalid attribute name: ' + attribute);
+ return attribute;
+}
+function validateElementAttributes(attribs) {
+ if (!attribs.trim().length)
+ throw Error('Invalid request option: elementAttributes must contain at least one attribute.');
+ var lowerAttributes = {};
+ var attributes = {};
+ var tokens = attribs.split(/\s+/);
+ for (var i = 0; i < tokens.length; i++) {
+ var name = tokens[i];
+ if (!name)
+ continue;
+ var name = validateAttribute(name);
+ var nameLower = name.toLowerCase();
+ if (lowerAttributes[nameLower])
+ throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.');
+ attributes[name] = true;
+ lowerAttributes[nameLower] = true;
+ }
+ return Object.keys(attributes);
+}
+function elementFilterAttributes(selectors) {
+ var attributes = {};
+ selectors.forEach(function (selector) {
+ selector.qualifiers.forEach(function (qualifier) {
+ attributes[qualifier.attrName] = true;
+ });
+ });
+ return Object.keys(attributes);
+}
+var MutationSummary = (function () {
+ function MutationSummary(opts) {
+ var _this = this;
+ this.connected = false;
+ this.options = MutationSummary.validateOptions(opts);
+ this.observerOptions = MutationSummary.createObserverOptions(this.options.queries);
+ this.root = this.options.rootNode;
+ this.callback = this.options.callback;
+ this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) {
+ return query.elementFilter ? query.elementFilter : [];
+ }));
+ if (!this.elementFilter.length)
+ this.elementFilter = undefined;
+ this.calcReordered = this.options.queries.some(function (query) {
+ return query.all;
+ });
+ this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this.
+ if (MutationSummary.createQueryValidator) {
+ this.queryValidators = this.options.queries.map(function (query) {
+ return MutationSummary.createQueryValidator(_this.root, query);
+ });
+ }
+ this.observer = new MutationObserverCtor(function (mutations) {
+ _this.observerCallback(mutations);
+ });
+ this.reconnect();
+ }
+ MutationSummary.createObserverOptions = function (queries) {
+ var observerOptions = {
+ childList: true,
+ subtree: true
+ };
+ var attributeFilter;
+ function observeAttributes(attributes) {
+ if (observerOptions.attributes && !attributeFilter)
+ return; // already observing all.
+ observerOptions.attributes = true;
+ observerOptions.attributeOldValue = true;
+ if (!attributes) {
+ // observe all.
+ attributeFilter = undefined;
+ return;
+ }
+ // add to observed.
+ attributeFilter = attributeFilter || {};
+ attributes.forEach(function (attribute) {
+ attributeFilter[attribute] = true;
+ attributeFilter[attribute.toLowerCase()] = true;
+ });
+ }
+ queries.forEach(function (query) {
+ if (query.characterData) {
+ observerOptions.characterData = true;
+ observerOptions.characterDataOldValue = true;
+ return;
+ }
+ if (query.all) {
+ observeAttributes();
+ observerOptions.characterData = true;
+ observerOptions.characterDataOldValue = true;
+ return;
+ }
+ if (query.attribute) {
+ observeAttributes([query.attribute.trim()]);
+ return;
+ }
+ var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []);
+ if (attributes.length)
+ observeAttributes(attributes);
+ });
+ if (attributeFilter)
+ observerOptions.attributeFilter = Object.keys(attributeFilter);
+ return observerOptions;
+ };
+ MutationSummary.validateOptions = function (options) {
+ for (var prop in options) {
+ if (!(prop in MutationSummary.optionKeys))
+ throw Error('Invalid option: ' + prop);
+ }
+ if (typeof options.callback !== 'function')
+ throw Error('Invalid options: callback is required and must be a function');
+ if (!options.queries || !options.queries.length)
+ throw Error('Invalid options: queries must contain at least one query request object.');
+ var opts = {
+ callback: options.callback,
+ rootNode: options.rootNode || document,
+ observeOwnChanges: !!options.observeOwnChanges,
+ oldPreviousSibling: !!options.oldPreviousSibling,
+ queries: []
+ };
+ for (var i = 0; i < options.queries.length; i++) {
+ var request = options.queries[i];
+ // all
+ if (request.all) {
+ if (Object.keys(request).length > 1)
+ throw Error('Invalid request option. all has no options.');
+ opts.queries.push({ all: true });
+ continue;
+ }
+ // attribute
+ if ('attribute' in request) {
+ var query = {
+ attribute: validateAttribute(request.attribute)
+ };
+ query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']');
+ if (Object.keys(request).length > 1)
+ throw Error('Invalid request option. attribute has no options.');
+ opts.queries.push(query);
+ continue;
+ }
+ // element
+ if ('element' in request) {
+ var requestOptionCount = Object.keys(request).length;
+ var query = {
+ element: request.element,
+ elementFilter: Selector.parseSelectors(request.element)
+ };
+ if (request.hasOwnProperty('elementAttributes')) {
+ query.attributeList = validateElementAttributes(request.elementAttributes);
+ requestOptionCount--;
+ }
+ if (requestOptionCount > 1)
+ throw Error('Invalid request option. element only allows elementAttributes option.');
+ opts.queries.push(query);
+ continue;
+ }
+ // characterData
+ if (request.characterData) {
+ if (Object.keys(request).length > 1)
+ throw Error('Invalid request option. characterData has no options.');
+ opts.queries.push({ characterData: true });
+ continue;
+ }
+ throw Error('Invalid request option. Unknown query request.');
+ }
+ return opts;
+ };
+ MutationSummary.prototype.createSummaries = function (mutations) {
+ if (!mutations || !mutations.length)
+ return [];
+ var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling);
+ var summaries = [];
+ for (var i = 0; i < this.options.queries.length; i++) {
+ summaries.push(new Summary(projection, this.options.queries[i]));
+ }
+ return summaries;
+ };
+ MutationSummary.prototype.checkpointQueryValidators = function () {
+ this.queryValidators.forEach(function (validator) {
+ if (validator)
+ validator.recordPreviousState();
+ });
+ };
+ MutationSummary.prototype.runQueryValidators = function (summaries) {
+ this.queryValidators.forEach(function (validator, index) {
+ if (validator)
+ validator.validate(summaries[index]);
+ });
+ };
+ MutationSummary.prototype.changesToReport = function (summaries) {
+ return summaries.some(function (summary) {
+ var summaryProps = ['added', 'removed', 'reordered', 'reparented',
+ 'valueChanged', 'characterDataChanged'];
+ if (summaryProps.some(function (prop) { return summary[prop] && summary[prop].length; }))
+ return true;
+ if (summary.attributeChanged) {
+ var attrNames = Object.keys(summary.attributeChanged);
+ var attrsChanged = attrNames.some(function (attrName) {
+ return !!summary.attributeChanged[attrName].length;
+ });
+ if (attrsChanged)
+ return true;
+ }
+ return false;
+ });
+ };
+ MutationSummary.prototype.observerCallback = function (mutations) {
+ if (!this.options.observeOwnChanges)
+ this.observer.disconnect();
+ var summaries = this.createSummaries(mutations);
+ this.runQueryValidators(summaries);
+ if (this.options.observeOwnChanges)
+ this.checkpointQueryValidators();
+ if (this.changesToReport(summaries))
+ this.callback(summaries);
+ // disconnect() may have been called during the callback.
+ if (!this.options.observeOwnChanges && this.connected) {
+ this.checkpointQueryValidators();
+ this.observer.observe(this.root, this.observerOptions);
+ }
+ };
+ MutationSummary.prototype.reconnect = function () {
+ if (this.connected)
+ throw Error('Already connected');
+ this.observer.observe(this.root, this.observerOptions);
+ this.connected = true;
+ this.checkpointQueryValidators();
+ };
+ MutationSummary.prototype.takeSummaries = function () {
+ if (!this.connected)
+ throw Error('Not connected');
+ var summaries = this.createSummaries(this.observer.takeRecords());
+ return this.changesToReport(summaries) ? summaries : undefined;
+ };
+ MutationSummary.prototype.disconnect = function () {
+ var summaries = this.takeSummaries();
+ this.observer.disconnect();
+ this.connected = false;
+ return summaries;
+ };
+ MutationSummary.NodeMap = NodeMap; // exposed for use in TreeMirror.
+ MutationSummary.parseElementFilter = Selector.parseSelectors; // exposed for testing.
+ MutationSummary.optionKeys = {
+ 'callback': true,
+ 'queries': true,
+ 'rootNode': true,
+ 'oldPreviousSibling': true,
+ 'observeOwnChanges': true
+ };
+ return MutationSummary;
+})();
+}
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.ts b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.ts
new file mode 100644
index 0000000..ee3a268
--- /dev/null
+++ b/files/plugin-HeatmapSessionRecording-5.2.3/libs/mutation-summary/src/mutation-summary.ts
@@ -0,0 +1,1750 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var MutationObserverCtor;
+if (typeof WebKitMutationObserver !== 'undefined')
+ MutationObserverCtor = WebKitMutationObserver;
+else
+ MutationObserverCtor = MutationObserver;
+
+if (MutationObserverCtor === undefined) {
+ console.error('DOM Mutation Observers are required.');
+ console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver');
+ throw Error('DOM Mutation Observers are required');
+}
+
+interface StringMap