| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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. |
| |
| #include <memory> |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop/message_loop.h" |
| #include "cobalt/dom/element.h" |
| #include "cobalt/dom/mutation_observer_init.h" |
| #include "cobalt/dom/mutation_observer_task_manager.h" |
| #include "cobalt/dom/mutation_record.h" |
| #include "cobalt/dom/mutation_reporter.h" |
| #include "cobalt/dom/node_list.h" |
| #include "cobalt/dom/text.h" |
| #include "cobalt/script/sequence.h" |
| #include "cobalt/script/testing/mock_exception_state.h" |
| #include "cobalt/test/empty_document.h" |
| #include "cobalt/test/mock_debugger_hooks.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::SaveArg; |
| |
| constexpr auto kOneshot = base::DebuggerHooks::AsyncTaskFrequency::kOneshot; |
| |
| namespace cobalt { |
| namespace dom { |
| // Helper struct for childList mutations. |
| struct ChildListMutationArguments { |
| scoped_refptr<dom::Element> previous_sibling; |
| scoped_refptr<dom::Element> next_sibling; |
| scoped_refptr<dom::NodeList> added_nodes; |
| scoped_refptr<dom::NodeList> removed_nodes; |
| }; |
| |
| typedef ::testing::StrictMock<test::MockDebuggerHooks> DebuggerHooksMock; |
| |
| class MutationCallbackMock { |
| public: |
| MOCK_METHOD2(NativeMutationCallback, |
| void(const MutationObserver::MutationRecordSequence&, |
| const scoped_refptr<MutationObserver>&)); |
| }; |
| |
| class MutationObserverTest : public ::testing::Test { |
| protected: |
| dom::Document* document() { return empty_document_.document(); } |
| MutationObserverTaskManager* task_manager() { return &task_manager_; } |
| DebuggerHooksMock* debugger_hooks() { return &debugger_hooks_; } |
| MutationCallbackMock* callback_mock() { return &callback_mock_; } |
| |
| scoped_refptr<Element> CreateDiv() { |
| scoped_refptr<Element> element = document()->CreateElement("div"); |
| DCHECK(element); |
| document()->AppendChild(element); |
| return element; |
| } |
| |
| scoped_refptr<MutationObserver> CreateObserver() { |
| return new MutationObserver( |
| base::Bind(&MutationCallbackMock::NativeMutationCallback, |
| base::Unretained(&callback_mock_)), |
| &task_manager_, debugger_hooks_); |
| } |
| |
| ChildListMutationArguments CreateChildListMutationArguments() { |
| // These nodes are not actually related in the way they are named |
| // (next_sibling, previous_sibling, etc) but that is not relevant for the |
| // purpose of these tests. |
| ChildListMutationArguments args; |
| args.previous_sibling = CreateDiv(); |
| args.next_sibling = CreateDiv(); |
| args.added_nodes = new NodeList(); |
| args.added_nodes->AppendNode(CreateDiv()); |
| args.removed_nodes = new NodeList(); |
| args.removed_nodes->AppendNode(CreateDiv()); |
| return args; |
| } |
| |
| private: |
| MutationObserverTaskManager task_manager_; |
| DebuggerHooksMock debugger_hooks_; |
| test::EmptyDocument empty_document_; |
| MutationCallbackMock callback_mock_; |
| base::MessageLoop message_loop_; |
| }; |
| |
| TEST_F(MutationObserverTest, CreateAttributeMutationRecord) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationRecord> record = |
| MutationRecord::CreateAttributeMutationRecord(target, "attribute_name", |
| std::string("old_value")); |
| ASSERT_TRUE(record); |
| |
| // "type" and attribute-related attributes are set as expected |
| EXPECT_STREQ("attributes", record->type().c_str()); |
| EXPECT_TRUE(record->attribute_name()); |
| EXPECT_STREQ("attribute_name", record->attribute_name()->c_str()); |
| EXPECT_TRUE(record->old_value()); |
| EXPECT_STREQ("old_value", record->old_value()->c_str()); |
| |
| // "target" is set as expected. |
| EXPECT_EQ(target, record->target()); |
| |
| // Namespaces are not supported. |
| EXPECT_FALSE(record->attribute_namespace()); |
| |
| // Unrelated attributes are not set. |
| ASSERT_TRUE(record->added_nodes()); |
| EXPECT_EQ(record->added_nodes()->length(), 0); |
| ASSERT_TRUE(record->removed_nodes()); |
| EXPECT_EQ(record->removed_nodes()->length(), 0); |
| EXPECT_FALSE(record->previous_sibling()); |
| EXPECT_FALSE(record->next_sibling()); |
| } |
| |
| TEST_F(MutationObserverTest, CreateCharacterDataMutationRecord) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationRecord> record = |
| MutationRecord::CreateCharacterDataMutationRecord( |
| target, std::string("old_character_data")); |
| ASSERT_TRUE(record); |
| |
| // "type" and attribute-related attributes are set as expected |
| EXPECT_STREQ("characterData", record->type().c_str()); |
| EXPECT_TRUE(record->old_value()); |
| EXPECT_STREQ("old_character_data", record->old_value()->c_str()); |
| |
| // "target" is set as expected. |
| EXPECT_EQ(target, record->target()); |
| |
| // Unrelated attributes are not set. |
| EXPECT_FALSE(record->attribute_name()); |
| EXPECT_FALSE(record->attribute_namespace()); |
| ASSERT_TRUE(record->added_nodes()); |
| EXPECT_EQ(record->added_nodes()->length(), 0); |
| ASSERT_TRUE(record->removed_nodes()); |
| EXPECT_EQ(record->removed_nodes()->length(), 0); |
| EXPECT_FALSE(record->previous_sibling()); |
| EXPECT_FALSE(record->next_sibling()); |
| } |
| |
| TEST_F(MutationObserverTest, CreateChildListMutationRecord) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| |
| ChildListMutationArguments args = CreateChildListMutationArguments(); |
| scoped_refptr<MutationRecord> record = |
| MutationRecord::CreateChildListMutationRecord( |
| target, args.added_nodes, args.removed_nodes, args.previous_sibling, |
| args.next_sibling); |
| ASSERT_TRUE(record); |
| |
| // "type" and attribute-related attributes are set as expected |
| EXPECT_STREQ("childList", record->type().c_str()); |
| EXPECT_EQ(args.added_nodes, record->added_nodes()); |
| EXPECT_EQ(args.removed_nodes, record->removed_nodes()); |
| EXPECT_EQ(args.previous_sibling, record->previous_sibling()); |
| EXPECT_EQ(args.next_sibling, record->next_sibling()); |
| |
| // "target" is set as expected. |
| EXPECT_EQ(target, record->target()); |
| |
| // Unrelated attributes are not set. |
| EXPECT_FALSE(record->attribute_name()); |
| EXPECT_FALSE(record->attribute_namespace()); |
| EXPECT_FALSE(record->old_value()); |
| } |
| |
| TEST_F(MutationObserverTest, MutationObserverInit) { |
| MutationObserverInit init; |
| // Default values are set according to spec. |
| EXPECT_FALSE(init.child_list()); |
| EXPECT_FALSE(init.subtree()); |
| |
| // Other values are not set. |
| EXPECT_FALSE(init.has_attributes()); |
| EXPECT_FALSE(init.has_character_data()); |
| EXPECT_FALSE(init.has_attribute_old_value()); |
| EXPECT_FALSE(init.has_character_data_old_value()); |
| EXPECT_FALSE(init.has_attribute_filter()); |
| |
| // Set other values. |
| init.set_attributes(true); |
| init.set_character_data(true); |
| init.set_attribute_old_value(true); |
| init.set_character_data_old_value(true); |
| script::Sequence<std::string> attribute_filter; |
| attribute_filter.push_back("a_filter"); |
| init.set_attribute_filter(attribute_filter); |
| |
| // Other values are now set. |
| EXPECT_TRUE(init.has_attributes()); |
| EXPECT_TRUE(init.attributes()); |
| EXPECT_TRUE(init.has_character_data()); |
| EXPECT_TRUE(init.character_data()); |
| EXPECT_TRUE(init.has_attribute_old_value()); |
| EXPECT_TRUE(init.attribute_old_value()); |
| EXPECT_TRUE(init.has_character_data_old_value()); |
| EXPECT_TRUE(init.character_data_old_value()); |
| EXPECT_TRUE(init.has_attribute_filter()); |
| EXPECT_EQ(init.attribute_filter().size(), attribute_filter.size()); |
| EXPECT_EQ(init.attribute_filter().at(0), attribute_filter.at(0)); |
| } |
| |
| TEST_F(MutationObserverTest, TakeRecords) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| |
| // Newly created observer shouldn't have records. |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserver::MutationRecordSequence records; |
| records = observer->TakeRecords(); |
| EXPECT_TRUE(records.empty()); |
| |
| // Append a mutation records. |
| scoped_refptr<MutationRecord> record = |
| MutationRecord::CreateCharacterDataMutationRecord( |
| target, std::string("old_character_data")); |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->QueueMutationRecord(record); |
| |
| // The queued record can be taken once. |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| ASSERT_EQ(records.at(0), record); |
| records = observer->TakeRecords(); |
| EXPECT_TRUE(records.empty()); |
| } |
| |
| TEST_F(MutationObserverTest, Notify) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| |
| // Create a new observer and queue a mutation record. |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| scoped_refptr<MutationRecord> record = |
| MutationRecord::CreateCharacterDataMutationRecord( |
| target, std::string("old_character_data")); |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->QueueMutationRecord(record); |
| |
| // Callback should be fired with the first argument being a sequence of the |
| // queued record, and the second argument being the observer. |
| MutationObserver::MutationRecordSequence records; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskStarted(async_task)); |
| EXPECT_CALL(*callback_mock(), NativeMutationCallback(_, observer)) |
| .WillOnce(SaveArg<0>(&records)); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskFinished(async_task)); |
| observer->Notify(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ(record, records.at(0)); |
| |
| // There should be no more records queued up after the callback has been |
| // fired. |
| records = observer->TakeRecords(); |
| EXPECT_TRUE(records.empty()); |
| |
| // Queue another mutation record on the same observer. |
| record = MutationRecord::CreateAttributeMutationRecord( |
| target, "attribute_name", std::string("old_attribute_data")); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "attributes", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->QueueMutationRecord(record); |
| |
| // Check that the new record goes to the callback. |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskStarted(async_task)); |
| EXPECT_CALL(*callback_mock(), NativeMutationCallback(_, observer)) |
| .WillOnce(SaveArg<0>(&records)); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskFinished(async_task)); |
| observer->Notify(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ(record, records.at(0)); |
| |
| // No more records after notifying. |
| records = observer->TakeRecords(); |
| EXPECT_TRUE(records.empty()); |
| } |
| |
| TEST_F(MutationObserverTest, ReportMutation) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| // Create a registered observer that cares about attribute and character data |
| // mutations. |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit init; |
| init.set_attributes(true); |
| init.set_child_list(false); |
| init.set_character_data(true); |
| |
| // Create a MutationReporter for the list of registered observers. |
| std::unique_ptr<std::vector<RegisteredObserver> > registered_observers( |
| new std::vector<RegisteredObserver>()); |
| registered_observers->push_back( |
| RegisteredObserver(target.get(), observer, init)); |
| MutationReporter reporter(target.get(), std::move(registered_observers)); |
| |
| // Report a few mutations. |
| const void* async_task_1; |
| const void* async_task_2; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "attributes", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task_1)); |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task_2)); |
| reporter.ReportAttributesMutation("attribute_name", std::string("old_value")); |
| reporter.ReportCharacterDataMutation("old_character_data"); |
| ChildListMutationArguments args = CreateChildListMutationArguments(); |
| reporter.ReportChildListMutation(args.added_nodes, args.removed_nodes, |
| args.previous_sibling, args.next_sibling); |
| |
| // Check that mutation records for the mutation types we care about have |
| // been queued. |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_1)); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_2)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(2, records.size()); |
| EXPECT_EQ(records.at(0)->type(), "attributes"); |
| EXPECT_EQ(records.at(1)->type(), "characterData"); |
| } |
| |
| TEST_F(MutationObserverTest, AttributeFilter) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| // Create a registered observer that cares about attribute and character data |
| // mutations. |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit init; |
| script::Sequence<std::string> attribute_filter; |
| attribute_filter.push_back("banana"); |
| attribute_filter.push_back("potato"); |
| init.set_attribute_filter(attribute_filter); |
| init.set_attributes(true); |
| |
| // Create a MutationReporter for the list of registered observers. |
| std::unique_ptr<std::vector<RegisteredObserver> > registered_observers( |
| new std::vector<RegisteredObserver>()); |
| registered_observers->push_back( |
| RegisteredObserver(target.get(), observer, init)); |
| MutationReporter reporter(target.get(), std::move(registered_observers)); |
| |
| // Report a few attribute mutations, two of which will get through the filter. |
| const void* async_task_1; |
| const void* async_task_2; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "attributes", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task_1)) |
| .WillOnce(SaveArg<0>(&async_task_2)); |
| reporter.ReportAttributesMutation("banana", std::string("rotten")); |
| reporter.ReportAttributesMutation("apple", std::string("wormy")); |
| reporter.ReportAttributesMutation("potato", std::string("mashed")); |
| |
| // Check that mutation records for the filtered attributes have been queued. |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_1)); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_2)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(2, records.size()); |
| EXPECT_STREQ(records.at(0)->attribute_name()->c_str(), "banana"); |
| EXPECT_STREQ(records.at(1)->attribute_name()->c_str(), "potato"); |
| } |
| |
| TEST_F(MutationObserverTest, RegisteredObserverList) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| |
| RegisteredObserverList observer_list(target.get()); |
| |
| // Add an observer with options. |
| MutationObserverInit options; |
| options.set_attributes(true); |
| EXPECT_TRUE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(1, observer_list.registered_observers().size()); |
| EXPECT_EQ(observer, observer_list.registered_observers()[0].observer()); |
| EXPECT_TRUE(observer_list.registered_observers()[0].options().attributes()); |
| EXPECT_FALSE( |
| observer_list.registered_observers()[0].options().has_character_data()); |
| EXPECT_FALSE(observer_list.registered_observers()[0].options().child_list()); |
| |
| // Adding the same observer updates the options. |
| options = MutationObserverInit(); |
| options.set_child_list(true); |
| EXPECT_TRUE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(1, observer_list.registered_observers().size()); |
| EXPECT_EQ(observer, observer_list.registered_observers()[0].observer()); |
| EXPECT_FALSE( |
| observer_list.registered_observers()[0].options().has_attributes()); |
| EXPECT_FALSE( |
| observer_list.registered_observers()[0].options().has_character_data()); |
| EXPECT_TRUE(observer_list.registered_observers()[0].options().child_list()); |
| |
| // Remove the observer. |
| observer_list.RemoveMutationObserver(observer); |
| EXPECT_EQ(0, observer_list.registered_observers().size()); |
| } |
| |
| TEST_F(MutationObserverTest, LazilySetOptions) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| |
| RegisteredObserverList observer_list(target.get()); |
| |
| // |attributes| gets set if an attribute-related option is set. |
| MutationObserverInit options; |
| options.set_attribute_old_value(true); |
| EXPECT_TRUE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(1, observer_list.registered_observers().size()); |
| EXPECT_TRUE(observer_list.registered_observers()[0].options().attributes()); |
| |
| // |character_data| gets set if an attribute-related option is set. |
| options = MutationObserverInit(); |
| options.set_character_data_old_value(true); |
| EXPECT_TRUE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(1, observer_list.registered_observers().size()); |
| EXPECT_TRUE( |
| observer_list.registered_observers()[0].options().character_data()); |
| } |
| |
| TEST_F(MutationObserverTest, InvalidOptions) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| |
| RegisteredObserverList observer_list(target.get()); |
| |
| // No type of mutation is set. |
| MutationObserverInit options; |
| EXPECT_FALSE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(0, observer_list.registered_observers().size()); |
| |
| // |attributes| is set as false, but attribute old data is set. |
| options.set_attributes(false); |
| options.set_attribute_old_value(true); |
| EXPECT_FALSE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(0, observer_list.registered_observers().size()); |
| |
| // |character_data| is set as false, but attribute old data is set. |
| options = MutationObserverInit(); |
| options.set_character_data(false); |
| options.set_character_data_old_value(true); |
| EXPECT_FALSE(observer_list.AddMutationObserver(observer, options)); |
| EXPECT_EQ(0, observer_list.registered_observers().size()); |
| } |
| |
| TEST_F(MutationObserverTest, InvalidOptionsRaisesException) { |
| scoped_refptr<dom::Element> target = CreateDiv(); |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit invalid_options; |
| |
| script::testing::MockExceptionState exception_state; |
| EXPECT_CALL(exception_state, SetSimpleExceptionVA(script::kTypeError, _, _)); |
| observer->Observe(target, invalid_options, &exception_state); |
| } |
| |
| TEST_F(MutationObserverTest, AddChildNodes) { |
| scoped_refptr<Element> root = CreateDiv(); |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_subtree(true); |
| options.set_child_list(true); |
| |
| const void* async_task_1; |
| const void* async_task_2; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "childList", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task_1)) |
| .WillOnce(SaveArg<0>(&async_task_2)); |
| observer->Observe(root, options); |
| |
| scoped_refptr<Element> child1 = document()->CreateElement("div"); |
| scoped_refptr<Element> child2 = document()->CreateElement("div"); |
| ASSERT_TRUE(child1); |
| ASSERT_TRUE(child2); |
| |
| root->AppendChild(child1); |
| child1->AppendChild(child2); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_1)); |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task_2)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(2, records.size()); |
| EXPECT_EQ("childList", records.at(0)->type()); |
| EXPECT_EQ(root, records.at(0)->target()); |
| ASSERT_TRUE(records.at(0)->removed_nodes()); |
| ASSERT_EQ(0, records.at(0)->removed_nodes()->length()); |
| ASSERT_TRUE(records.at(0)->added_nodes()); |
| ASSERT_EQ(1, records.at(0)->added_nodes()->length()); |
| EXPECT_EQ(child1, records.at(0)->added_nodes()->Item(0)); |
| |
| EXPECT_EQ("childList", records.at(1)->type()); |
| EXPECT_EQ(child1, records.at(1)->target()); |
| ASSERT_TRUE(records.at(1)->removed_nodes()); |
| ASSERT_EQ(0, records.at(1)->removed_nodes()->length()); |
| ASSERT_TRUE(records.at(1)->added_nodes()); |
| ASSERT_EQ(1, records.at(1)->added_nodes()->length()); |
| EXPECT_EQ(child2, records.at(1)->added_nodes()->Item(0)); |
| } |
| |
| TEST_F(MutationObserverTest, RemoveChildNode) { |
| scoped_refptr<Element> root = CreateDiv(); |
| scoped_refptr<Element> child1 = document()->CreateElement("div"); |
| scoped_refptr<Element> child2 = document()->CreateElement("div"); |
| ASSERT_TRUE(child1); |
| ASSERT_TRUE(child2); |
| |
| root->AppendChild(child1); |
| child1->AppendChild(child2); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_subtree(true); |
| options.set_child_list(true); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "childList", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| child1->RemoveChild(child2); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ("childList", records.at(0)->type()); |
| EXPECT_EQ(child1, records.at(0)->target()); |
| ASSERT_TRUE(records.at(0)->removed_nodes()); |
| ASSERT_EQ(1, records.at(0)->removed_nodes()->length()); |
| EXPECT_EQ(child2, records.at(0)->removed_nodes()->Item(0)); |
| } |
| |
| TEST_F(MutationObserverTest, MutateCharacterData) { |
| scoped_refptr<Element> root = CreateDiv(); |
| scoped_refptr<Text> text = document()->CreateTextNode("initial-data"); |
| ASSERT_TRUE(text); |
| root->AppendChild(text); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_subtree(true); |
| options.set_character_data(true); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| text->set_data("new-data"); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ("characterData", records.at(0)->type()); |
| EXPECT_EQ(text, records.at(0)->target()); |
| EXPECT_FALSE(records.at(0)->old_value()); |
| } |
| |
| TEST_F(MutationObserverTest, MutateCharacterDataWithOldValue) { |
| scoped_refptr<Element> root = CreateDiv(); |
| scoped_refptr<Text> text = document()->CreateTextNode("initial-data"); |
| ASSERT_TRUE(text); |
| root->AppendChild(text); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_subtree(true); |
| options.set_character_data_old_value(true); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| text->set_data("new-data"); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ("characterData", records.at(0)->type()); |
| EXPECT_EQ(text, records.at(0)->target()); |
| ASSERT_TRUE(records.at(0)->old_value()); |
| EXPECT_STREQ("initial-data", records.at(0)->old_value()->c_str()); |
| } |
| |
| TEST_F(MutationObserverTest, MutateAttribute) { |
| scoped_refptr<Element> root = CreateDiv(); |
| root->SetAttribute("banana", "purple"); |
| root->SetAttribute("apple", "green"); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| script::Sequence<std::string> filter; |
| filter.push_back("banana"); |
| MutationObserverInit options; |
| options.set_attributes(true); |
| options.set_attribute_filter(filter); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "attributes", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| root->SetAttribute("banana", "yellow"); |
| root->SetAttribute("apple", "brown"); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ("attributes", records.at(0)->type()); |
| EXPECT_EQ(root, records.at(0)->target()); |
| EXPECT_FALSE(records.at(0)->old_value()); |
| ASSERT_TRUE(records.at(0)->attribute_name()); |
| EXPECT_STREQ("banana", records.at(0)->attribute_name()->c_str()); |
| } |
| |
| TEST_F(MutationObserverTest, MutateAttributeWithOldValue) { |
| scoped_refptr<Element> root = CreateDiv(); |
| root->SetAttribute("banana", "purple"); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_attribute_old_value(true); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskScheduled(_, "attributes", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| root->SetAttribute("banana", "yellow"); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| ASSERT_EQ(1, records.size()); |
| EXPECT_EQ("attributes", records.at(0)->type()); |
| EXPECT_EQ(root, records.at(0)->target()); |
| ASSERT_TRUE(records.at(0)->old_value()); |
| ASSERT_TRUE(records.at(0)->attribute_name()); |
| EXPECT_STREQ("banana", records.at(0)->attribute_name()->c_str()); |
| EXPECT_STREQ("purple", records.at(0)->old_value()->c_str()); |
| } |
| |
| TEST_F(MutationObserverTest, Disconnect) { |
| scoped_refptr<Element> root = CreateDiv(); |
| scoped_refptr<Text> text = document()->CreateTextNode("initial-data"); |
| ASSERT_TRUE(text); |
| root->AppendChild(text); |
| |
| scoped_refptr<MutationObserver> observer = CreateObserver(); |
| MutationObserverInit options; |
| options.set_subtree(true); |
| options.set_character_data(true); |
| |
| const void* async_task; |
| EXPECT_CALL(*debugger_hooks(), |
| AsyncTaskScheduled(_, "characterData", kOneshot)) |
| .WillOnce(SaveArg<0>(&async_task)); |
| observer->Observe(root, options); |
| |
| // This should queue up a mutation record. |
| text->set_data("new-data"); |
| |
| EXPECT_CALL(*debugger_hooks(), AsyncTaskCanceled(async_task)); |
| observer->Disconnect(); |
| MutationObserver::MutationRecordSequence records = observer->TakeRecords(); |
| // MutationObserver.disconnect() should clear any queued records. |
| EXPECT_EQ(0, records.size()); |
| |
| // This should not queue a mutation record. |
| text->set_data("more-new-data"); |
| records = observer->TakeRecords(); |
| EXPECT_EQ(0, records.size()); |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |