Chrome扩展:从Content Script动态添加AngularJS控制器的正确方法



我正在使用AngularJS开发Chrome扩展。

我正在使用以下内容脚本代码将控制器附加到所需的 DOM 元素到网页

setController() {
if(this.setContollerCondition) {
this.controllerElement.attr('ng-controller', 'Controller as cntrl');
}
}

此方法并不总是有效,因为控制器构造函数未被调用,并且"ng-scope"未应用于元素。

我想问一下,动态连接控制器的正确方法是什么。

如果这种方法是正确的,我可能在哪里出错?

以下是页面加载时Chrome扩展程序中发生的活动的流程。

  1. 如果 ng-app 未初始化,则对其进行初始化。
  2. 初始化充当事件侦听器的函数
  3. 检查主机网址,根据网址运行相应的初始化功能
    • 如果添加了特定的 dom 元素,则添加扩展的自定义 DOM 元素,然后使用 setController() 函数将控制器添加到元素中。

几乎所有函数都是异步调用。

当前发生的行为如下

  1. 控制器设置正确,与 web.whatsapp.com 的特定主机一起工作正常
    • 在这里,扩展中的所有按钮都放置在适当的位置,并且工作得非常出色。
  2. 对于另一台主机,控制器有时会设置,有时不会设置。这里的主人很 www.linkedin.com
    • 这里 按钮有时会放置。如果未放置,ng-scope不会应用于 DOM 元素,因为控制器构造函数未初始化。
  3. 对于第三台主机,永远不会设置控制器。这是 mail.google.com
    • 在这里,按钮总是被放置,但范围没有应用于所需的 DOM 元素,并且没有调用控制器的构造函数。

该项目使用 Webpack 和 ES6。

以下文件与实际代码类似,可帮助您更好地了解问题。

manifest.json

{
"manifest_version": 2,
"name": "MyExtension",
"short_name": "MyExtension",
"omnibox": {
"keyword": "MyExtension"
},
"description": "Description",
"version": "2.0.7",
"version_name": "2.0.7",
"browser_action": {
"default_icon": {
"128": "assets/images/icon-v2-128px.png",
"16": "assets/images/icon-v2-16px.png",
"48": "assets/images/icon-v2-48px.png"
},
"default_title": "MyExtension",
"default_popup": "index.html"
},
"content_scripts": [ {
"js": [ "assets/lib/angular.min.js", "assets/lib/jquery-2.2.3.min.js", "assets/lib/bootstrap.min.js", "dist/contentScript.bundle.js" ],
"css": ["assets/css/font-awesome.min.css", "assets/css/bootstrapInject.css", "assets/css/injectingStyle.css"],
"matches": ["*://*.linkedin.com/*", "*://web.whatsapp.com/*", "*://mail.google.com/*"],
"run_at": "document_end"
} ],
"background": {
"page": "background.html"
},
"permissions": [
"tabs",
"http://*/*",
"https://*/*",
"contextMenus",
"notifications",
"unlimitedStorage",
"storage"
],
"icons": {
"128": "assets/images/icon-v2-128px.png",
"16": "assets/images/icon-v2-16px.png",
"48": "assets/images/icon-v2-48px.png",
"16": "assets/images/icon-v2-16px.png"
},
"web_accessible_resources": [
"all required files here"
],
"update_url": "https://clients2.google.com/service/update2/crx",
"content_security_policy": "script-src 'self' 'unsafe-eval' https://ssl.google-analytics.com https://connect.facebook.net https://platform.twitter.com https://staticxx.facebook.com; object-src 'self'"
}

以下是init.js它附加了控制器

export default class AddContactModule {
constructor() {
// Check for the webpage we are currently in (linkedin, facebook, twitter, naukri, whatsapp, mail.google)
this.currentHost = window.location.host.split('.');
this.app = 'extension-extension';
// Variable to intialize parent of button we will add
this.globalButtonVar = '';
this.copyglobalButtonVar = '';
this.globalFormTemplate = '';
this.globalForm = '';
this.isLoggedin = false;
// Get ng-app param and check if its already present if it is then no need to intialize angular module again
this.ifAppDirective = document.querySelector('[ng-app]');
console.log("hello1");
this.init();
}
init() {
this.eventHandling();
this.addContactModel();
this.checkHost();
}
// Function to set the flag to check if user is logged in or not
eventHandling() {
if (!this.ifAppDirective && !$('.ng-scope').length) {
// If ng-app is not initialized initialize ng-app
$('body').attr('ng-app', this.app);
this.miscellaneousEvent();
}
}
// Function to call when user syncing images
// Function to handle other events
miscellaneousEvent() {
$(document).on('click', '.dropdown-menu .input-group-btn, .dropdown-menu .checkbox, .dropdown-menu a', function (e) {
e.stopPropagation();
});
// Cancel the fetch list dialoge box
$('body').on('click', '#fetchContainer-step2 > .innerContainer .close-fetch, .close-intro, .close-dialog', function() {
let thisEle = $(this).attr('id');
let scope = angular.element($('.pane.pane-three')).scope();
scope.$apply(function() {
scope.contact.cancelFetch(false, thisEle);
});
});
// Close the notification of start sync message
$('body').on('click', '#extension-close-notice', function(event) {
let scope = angular.element($('#extension-close-notice')).scope();
scope.$apply(function() {
scope.closeNotification();
});
});
// If clicked on linkedin save button open call the trigger function to open contact details
$('body').off('click', '#linkedin-save').on('click', '#linkedin-save', function() {
let scope = angular.element($('#profile-wrapper')).scope();
scope.$apply(function() {
scope.contact.linkedinFetch();
});
});
// trigger save function for gmail
$('body').off('click', '#gmail-save').on('click', '#gmail-save', function() {
let scope = angular.element($('.nH.g.id')).scope();
scope.$apply(function() {
scope.gmailFetch();
});
});
}
// Function to handle addcontactmodel open/close events
addContactModel() {
$('body').on('show.bs.modal', '#addContactModal', function () {
$(this).after('<div class="modal-backdrop fade in"></div>');
}).on('hide.bs.modal', '#addContactModal', function () {
$('.modal-backdrop').remove();
});
$('body').off('shown.bs.modal', '#addContactModal').on('shown.bs.modal', '#addContactModal', (event) => {
let openedBy = $(event.relatedTarget);
this.loginStatus();
// Check for the target popup is opened by whatzup from group
if(openedBy.hasClass('whatsappGroupBtn') || openedBy.hasClass('whatsappGroupChat')) {
let contactObj = {
ContactMobile: openedBy.data('contactnumber'),
given: openedBy.data('name'),
profilePic: openedBy.data('profilepic'),
};
// Reduce size of modal for group detail page
if(openedBy.hasClass('whatsappGroupBtn')) {
contactObj.addFrom = 'wa_group';
} else {
contactObj.addFrom = 'wa_chat';
}
// Assigne all found details to form, for this call controller function
let scope = angular.element($('#addContactForm')).scope();
// Set form to its default state
scope.$apply(() => {
scope.contact.updateForm(contactObj);
});
}
}).off('hidden.bs.modal', '#addContactModal').on('hidden.bs.modal', '#addContactModal', () => {
$('.arrowPointing').hide();
// Assigne all found details to form, for this call controller function
let scope = angular.element($('#addContactForm')).scope();
// Set form to its default state
scope.$apply(() => {
scope.contact.addNewContact = false;
// Helps to decide if the contact should be added to existing or new
scope.contact.addtoExisting = false;
scope.contact.noContact = false;
// Contains searched contact list
scope.contact.contactList = [];
// Search field model, this will have the searching text
scope.contact.searchKey = '';
scope.contact.searchCompleted = false;
scope.contact.globalTimeout = 0;
//lets you add another unknown contact when one unknown already has been added
scope.contact.contactAdded = false;
scope.contact.contactSaved = false;
});
});
}
// Hide/show login arrow depends on the login status
// Depends on host call linkedin init or facebookinit
checkHost() {
switch(this.currentHost[1]) {
case 'linkedin': {
this.initLinkedin();
break;
}
case 'google': {
this.initGmail();
break;
}
case 'whatsapp': {
this.initWhatsapp();
} default: {}
}
}
// Function to add template & button to linkedin
initLinkedin() {
// Variable to check if the button we are adding is in extra info section or not
let ifTablist = 1;
let linkedinForm = ''; 
// Button template if adding to extra info section
let buttonTemplate = '<span class="inline-block extension-save-button"><button type="button" id="linkedin-save" class="secondary top-card-action link-without-visited-state"><img src="https://d73xd4ooutekr.cloudfront.net/v4/img/logo-42.png"> Save Contact</a></span>';
let buttonContainerLength = 0;
// Get the parent
$('body').on('DOMNodeInserted', (event) => {
if(event.target.id == 'profile-wrapper' && !buttonContainerLength) {
this.globalButtonVar = this.copyglobalButtonVar = $('body').find('#profile-wrapper');
buttonContainerLength = this.globalButtonVar.find('.pv-top-card-section__actions').length;
// Check if the selected parent is not present 
if(buttonContainerLength) {
this.globalButtonVar.find('.pv-top-card-section__actions').append(buttonTemplate);
this.setController();
}
}
});
}
// Function to set ng-controller
setController() {
if(this.globalButtonVar.length) {
this.globalButtonVar.attr('ng-controller', 'ContactModalController as contact');
}
}
}

以下是控制器.js文件

"use strict";
import AddContactModule from './init'
export default class ContactModalController {
constructor($scope, $compile) {
// Intializing dependencies to the scope variable
this.$scope = $scope;
this.$compile = $compile;
/* Assign required objects which need to make everything works */
}

// Function to fetch linkedin details
linkedinFetch() {
// Show more button, this will show all contact details of current user
let saveBtn = $('.contact-see-more-less')
if(saveBtn.attr('data-control-name') == 'contact_see_more') {
saveBtn.click();
}
this.resetModal();
this.grabContactDetails.getLinkedinDetails(this.$scope, (response) => {
this.contactObj = response;
// default selected List
this.contactObj.selectedList = [];
$('#addContactModal').modal('show');
});
}
}

我遇到了类似的问题,控制器没有初始化。

我将应用程序设置在HTML 标签上,并将控制器设置在正文标签上。

据我说,由于DOM元素的动态加载,问题即将到来

最新更新